Typed ACS and transaction streams for Java codegen (#15159)

* added transactionFilter

* move transactionFilter to `TransactionFilter`

* fromCreatedEvent

* format

* added getTransaction with contract type companion

* ACS using contract type companion

* position

* add change log

CHANGELOG_BEGIN
CHANGELOG_END

* Ct

* Ct

* format

* better exception

* fixed test cases

* added java doc and rename method to GetContracts

* address Stephen's comments

* address Stephen's comments

* address Stephen's comments

* remove unused import

* format

* address Stephen's comments

* address Stephen's comments

* Make some codegen code to be java 8 compatible; Modify IouMain to use the new getActiveContracts. to address Stephen's comments

* use 1.11, no need to be java 8 compatible

* revert

* 11 not 1.11
This commit is contained in:
Chun Lok Ling 2022-10-12 21:01:01 +01:00 committed by GitHub
parent a49dd9ed8c
commit 74dd242984
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 434 additions and 57 deletions

View File

@ -9,8 +9,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<daml-codegen-java.output>${project.build.directory}/generated-sources/iou</daml-codegen-java.output>
<ledgerhost>localhost</ledgerhost>
<ledgerport>6865</ledgerport>

View File

@ -4,6 +4,7 @@
package com.daml.quickstart.iou;
import com.daml.ledger.javaapi.data.*;
import com.daml.ledger.rxjava.ContractUtil;
import com.daml.ledger.rxjava.DamlLedgerClient;
import com.daml.ledger.rxjava.LedgerClient;
import com.daml.quickstart.model.iou.Iou;
@ -59,14 +60,14 @@ public class IouMain {
client
.getActiveContractSetClient()
.getActiveContracts(iouFilter, true)
.getActiveContracts(ContractUtil.of(Iou.COMPANION), Collections.singleton(party), true)
.blockingForEach(
response -> {
response
activeContracts -> {
activeContracts
.getOffset()
.ifPresent(offset -> acsOffset.set(new LedgerOffset.Absolute(offset)));
response.getCreatedEvents().stream()
.map(Iou.Contract::fromCreatedEvent)
activeContracts
.getContracts()
.forEach(
contract -> {
long id = idCounter.getAndIncrement();

View File

@ -3,9 +3,11 @@
package com.daml.ledger.rxjava;
import com.daml.ledger.javaapi.data.ActiveContracts;
import com.daml.ledger.javaapi.data.GetActiveContractsResponse;
import com.daml.ledger.javaapi.data.TransactionFilter;
import io.reactivex.Flowable;
import java.util.Set;
/** An RxJava version of {@link com.daml.ledger.api.v1.ActiveContractsServiceGrpc} */
public interface ActiveContractsClient {
@ -15,4 +17,31 @@ public interface ActiveContractsClient {
Flowable<GetActiveContractsResponse> getActiveContracts(
TransactionFilter filter, boolean verbose, String accessToken);
/**
* Get active Contracts
*
* @param contractUtil Utilities for specified type of contract. It can be instantiated with
* <code>ContractTypeCompanion</code>
* @param parties Set of parties to be included in the transaction filter.
* @param verbose If enabled, values served over the API will contain more information than
* strictly necessary to interpret the data.
* @return Flowable of active contracts of type <code>Ct</code>
*/
<Ct> Flowable<ActiveContracts<Ct>> getActiveContracts(
ContractUtil<Ct> contractUtil, Set<String> parties, boolean verbose);
/**
* Get active Contracts
*
* @param contractUtil Utilities for specified type of contract. It can be instantiated with
* <code>ContractTypeCompanion</code>
* @param parties Set of parties to be included in the transaction filter.
* @param verbose If enabled, values served over the API will contain more information than
* strictly necessary to interpret the data.
* @param accessToken Access token for authentication.
* @return Active contracts of type <code>Ct</code>
*/
<Ct> Flowable<ActiveContracts<Ct>> getActiveContracts(
ContractUtil<Ct> contractUtil, Set<String> parties, boolean verbose, String accessToken);
}

View File

@ -0,0 +1,64 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.rxjava;
import com.daml.ledger.javaapi.data.*;
import com.daml.ledger.javaapi.data.codegen.Contract;
import com.daml.ledger.javaapi.data.codegen.ContractCompanion;
import com.daml.ledger.javaapi.data.codegen.InterfaceCompanion;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* This class contains utilities to decode a <code>CreatedEvent</code> and create a <code>
* TransactionFilter</code> by provider parties It can only be instantiated with a subtype of <code>
* ContractCompanion</code>
*/
public final class ContractUtil<Ct> {
private final FromCreatedEventFunc<CreatedEvent, Ct> fromCreatedEvent;
private final Filter filter;
private ContractUtil(FromCreatedEventFunc<CreatedEvent, Ct> fromCreatedEvent, Filter filter) {
this.fromCreatedEvent = fromCreatedEvent;
this.filter = filter;
}
public static <Ct> ContractUtil<Ct> of(ContractCompanion<Ct, ?, ?> companion) {
Filter filter =
new InclusiveFilter(Collections.singleton(companion.TEMPLATE_ID), Collections.emptyMap());
return new ContractUtil<>(companion::fromCreatedEvent, filter);
}
public static <Cid, View> ContractUtil<Contract<Cid, View>> of(
InterfaceCompanion<?, Cid, View> companion) {
Filter filter =
new InclusiveFilter(
Collections.emptySet(),
Collections.singletonMap(companion.TEMPLATE_ID, Filter.Interface.INCLUDE_VIEW));
return new ContractUtil<>(companion::fromCreatedEvent, filter);
}
public Ct toContract(CreatedEvent createdEvent) throws IllegalArgumentException {
return fromCreatedEvent.apply(createdEvent);
}
public TransactionFilter transactionFilter(Set<String> parties) {
return transactionFilter(filter, parties);
}
private static TransactionFilter transactionFilter(Filter filter, Set<String> parties) {
Map<String, Filter> partyToFilters =
parties.stream().collect(Collectors.toMap(Function.identity(), x -> filter));
return new FiltersByParty(partyToFilters);
}
@FunctionalInterface
private interface FromCreatedEventFunc<T, R> {
R apply(T t) throws IllegalArgumentException;
}
}

View File

@ -3,10 +3,7 @@
package com.daml.ledger.rxjava;
import com.daml.ledger.javaapi.data.LedgerOffset;
import com.daml.ledger.javaapi.data.Transaction;
import com.daml.ledger.javaapi.data.TransactionFilter;
import com.daml.ledger.javaapi.data.TransactionTree;
import com.daml.ledger.javaapi.data.*;
import io.reactivex.Flowable;
import io.reactivex.Single;
import java.util.Set;
@ -30,6 +27,39 @@ public interface TransactionsClient {
Flowable<Transaction> getTransactions(
LedgerOffset begin, TransactionFilter filter, boolean verbose, String accessToken);
/**
* Get contracts
*
* @param contractUtil Utilities for specified type of contract. It can be instantiated with
* <code>ContractTypeCompanion</code>
* @param begin begin offset.
* @param parties Set of parties to be included in the transaction filter.
* @param verbose If enabled, values served over the API will contain more information than
* strictly necessary to interpret the data.
* @return Flowable of contract type <code>Ct</code>
*/
<Ct> Flowable<Ct> getContracts(
ContractUtil<Ct> contractUtil, LedgerOffset begin, Set<String> parties, boolean verbose);
/**
* Get contracts
*
* @param contractUtil Utilities for specified type of contract. It can be instantiated with
* <code>ContractTypeCompanion</code>
* @param begin begin offset.
* @param parties Set of parties to be included in the transaction filter.
* @param verbose If enabled, values served over the API will contain more information than
* strictly necessary to interpret the data.
* @param accessToken Access token for authentication.
* @return Flowable of contract type <code>Ct</code>
*/
<Ct> Flowable<Ct> getContracts(
ContractUtil<Ct> contractUtil,
LedgerOffset begin,
Set<String> parties,
boolean verbose,
String accessToken);
Flowable<TransactionTree> getTransactionsTrees(
LedgerOffset begin, LedgerOffset end, TransactionFilter filter, boolean verbose);

View File

@ -6,15 +6,17 @@ package com.daml.ledger.rxjava.grpc;
import com.daml.grpc.adapter.ExecutionSequencerFactory;
import com.daml.ledger.api.v1.ActiveContractsServiceGrpc;
import com.daml.ledger.api.v1.ActiveContractsServiceOuterClass;
import com.daml.ledger.javaapi.data.GetActiveContractsRequest;
import com.daml.ledger.javaapi.data.GetActiveContractsResponse;
import com.daml.ledger.javaapi.data.TransactionFilter;
import com.daml.ledger.javaapi.data.*;
import com.daml.ledger.rxjava.ActiveContractsClient;
import com.daml.ledger.rxjava.ContractUtil;
import com.daml.ledger.rxjava.grpc.helpers.StubHelper;
import com.daml.ledger.rxjava.util.ClientPublisherFlowable;
import io.grpc.Channel;
import io.reactivex.Flowable;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.NonNull;
public class ActiveContractClientImpl implements ActiveContractsClient {
@ -56,4 +58,36 @@ public class ActiveContractClientImpl implements ActiveContractsClient {
@NonNull TransactionFilter filter, boolean verbose, @NonNull String accessToken) {
return getActiveContracts(filter, verbose, Optional.of(accessToken));
}
private <Ct> Flowable<ActiveContracts<Ct>> getActiveContracts(
ContractUtil<Ct> contractUtil,
Set<String> parties,
boolean verbose,
Optional<String> accessToken) {
TransactionFilter filter = contractUtil.transactionFilter(parties);
Flowable<GetActiveContractsResponse> responses =
getActiveContracts(filter, verbose, accessToken);
return responses.map(
response -> {
List<Ct> activeContracts =
response.getCreatedEvents().stream()
.map(contractUtil::toContract)
.collect(Collectors.toList());
return new ActiveContracts<>(
response.getOffset().orElse(""), activeContracts, response.getWorkflowId());
});
}
@Override
public <Ct> Flowable<ActiveContracts<Ct>> getActiveContracts(
ContractUtil<Ct> contractUtil, Set<String> parties, boolean verbose) {
return getActiveContracts(contractUtil, parties, verbose, Optional.empty());
}
@Override
public <Ct> Flowable<ActiveContracts<Ct>> getActiveContracts(
ContractUtil<Ct> contractUtil, Set<String> parties, boolean verbose, String accessToken) {
return getActiveContracts(contractUtil, parties, verbose, Optional.of(accessToken));
}
}

View File

@ -7,6 +7,7 @@ import com.daml.grpc.adapter.ExecutionSequencerFactory;
import com.daml.ledger.api.v1.TransactionServiceGrpc;
import com.daml.ledger.api.v1.TransactionServiceOuterClass;
import com.daml.ledger.javaapi.data.*;
import com.daml.ledger.rxjava.ContractUtil;
import com.daml.ledger.rxjava.TransactionsClient;
import com.daml.ledger.rxjava.grpc.helpers.StubHelper;
import com.daml.ledger.rxjava.util.ClientPublisherFlowable;
@ -16,6 +17,7 @@ import io.reactivex.Single;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
public final class TransactionClientImpl implements TransactionsClient {
private final String ledgerId;
@ -73,6 +75,40 @@ public final class TransactionClientImpl implements TransactionsClient {
return getTransactions(begin, end, filter, verbose, Optional.of(accessToken));
}
private <Ct> Flowable<Ct> getContracts(
ContractUtil<Ct> contractUtil,
LedgerOffset begin,
Set<String> parties,
boolean verbose,
Optional<String> accessToken) {
TransactionFilter filter = contractUtil.transactionFilter(parties);
Flowable<Transaction> transactions = getTransactions(begin, filter, verbose, accessToken);
Flowable<CreatedEvent> createdEvents =
transactions.concatMapIterable(
tx ->
tx.getEvents().stream()
.filter(e -> e instanceof CreatedEvent)
.map(e -> (CreatedEvent) e)
.collect(Collectors.toList()));
return createdEvents.map(contractUtil::toContract);
}
@Override
public <Ct> Flowable<Ct> getContracts(
ContractUtil<Ct> contractUtil, LedgerOffset begin, Set<String> parties, boolean verbose) {
return getContracts(contractUtil, begin, parties, verbose, Optional.empty());
}
@Override
public <Ct> Flowable<Ct> getContracts(
ContractUtil<Ct> contractUtil,
LedgerOffset begin,
Set<String> parties,
boolean verbose,
String accessToken) {
return getContracts(contractUtil, begin, parties, verbose, Optional.of(accessToken));
}
private Flowable<Transaction> getTransactions(
LedgerOffset begin, TransactionFilter filter, boolean verbose, Optional<String> accessToken) {
TransactionServiceOuterClass.GetTransactionsRequest request =

View File

@ -0,0 +1,69 @@
// 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;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.NonNull;
public final class ActiveContracts<Ct> implements WorkflowEvent {
private final String offset;
private final List<Ct> activeContracts;
private final String workflowId;
public ActiveContracts(
@NonNull String offset, @NonNull List<Ct> activeContracts, String workflowId) {
this.offset = offset;
this.activeContracts = activeContracts;
this.workflowId = workflowId;
}
@NonNull
public Optional<String> getOffset() {
// Empty string indicates that the field is not present in the protobuf.
return Optional.of(offset).filter(off -> !offset.equals(""));
}
@NonNull
public List<@NonNull Ct> getContracts() {
return activeContracts;
}
@NonNull
public String getWorkflowId() {
return workflowId;
}
@Override
public String toString() {
return "ActiveContracts{"
+ "offset='"
+ offset
+ '\''
+ ", activeContracts="
+ activeContracts
+ ", workflowId="
+ workflowId
+ '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ActiveContracts<?> that = (ActiveContracts<?>) o;
return Objects.equals(offset, that.offset)
&& Objects.equals(activeContracts, that.activeContracts)
&& Objects.equals(workflowId, that.workflowId);
}
@Override
public int hashCode() {
return Objects.hash(offset, activeContracts, workflowId);
}
}

View File

@ -4,7 +4,13 @@
package com.daml.ledger.javaapi.data;
import com.daml.ledger.api.v1.TransactionFilterOuterClass;
import com.daml.ledger.javaapi.data.codegen.ContractCompanion;
import com.daml.ledger.javaapi.data.codegen.ContractTypeCompanion;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public abstract class TransactionFilter {
@ -17,4 +23,17 @@ public abstract class TransactionFilter {
abstract TransactionFilterOuterClass.TransactionFilter toProto();
public abstract Set<String> getParties();
public static TransactionFilter transactionFilter(
ContractTypeCompanion<?, ?> contractCompanion, Set<String> parties) {
Filter filter =
(contractCompanion instanceof ContractCompanion)
? new InclusiveFilter(Set.of(contractCompanion.TEMPLATE_ID), Collections.emptyMap())
: new InclusiveFilter(
Collections.emptySet(),
Map.of(contractCompanion.TEMPLATE_ID, Filter.Interface.INCLUDE_VIEW));
Map<String, Filter> partyToFilters =
parties.stream().collect(Collectors.toMap(Function.identity(), x -> filter));
return new FiltersByParty(partyToFilters);
}
}

View File

@ -49,7 +49,7 @@ public abstract class Contract<Id, Data> implements com.daml.ledger.javaapi.data
}
// concrete 1st type param would need a self-reference type param in Contract
protected abstract ContractCompanion<? extends Contract<Id, Data>, Id, Data> getCompanion();
protected abstract ContractTypeCompanion<?, Data> getCompanion();
@Override
public boolean equals(Object object) {
@ -81,7 +81,7 @@ public abstract class Contract<Id, Data> implements com.daml.ledger.javaapi.data
public String toString() {
return String.format(
"%s.Contract(%s, %s, %s, %s, %s)",
getCompanion().templateClassName,
getCompanion().TEMPLATE_CLASS_NAME,
this.id,
this.data,
this.agreementText,

View File

@ -26,8 +26,6 @@ import java.util.function.Function;
* the template, whose instances contain only the payload.
*/
public abstract class ContractCompanion<Ct, Id, Data> extends ContractTypeCompanion<Data, Data> {
final String templateClassName; // not something we want outside this package
protected final Function<String, Id> newContractId;
protected final Function<DamlRecord, Data> fromValue;
@ -84,8 +82,7 @@ public abstract class ContractCompanion<Ct, Id, Data> extends ContractTypeCompan
Function<String, Id> newContractId,
Function<DamlRecord, Data> fromValue,
List<ChoiceMetadata<Data, ?, ?>> choices) {
super(templateId, choices);
this.templateClassName = templateClassName;
super(templateId, templateClassName, choices);
this.newContractId = newContractId;
this.fromValue = fromValue;
}

View File

@ -21,6 +21,8 @@ public abstract class ContractTypeCompanion<ContractType, Data> {
/** The full template ID of the template or interface that defined this companion. */
public final Identifier TEMPLATE_ID;
final String TEMPLATE_CLASS_NAME;
/**
* The provides a mapping of choice name to Choice.
*
@ -40,8 +42,11 @@ public abstract class ContractTypeCompanion<ContractType, Data> {
* interface in question instead.
*/
protected ContractTypeCompanion(
Identifier templateId, List<ChoiceMetadata<ContractType, ?, ?>> choices) {
Identifier templateId,
String templateClassName,
List<ChoiceMetadata<ContractType, ?, ?>> choices) {
TEMPLATE_ID = templateId;
TEMPLATE_CLASS_NAME = templateClassName;
this.choices =
choices.stream().collect(Collectors.toMap(choice -> choice.name, Function.identity()));
}

View File

@ -0,0 +1,33 @@
// 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.Optional;
import java.util.Set;
final class ContractWithInterfaceView<Id, View> extends Contract<Id, View> {
private final ContractTypeCompanion<?, View> contractTypeCompanion;
ContractWithInterfaceView(
ContractTypeCompanion<?, View> contractTypeCompanion,
Id id,
View interfaceView,
Optional<String> agreementText,
Set<String> signatories,
Set<String> observers) {
super(id, interfaceView, agreementText, signatories, observers);
this.contractTypeCompanion = contractTypeCompanion;
}
@Override
protected ContractTypeCompanion<?, View> getCompanion() {
return contractTypeCompanion;
}
@Override
public boolean equals(Object object) {
return object instanceof ContractWithInterfaceView && super.equals(object);
}
}

View File

@ -52,7 +52,7 @@ public abstract class ContractWithKey<Id, Data, Key> extends Contract<Id, Data>
public final String toString() {
return String.format(
"%s.Contract(%s, %s, %s, %s, %s, %s)",
getCompanion().templateClassName,
getCompanion().TEMPLATE_CLASS_NAME,
this.id,
this.data,
this.agreementText,

View File

@ -3,8 +3,14 @@
package com.daml.ledger.javaapi.data.codegen;
import com.daml.ledger.javaapi.data.*;
import com.daml.ledger.javaapi.data.DamlRecord;
import com.daml.ledger.javaapi.data.Identifier;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
/**
* Metadata and utilities associated with an interface as a whole. Its subclasses serve to
@ -14,7 +20,9 @@ import java.util.List;
* @param <View> The {@link DamlRecord} subclass representing the interface view, as may be
* retrieved from the ACS or transaction stream.
*/
public abstract class InterfaceCompanion<I, View> extends ContractTypeCompanion<I, View> {
public abstract class InterfaceCompanion<I, Id, View> extends ContractTypeCompanion<I, View> {
private final Function<String, Id> newContractId;
public final ValueDecoder<View> valueDecoder;
@ -25,10 +33,54 @@ public abstract class InterfaceCompanion<I, View> extends ContractTypeCompanion<
* INTERFACE} field on generated code for Daml interfaces instead.
*/
protected InterfaceCompanion(
String templateClassName,
Identifier templateId,
Function<String, Id> newContractId,
ValueDecoder<View> valueDecoder,
List<ChoiceMetadata<I, ?, ?>> choices) {
super(templateId, choices);
super(templateId, templateClassName, choices);
this.newContractId = newContractId;
this.valueDecoder = valueDecoder;
}
private Contract<Id, View> fromIdAndRecord(
String contractId,
Map<Identifier, DamlRecord> interfaceViews,
Optional<String> agreementText,
Set<String> signatories,
Set<String> observers)
throws IllegalArgumentException {
Optional<DamlRecord> maybeRecord = Optional.ofNullable(interfaceViews.get(TEMPLATE_ID));
Optional<DamlRecord> maybeFailedRecord = Optional.ofNullable(interfaceViews.get(TEMPLATE_ID));
Id id = newContractId.apply(contractId);
return maybeRecord
.map(
record -> {
View view = valueDecoder.decode(record);
return new ContractWithInterfaceView<>(
this, id, view, agreementText, signatories, observers);
})
.orElseThrow(
() ->
maybeFailedRecord
.map(
record ->
new IllegalArgumentException(
"Failed interface view for " + TEMPLATE_ID))
.orElseThrow(
() ->
new IllegalArgumentException(
"interface view of " + TEMPLATE_ID + " not found.")));
}
public final Contract<Id, View> fromCreatedEvent(CreatedEvent event)
throws IllegalArgumentException {
return fromIdAndRecord(
event.getContractId(),
event.getInterfaceViews(),
event.getAgreementText(),
event.getSignatories(),
event.getObservers());
}
}

View File

@ -108,12 +108,10 @@ object ContractClass {
private[this] val contractIdClassName = ClassName bestGuess "ContractId"
private[this] def generateGetCompanion(templateClassName: ClassName): MethodSpec = {
val contractClassName = ClassName bestGuess "Contract"
ClassGenUtils.generateGetCompanion(
ParameterizedTypeName.get(
ClassName get classOf[javaapi.data.codegen.ContractCompanion[_, _, _]],
contractClassName,
contractIdClassName,
ClassName get classOf[javaapi.data.codegen.ContractTypeCompanion[_, _]],
templateClassName,
templateClassName,
),
companionFieldName,

View File

@ -106,35 +106,45 @@ object InterfaceClass extends StrictLogging {
interfaceName: ClassName,
choiceNames: Set[ChoiceName],
interfaceViewTypeName: ClassName,
): TypeSpec = TypeSpec
.classBuilder(companionClassName)
.superclass(
ParameterizedTypeName
.get(ClassName get classOf[InterfaceCompanion[_, _]], interfaceName, interfaceViewTypeName)
)
.addModifiers(Modifier.FINAL, Modifier.PUBLIC, Modifier.STATIC)
.addMethod {
MethodSpec
.constructorBuilder()
// intentionally package-private
.addStatement(
"super($T.$N, $T.$L(), $T.of($L))",
interfaceName,
ClassGenUtils.templateIdFieldName,
interfaceViewTypeName,
"valueDecoder",
classOf[java.util.List[_]],
CodeBlock
.join(
choiceNames
.map(choiceName => CodeBlock.of("$N", toChoiceNameField(choiceName)))
.asJava,
",$W",
),
)
.build()
}
.build()
): TypeSpec = {
val contractIdClassName = ClassName bestGuess "ContractId"
TypeSpec
.classBuilder(companionClassName)
.superclass(
ParameterizedTypeName
.get(
ClassName get classOf[InterfaceCompanion[_, _, _]],
interfaceName,
contractIdClassName,
interfaceViewTypeName,
)
)
.addModifiers(Modifier.FINAL, Modifier.PUBLIC, Modifier.STATIC)
.addMethod {
MethodSpec
.constructorBuilder()
// intentionally package-private
.addStatement(
"super($>$Z$S, $T.$N, $T::new, $T.$L(), $T.of($L))$<$Z",
interfaceName,
interfaceName,
ClassGenUtils.templateIdFieldName,
contractIdClassName,
interfaceViewTypeName,
"valueDecoder",
classOf[java.util.List[_]],
CodeBlock
.join(
choiceNames
.map(choiceName => CodeBlock.of("$N", toChoiceNameField(choiceName)))
.asJava,
",$W",
),
)
.build()
}
.build()
}
private def generateTemplateIdField(packageId: PackageId, name: QualifiedName): FieldSpec =
ClassGenUtils.generateTemplateIdField(