Skip to content
Discussions/App Development/Design of `exerciseArchive` in Java bindingsForum ↗

Design of `exerciseArchive` in Java bindings

App Development6 posts286 viewsLast activity Jul 2023
TA
Tamas_KalczaOP
Jun 2023

I wanted to create a generic code that given a contract ID from the generated codegen code returns the corresponding archive command. During this I have noticed that it seems to be extremely difficult (even considering using reflection).

Can someone explain some the design decisions behind the archival in the Java bindings? I have the following questions in particular:

  • Why is the exerciseArchive not defined on a generic/super interface, e.g. ContractId?
  • Why does the exerciseArchive method have a parameter?
  • Why is that parameter in the generated code and not in some generic code?
ST
Stephen
Jun 2023

The past several releases of Java bindings/codegen have seen numerous abstractions introduced to allow more kinds of abstract utilities to be written against them, chiefly ContractTypeCompanion, ValueDecoder, and Update, which among other things enable typed exercise and typed ACS/tx stream decoding. With even functions like this having been generated inline instead of handwritten and called, there are a lot of potential further developments in this area.

Tamas_Kalcza:

Why does the exerciseArchive method have a parameter?

The rule that lets Java codegen generate flattened overloads for choice argument record types doesn’t work on Archive. It’s definitely because it’s a strange argument, but I’m not exactly sure why.

Tamas_Kalcza:

I wanted to create a generic code that given a contract ID from the generated codegen code returns the corresponding archive command.

Given a javaapi.data.codegen.ContractId<?>, I would take an approach that avoids exerciseArchive entirely, just relying on the generated Archive class.

// this is constant; you can stash it somewhere
// it can be copied from any codegenned CHOICE_Archive
Choice<Object, Archive, Unit> choiceArchive = Choice.create(
    "Archive", value -> value.toValue(),
    Archive.valueDecoder(),
    PrimitiveValueDecoders.fromUnit);

// exactly what exerciseArchive does, but supplying the argument
var update = cid.makeExerciseCmd(choiceArchive, new Archive());

// update is the same as what exerciseArchive returns
TA
Tamas_Kalcza
Jun 2023

Those are great additions and I use them regularly.

Are you saying there is technical reason (imposed by some Java codegen library) that prevents pulling exerciseArchive up to com.daml.ledger.javaapi.data.codegen.ContractId and/or remove the argument? Maybe my mind tricks me, however I remember there used to be a variant of this method without the argument. If that was possible I think it should not be difficult to pull up the method.

Thanks for the code snippet for generic archive approach. The issue here is really that Archive is generated. So that code only works if such a snippet lives in a codebase that has access to the generated code. I wanted to create code that relies only the bindings itself.

I solved my problem using reflection, however it is not very nice.

static <Data extends Template, Id extends ContractId<Data>> Update<?> archive(Id contractId) {
  try {
    var exerciseMethods = contractId.getClass().getInterfaces()[0].getDeclaredMethods();
    var exerciseArchive = Arrays
      .stream(exerciseMethods)
      .filter(x -> Objects.equals("exerciseArchive", x.getName()))
      .findFirst().get();
    var archiveType = exerciseArchive.getParameterTypes()[0];
    var archiveArgument = archiveType.getDeclaredConstructor().newInstance();
    return (Update<?>) exerciseArchive.invoke(contractId, archiveArgument);
  } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
    throw new RuntimeException(e);
  }
}
ST
Stephen
Jun 2023
Tamas_Kalcza:

Thanks for the code snippet for generic archive approach. The issue here is really that Archive is generated. So that code only works if such a snippet lives in a codebase that has access to the generated code.

That can be worked around by the fact that Choice doesn’t require that its typed forms (every type parameter) are codegenned or “canonical” in any way. The code snippet I wrote can be adapted as follows to avoid codegen:

class MyArchive {}

Choice<Object, MyArchive, Unit> choiceArchive = Choice.create(
    "Archive", value -> new /*data.*/DamlRecord(),
    v -> new MyArchive(),
    PrimitiveValueDecoders.fromUnit);

You could add stuff to confirm that v is a record, but because you don’t need any data, ignoring it is fine here.

ST
Stephen
Jul 2023

You may wish to :bell: Subscribe to this relevant PR:

github.com/digital-asset/daml

generic `exerciseArchive` for Java codegen

digital-asset:maindigital-asset:s11/15540-generic-exerciseArchive
opened 08:15PM - 11 Jul 23 UTC

Fixes #15540 with two related features: 1. This was skipped before by the fla

ST
Stephen
Jul 2023

The above should be included in Daml 2.7.0.

← Back to Discussions