Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

non-reflective abstractions for Java codegen Contracts #13724

Merged
merged 29 commits into from
May 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
72ae4dc
shift some generated Contract code to a handwritten superclass
S11001001 Apr 27, 2022
5d43d23
support contracts with keys as well
S11001001 Apr 27, 2022
f6aada1
companion types for templates and contracts
S11001001 Apr 27, 2022
99837d0
simplify the key-dependent generator branches
S11001001 Apr 27, 2022
32b2b85
reformulate ContractCompanion so generated code doesn't have to subclass
S11001001 Apr 27, 2022
d11ccd8
generate a `COMPANION` for each template
S11001001 Apr 27, 2022
da92178
missing @FunctionalInterface
S11001001 Apr 28, 2022
70cb4c8
slightly smaller companion constructor
S11001001 Apr 28, 2022
2ea9e9d
optional newlines for a nicer constructor call
S11001001 Apr 28, 2022
4a02c9b
fromIdAndRecord is a static forwarder
S11001001 Apr 28, 2022
ca8fb95
encapsulate the idea of a static forwarder to companion
S11001001 Apr 28, 2022
173b87a
replace fromCreatedEvent with a forwarder
S11001001 Apr 28, 2022
23cccf8
reformulate companion forwarder without generating patterns
S11001001 Apr 28, 2022
6430ef0
deprecated fromIdAndRecord is a companion forwarder
S11001001 Apr 28, 2022
d6059cd
small reformat
S11001001 Apr 28, 2022
345fde8
remove magic strings TEMPLATE_ID and COMPANION
S11001001 Apr 28, 2022
5b2ba1c
Merge commit '615d2e62068c0213b8c43f2a73f7ef2ead23744f' into 13471-ja…
S11001001 Apr 28, 2022
8f2f077
simpler super constructor derivation
S11001001 Apr 29, 2022
c458134
reformat
S11001001 Apr 29, 2022
67672f7
link Contract to its companion
S11001001 Apr 29, 2022
a18e73d
do not generate toString for Contract
S11001001 Apr 29, 2022
4694cef
note on getCompanion type
S11001001 Apr 29, 2022
2af8bd0
use the companion in some ledger reading tests
S11001001 Apr 29, 2022
789183e
test the companion's fromCreatedEvent and Contract's toString
S11001001 Apr 29, 2022
77f7df3
document changes in template Java codegen
S11001001 Apr 29, 2022
dce512e
add changelog
S11001001 Apr 29, 2022
d8836b2
rewrite changelog
S11001001 May 6, 2022
6b53ca1
Merge commit '1b4f9bb2f8f6fb6139d694764f039e9c37d26978' into 13471-ja…
S11001001 May 6, 2022
0de313e
remove unused abstract method; note deprecation times
S11001001 May 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions docs/source/app-dev/bindings-java/codegen.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,18 @@ A file is generated that defines three Java classes:

.. code-block:: java
:caption: com/acme/templates/Bar.java
:emphasize-lines: 3,14,24
:emphasize-lines: 3,22,33

package com.acme.templates;

public class Bar extends Template {

public static final Identifier TEMPLATE_ID = new Identifier("some-package-id", "Com.Acme.Templates", "Bar");

public static final ContractCompanion.WithKey<Contract, ContractId, Bar, BarKey> COMPANION =
new ContractCompanion.WithKey<>("com.acme.templates.Bar",
TEMPLATE_ID, ContractId::new, Bar::fromValue, Contract::new, e -> BarKey.fromValue(e));

public final String owner;
public final String name;

Expand All @@ -190,7 +194,8 @@ A file is generated that defines three Java classes:

public CreateAndExerciseCommand createAndExerciseBar_SomeChoice(String aName) { /* ... */ }

public static class ContractId {
public static class ContractId extends com.daml.ledger.javaapi.data.codegen.ContractId<Bar> {
// inherited:
public final String contractId;

public ExerciseCommand exerciseArchive(Unit arg) { /* ... */ }
Expand All @@ -200,7 +205,8 @@ A file is generated that defines three Java classes:
public ExerciseCommand exerciseBar_SomeChoice(String aName) { /* ... */ }
}

public static class Contract {
public static class Contract extends ContractWithKey<ContractId, Bar, BarKey> {
// inherited:
public final ContractId id;
public final Bar data;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.ledger.javaapi.data.codegen;

import java.util.Objects;
import java.util.Optional;
import java.util.Set;

/**
* A superclass for all codegen-generated Contracts.
*
* @param <Id> The generated contract ID class alongside the generated Contract class.
* @param <Data> The containing template's associated record type.
*/
public abstract class Contract<Id, Data> implements com.daml.ledger.javaapi.data.Contract {
public final Id id;

public final Data data;

public final Optional<String> agreementText;

public final Set<String> signatories;

public final Set<String> observers;

protected Contract(
Id id,
Data data,
Optional<String> agreementText,
Set<String> signatories,
Set<String> observers) {
this.id = id;
this.data = data;
this.agreementText = agreementText;
this.signatories = signatories;
this.observers = observers;
}

// concrete 1st type param would need a self-reference type param in Contract
protected abstract ContractCompanion<? extends Contract<Id, Data>, Id, Data> getCompanion();

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null) {
return false;
}
if (!(object instanceof Contract)) {
return false;
}
Contract<?, ?> other = (Contract<?, ?>) object;
// a bespoke Contract-specific equals is unneeded, as 'data' here
// already compares the associated record types' classes
return this.id.equals(other.id)
&& this.data.equals(other.data)
&& this.agreementText.equals(other.agreementText)
&& this.signatories.equals(other.signatories)
&& this.observers.equals(other.observers);
}

@Override
public int hashCode() {
return Objects.hash(this.id, this.data, this.agreementText, this.signatories, this.observers);
}

@Override
public String toString() {
return String.format(
"%s.Contract(%s, %s, %s, %s, %s)",
getCompanion().templateClassName,
this.id,
this.data,
this.agreementText,
this.signatories,
this.observers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.ledger.javaapi.data.codegen;

import com.daml.ledger.javaapi.data.CreatedEvent;
import com.daml.ledger.javaapi.data.DamlRecord;
import com.daml.ledger.javaapi.data.Identifier;
import com.daml.ledger.javaapi.data.Value;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

public abstract class ContractCompanion<Ct, Id, Data> {
public final Identifier TEMPLATE_ID;
final String templateClassName; // not something we want outside this package

protected final Function<String, Id> newContractId;
protected final Function<DamlRecord, Data> fromValue;

public abstract Ct fromCreatedEvent(CreatedEvent event);

protected ContractCompanion(
String templateClassName,
Identifier templateId,
Function<String, Id> newContractId,
Function<DamlRecord, Data> fromValue) {
this.TEMPLATE_ID = templateId;
this.templateClassName = templateClassName;
this.newContractId = newContractId;
this.fromValue = fromValue;
}

public static final class WithoutKey<Ct, Id, Data> extends ContractCompanion<Ct, Id, Data> {
private final NewContract<Ct, Id, Data> newContract;

public WithoutKey(
String templateClassName,
Identifier templateId,
Function<String, Id> newContractId,
Function<DamlRecord, Data> fromValue,
NewContract<Ct, Id, Data> newContract) {
super(templateClassName, templateId, newContractId, fromValue);
this.newContract = newContract;
}

public Ct fromIdAndRecord(
String contractId,
DamlRecord record$,
Optional<String> agreementText,
Set<String> signatories,
Set<String> observers) {
Id id = newContractId.apply(contractId);
Data data = fromValue.apply(record$);
return newContract.newContract(id, data, agreementText, signatories, observers);
}

/**
* @deprecated since introduction; only exists to support generated method that has itself been
* deprecated since v0.12.18
*/
@Deprecated
public Ct fromIdAndRecord(String contractId, DamlRecord record$) {
return fromIdAndRecord(
contractId, record$, Optional.empty(), Collections.emptySet(), Collections.emptySet());
}

@Override
public Ct fromCreatedEvent(CreatedEvent event) {
return fromIdAndRecord(
event.getContractId(),
event.getArguments(),
event.getAgreementText(),
event.getSignatories(),
event.getObservers());
}

@FunctionalInterface
public interface NewContract<Ct, Id, Data> {
Ct newContract(
Id id,
Data data,
Optional<String> agreementText,
Set<String> signatories,
Set<String> observers);
}
}

public static final class WithKey<Ct, Id, Data, Key> extends ContractCompanion<Ct, Id, Data> {
private final NewContract<Ct, Id, Data, Key> newContract;
private final Function<Value, Key> keyFromValue;

public WithKey(
String templateClassName,
Identifier templateId,
Function<String, Id> newContractId,
Function<DamlRecord, Data> fromValue,
NewContract<Ct, Id, Data, Key> newContract,
Function<Value, Key> keyFromValue) {
super(templateClassName, templateId, newContractId, fromValue);
this.newContract = newContract;
this.keyFromValue = keyFromValue;
}

public Ct fromIdAndRecord(
String contractId,
DamlRecord record$,
Optional<String> agreementText,
Optional<Key> key,
Set<String> signatories,
Set<String> observers) {
Id id = newContractId.apply(contractId);
Data data = fromValue.apply(record$);
return newContract.newContract(id, data, agreementText, key, signatories, observers);
}

/**
* @deprecated since introduction; only exists to support generated method that has itself been
* deprecated since v0.12.18
*/
@Deprecated
public Ct fromIdAndRecord(String contractId, DamlRecord record$) {
return fromIdAndRecord(
contractId,
record$,
Optional.empty(),
Optional.empty(),
Collections.emptySet(),
Collections.emptySet());
}

@Override
public Ct fromCreatedEvent(CreatedEvent event) {
return fromIdAndRecord(
event.getContractId(),
event.getArguments(),
event.getAgreementText(),
event.getContractKey().map(keyFromValue),
event.getSignatories(),
event.getObservers());
}

@FunctionalInterface
public interface NewContract<Ct, Id, Data, Key> {
Ct newContract(
Id id,
Data data,
Optional<String> agreementText,
Optional<Key> key,
Set<String> signatories,
Set<String> observers);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.ledger.javaapi.data.codegen;

import java.util.Objects;
import java.util.Optional;
import java.util.Set;

public abstract class ContractWithKey<Id, Data, Key> extends Contract<Id, Data> {
public final Optional<Key> key;

protected ContractWithKey(
Id id,
Data data,
Optional<String> agreementText,
Optional<Key> key,
Set<String> signatories,
Set<String> observers) {
super(id, data, agreementText, signatories, observers);
this.key = key;
}

@Override
public final boolean equals(Object object) {
return object instanceof ContractWithKey
&& super.equals(object)
&& this.key.equals(((ContractWithKey<?, ?, ?>) object).key);
}

@Override
public final int hashCode() {
return Objects.hash(
this.id, this.data, this.agreementText, this.key, this.signatories, this.observers);
}

@Override
public final String toString() {
return String.format(
"%s.Contract(%s, %s, %s, %s, %s, %s)",
getCompanion().templateClassName,
this.id,
this.data,
this.agreementText,
this.key,
this.signatories,
this.observers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,20 @@ void contractHasFromIdAndRecord() {
nonEmptyAgreement.agreementText, Optional.of("I agree"), "Unexpected agreementText");
}

private static final CreatedEvent agreementEvent =
new CreatedEvent(
Collections.emptyList(),
"eventId",
SimpleTemplate.TEMPLATE_ID,
"cid",
simpleTemplateRecord,
Optional.of("I agree"),
Optional.empty(),
Collections.emptySet(),
Collections.emptySet());

@Test
void contractHasFromCreatedEvent() {
CreatedEvent agreementEvent =
new CreatedEvent(
Collections.emptyList(),
"eventId",
SimpleTemplate.TEMPLATE_ID,
"cid",
simpleTemplateRecord,
Optional.of("I agree"),
Optional.empty(),
Collections.emptySet(),
Collections.emptySet());
CreatedEvent noAgreementEvent =
new CreatedEvent(
Collections.emptyList(),
Expand All @@ -123,4 +124,20 @@ void contractHasFromCreatedEvent() {
SimpleTemplate.Contract.fromCreatedEvent(noAgreementEvent);
assertFalse(withoutAgreement.agreementText.isPresent(), "AgreementText was present");
}

@Test
void contractHasCompanion() {
var companion = SimpleTemplate.COMPANION;
SimpleTemplate.Contract withAgreement = companion.fromCreatedEvent(agreementEvent);
SimpleTemplate data = withAgreement.data;
assertEquals(new SimpleTemplate("Bob"), data);
}

@Test
void contractHasToString() {
assertEquals(
"tests.template1.SimpleTemplate.Contract(ContractId(cid), "
+ "tests.template1.SimpleTemplate(Bob), Optional[I agree], [], [])",
SimpleTemplate.Contract.fromCreatedEvent(agreementEvent).toString());
}
}
Loading