Design of `exerciseArchive` in Java bindings

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?

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.

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.

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

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);
  }
}

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.

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

The above should be included in Daml 2.7.0.