diff --git a/sdk/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/PackageVersion.java b/sdk/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/PackageVersion.java new file mode 100644 index 0000000000..203c898284 --- /dev/null +++ b/sdk/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/PackageVersion.java @@ -0,0 +1,64 @@ +package com.daml.ledger.javaapi.data; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class PackageVersion implements Comparable { + private final int[] segments; + + /** + * Creates a PackageVersion from the provided segments. + *

+ * This method is meant only for internal API usage. + * It is marked unsafe as it does not validate the input + * according to the accepted ledger format of PackageVersion. + */ + public PackageVersion(int[] segments) { + this.segments = segments; + } + + /** + * Parses the provided String value into a PackageVersion. + *

+ * This method is meant only for internal API usage. + * It is marked unsafe as it does not validate the input + * according to the accepted ledger format of PackageVersion. + */ + public static PackageVersion unsafeFromString(@NonNull String version) { + String[] parts = version.split("\\."); + int[] segments = new int[parts.length]; + for (int i = 0; i < parts.length; i++) { + segments[i] = Integer.parseInt(parts[i]); + if (segments[i] < 0) { + throw new IllegalArgumentException("Invalid version. No negative segments allowed: " + version); + } + } + return new PackageVersion(segments); + } + + @Override + public int compareTo(PackageVersion other) { + return Arrays.compare(this.segments, other.segments); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PackageVersion that = (PackageVersion) o; + return Arrays.equals(segments, that.segments); + } + + @Override + public int hashCode() { + return Arrays.hashCode(segments); + } + + @Override + public String toString() { + return Arrays.stream(segments).mapToObj(Integer::toString) + .collect(Collectors.joining(".")); + } +} diff --git a/sdk/canton/community/bindings-java/src/test/scala/com/daml/ledger/javaapi/data/PackageVersionSpec.scala b/sdk/canton/community/bindings-java/src/test/scala/com/daml/ledger/javaapi/data/PackageVersionSpec.scala new file mode 100644 index 0000000000..10f0bb452d --- /dev/null +++ b/sdk/canton/community/bindings-java/src/test/scala/com/daml/ledger/javaapi/data/PackageVersionSpec.scala @@ -0,0 +1,40 @@ +package com.daml.ledger.javaapi.data + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import scala.util.Random + +class PackageVersionSpec extends AnyFlatSpec with Matchers { + + "PackageVersion" should "be parsed correctly from String" in { + val packageVersion = PackageVersion.unsafeFromString("1.22.333") + packageVersion.toString shouldBe "1.22.333" + packageVersion shouldBe new PackageVersion(Array(1, 22, 333)) + } + + "PackageVersion" should "not allow negative or non-integers" in { + an[IllegalArgumentException] should be thrownBy PackageVersion.unsafeFromString("0.-1") + an[IllegalArgumentException] should be thrownBy PackageVersion.unsafeFromString("0.beef") + } + + "PackageVersion" should "be ordered correctly" in { + + val expectedOrderedPackageVersions = Seq( + // Lowest possible package version + PackageVersion.unsafeFromString("0"), + PackageVersion.unsafeFromString("0.1"), + PackageVersion.unsafeFromString("0.11"), + PackageVersion.unsafeFromString("1.0"), + PackageVersion.unsafeFromString("2"), + PackageVersion.unsafeFromString("10"), + PackageVersion.unsafeFromString(s"${Int.MaxValue}"), + PackageVersion.unsafeFromString(s"${Int.MaxValue}.3"), + PackageVersion.unsafeFromString(s"${Int.MaxValue}." * 23 + "99"), + ) + + Random + .shuffle(expectedOrderedPackageVersions) + .sorted should contain theSameElementsInOrderAs expectedOrderedPackageVersions + } +} diff --git a/sdk/language-support/java/codegen/BUILD.bazel b/sdk/language-support/java/codegen/BUILD.bazel index e250f635a2..f817e030f0 100644 --- a/sdk/language-support/java/codegen/BUILD.bazel +++ b/sdk/language-support/java/codegen/BUILD.bazel @@ -239,8 +239,10 @@ scala_source_jar( ], exclude = test_exclusions.get(ver, []), ), + enable_interfaces = ver == "2.dev", project_name = "integration-tests-model", target = ver, + version = "1.2.3", ) for ver in COMPILER_LF_VERSIONS ] diff --git a/sdk/language-support/java/codegen/codegen.bzl b/sdk/language-support/java/codegen/codegen.bzl index 911714383b..e15ae346aa 100644 --- a/sdk/language-support/java/codegen/codegen.bzl +++ b/sdk/language-support/java/codegen/codegen.bzl @@ -68,5 +68,5 @@ def dar_to_java(**kwargs): break test_exclusions = { - "2.1": ["src/it/daml/Tests/ContractKeys.daml"], + "2.1": ["src/it/daml/Tests/ContractKeys.daml", "src/it/daml/Tests/SimpleInterface.daml"], } diff --git a/sdk/language-support/java/codegen/src/it/daml/Tests/SimpleInterface.daml b/sdk/language-support/java/codegen/src/it/daml/Tests/SimpleInterface.daml new file mode 100644 index 0000000000..78c48e151b --- /dev/null +++ b/sdk/language-support/java/codegen/src/it/daml/Tests/SimpleInterface.daml @@ -0,0 +1,10 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + + +module Tests.SimpleInterface where + +data SomeViewType = SomeViewType { field: Text } + +interface SimpleInterface where + viewtype SomeViewType diff --git a/sdk/language-support/java/codegen/src/it/java-2.dev/com/daml/AllTests.java b/sdk/language-support/java/codegen/src/it/java-2.dev/com/daml/AllTests.java index b01cff6ec2..4abc6a8ea2 100644 --- a/sdk/language-support/java/codegen/src/it/java-2.dev/com/daml/AllTests.java +++ b/sdk/language-support/java/codegen/src/it/java-2.dev/com/daml/AllTests.java @@ -7,5 +7,10 @@ import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) -@Suite.SuiteClasses({AllGenericTests.class, ContractKeysTest.class, TextMapTest.class}) +@Suite.SuiteClasses({ + AllGenericTests.class, + ContractKeysTest.class, + TextMapTest.class, + InterfacePackageNameAndVersionTest.class +}) public class AllTests {} diff --git a/sdk/language-support/java/codegen/src/it/java-2.dev/com/daml/InterfacePackageNameAndVersionTest.java b/sdk/language-support/java/codegen/src/it/java-2.dev/com/daml/InterfacePackageNameAndVersionTest.java new file mode 100644 index 0000000000..46ce78047b --- /dev/null +++ b/sdk/language-support/java/codegen/src/it/java-2.dev/com/daml/InterfacePackageNameAndVersionTest.java @@ -0,0 +1,26 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml; + +import static org.junit.Assert.assertEquals; + +import com.daml.ledger.javaapi.data.PackageVersion; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import tests.simpleinterface.SimpleInterface; + +// TODO: Merge with PackageNameAndVersionTest.java once interfaces are marked stable +@RunWith(JUnitPlatform.class) +public class InterfacePackageNameAndVersionTest { + @Test + void packageName() { + assertEquals(SimpleInterface.PACKAGE_NAME, "integration-tests-model"); + } + + @Test + void packageVersion() { + assertEquals(SimpleInterface.PACKAGE_VERSION, PackageVersion.unsafeFromString("1.2.3")); + } +} diff --git a/sdk/language-support/java/codegen/src/it/java/com/daml/AllGenericTests.java b/sdk/language-support/java/codegen/src/it/java/com/daml/AllGenericTests.java index d8cfe48696..03ddb41123 100644 --- a/sdk/language-support/java/codegen/src/it/java/com/daml/AllGenericTests.java +++ b/sdk/language-support/java/codegen/src/it/java/com/daml/AllGenericTests.java @@ -15,6 +15,7 @@ import org.junit.runners.Suite; DecoderTest.class, GenMapTest.class, ListTest.class, + PackageNameAndVersionTest.class, NumericTest.class, OptionalTest.class, ParametrizedContractIdTest.class, diff --git a/sdk/language-support/java/codegen/src/it/java/com/daml/PackageNameAndVersionTest.java b/sdk/language-support/java/codegen/src/it/java/com/daml/PackageNameAndVersionTest.java new file mode 100644 index 0000000000..cb566d72b7 --- /dev/null +++ b/sdk/language-support/java/codegen/src/it/java/com/daml/PackageNameAndVersionTest.java @@ -0,0 +1,25 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml; + +import static org.junit.Assert.assertEquals; + +import com.daml.ledger.javaapi.data.PackageVersion; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import tests.template1.SimpleTemplate; + +@RunWith(JUnitPlatform.class) +public class PackageNameAndVersionTest { + @Test + void packageName() { + assertEquals(SimpleTemplate.PACKAGE_NAME, "integration-tests-model"); + } + + @Test + void packageVersion() { + assertEquals(SimpleTemplate.PACKAGE_VERSION, PackageVersion.unsafeFromString("1.2.3")); + } +} diff --git a/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/ClassForType.scala b/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/ClassForType.scala index 5b0a88db2b..1b1b1b9019 100644 --- a/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/ClassForType.scala +++ b/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/ClassForType.scala @@ -66,6 +66,7 @@ object ClassForType extends StrictLogging { typeWithContext.auxiliarySignatures, typeWithContext.interface.packageId, interfaceName, + typeWithContext.interface.metadata, ) } yield javaFile(packageName, interfaceClass) diff --git a/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/ClassGenUtils.scala b/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/ClassGenUtils.scala index b9048a1002..93353cbb30 100644 --- a/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/ClassGenUtils.scala +++ b/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/ClassGenUtils.scala @@ -4,7 +4,7 @@ package com.daml.lf.codegen.backend.java.inner import com.daml.lf.data.Ref -import Ref.{ChoiceName, PackageId} +import Ref.{ChoiceName, PackageId, PackageName, PackageVersion} import com.daml.lf.typesig.{DefDataType, Record, TypeCon} import com.daml.lf.typesig.PackageSignature.TypeDecl @@ -50,6 +50,8 @@ private[inner] object ClassGenUtils { } val templateIdFieldName = "TEMPLATE_ID" + val packageNameFieldName = "PACKAGE_NAME" + val packageVersionFieldName = "PACKAGE_VERSION" val companionFieldName = "COMPANION" val archiveChoiceName = ChoiceName assertFromString "Archive" @@ -71,6 +73,44 @@ private[inner] object ClassGenUtils { ) .build() + def generatePackageNameField(packageName: PackageName) = + FieldSpec + .builder( + ClassName.get(classOf[String]), + packageNameFieldName, + Modifier.STATIC, + Modifier.FINAL, + Modifier.PUBLIC, + ) + .initializer("$S", packageName) + .build() + + def generatePackageVersionField(packageVersion: PackageVersion) = { + val packageVersionSegmentIntArrLiteral = + packageVersion.segments.toArray.mkString("{", ", ", "}") + val intArrayTypeName = ArrayTypeName.of(classOf[Int]) + FieldSpec + .builder( + ClassName.get(classOf[javaapi.data.PackageVersion]), + packageVersionFieldName, + Modifier.STATIC, + Modifier.FINAL, + Modifier.PUBLIC, + ) + .initializer( + CodeBlock + .builder() + .add( + "new $T(new $T $L)", + ClassName.get(classOf[javaapi.data.PackageVersion]), + intArrayTypeName, + packageVersionSegmentIntArrLiteral, + ) + .build() + ) + .build() + } + def generateFlattenedCreateOrExerciseMethod( name: String, returns: TypeName, diff --git a/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/InterfaceClass.scala b/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/InterfaceClass.scala index 8009003f02..b96b0c7956 100644 --- a/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/InterfaceClass.scala +++ b/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/InterfaceClass.scala @@ -8,8 +8,7 @@ import com.daml.ledger.javaapi.data.codegen.{Contract, InterfaceCompanion} import com.daml.lf.codegen.NodeWithContext.AuxiliarySignatures import com.daml.lf.codegen.backend.java.inner.TemplateClass.toChoiceNameField import com.daml.lf.data.Ref.{ChoiceName, PackageId, QualifiedName} -import com.daml.lf.typesig -import typesig.DefInterface +import com.daml.lf.typesig.{DefInterface, PackageMetadata} import com.squareup.javapoet._ import com.typesafe.scalalogging.StrictLogging import scalaz.-\/ @@ -26,6 +25,7 @@ object InterfaceClass extends StrictLogging { typeDeclarations: AuxiliarySignatures, packageId: PackageId, interfaceId: QualifiedName, + packageMetadata: PackageMetadata, )(implicit packagePrefixes: PackagePrefixes): TypeSpec = TrackLineage.of("interface", interfaceName.simpleName()) { logger.info("Start") @@ -33,6 +33,8 @@ object InterfaceClass extends StrictLogging { .classBuilder(interfaceName) .addModifiers(Modifier.FINAL, Modifier.PUBLIC) .addField(generateTemplateIdField(packageId, interfaceId)) + .addField(ClassGenUtils.generatePackageNameField(packageMetadata.name)) + .addField(ClassGenUtils.generatePackageVersionField(packageMetadata.version)) .addFields( TemplateClass .generateChoicesMetadata( diff --git a/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/TemplateClass.scala b/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/TemplateClass.scala index 2a9a17c08d..1820050812 100644 --- a/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/TemplateClass.scala +++ b/sdk/language-support/java/codegen/src/main/scala/com/daml/lf/codegen/backend/java/inner/TemplateClass.scala @@ -4,7 +4,7 @@ package com.daml.lf.codegen.backend.java.inner import com.daml.ledger.javaapi -import ClassGenUtils.{companionFieldName, templateIdFieldName, generateGetCompanion} +import ClassGenUtils.{companionFieldName, generateGetCompanion, templateIdFieldName} import com.daml.lf.codegen.TypeWithContext import com.daml.lf.data.Ref import Ref.ChoiceName @@ -46,6 +46,8 @@ private[inner] object TemplateClass extends StrictLogging { .addModifiers(Modifier.FINAL, Modifier.PUBLIC) .superclass(classOf[javaapi.data.Template]) .addField(generateTemplateIdField(typeWithContext)) + .addField(generatePackageNameField(typeWithContext)) + .addField(generatePackageVersionField(typeWithContext)) .addMethod(generateCreateMethod(className)) .addMethods( generateDeprecatedStaticExerciseByKeyMethods( @@ -504,6 +506,12 @@ private[inner] object TemplateClass extends StrictLogging { typeWithContext.name, ) + private def generatePackageVersionField(typeWithContext: TypeWithContext) = + ClassGenUtils.generatePackageVersionField(typeWithContext.interface.metadata.version) + + private def generatePackageNameField(typeWithContext: TypeWithContext) = + ClassGenUtils.generatePackageNameField(typeWithContext.interface.metadata.name) + def generateChoicesMetadata( templateClassName: ClassName, templateChoices: Map[ChoiceName, TemplateChoice.FWT],