Skip to main content

Rune and Java

1. Types and Enums in Rune

When you compile a Rune model, each type becomes a Java class that implements RosettaModelObject. These generated classes follow a predictable structure.

1.1. Structure of generated model classes

Key characteristics:

  • Immutable objects Once created, the object cannot be modified.
  • Builder pattern Each class has a nested Builder implementing RosettaModelObjectBuilder. Builders are the only way to construct instances.
  • Attributes → Getters Every field in the Rune model becomes a getter in Java.
  • Metadata support Classes may include metadata fields for additional context.
  • Utility methods Common generated methods include:
  • toString() – debugging
  • prune() – remove empty nested objects
  • process(...) – visitor pattern for walking the object graph

Example: Generated Java Interface For a Rune model like this:

type Vehicle:
registrationID string (1..1)
vehicleClassification VehicleClassificationEnum (1..1)

The generated Java interface would include:

@RuneDataType(value="Vehicle", builder=Vehicle.VehicleBuilderImpl.class, version="x.y.z")
public interface Vehicle extends RosettaModelObject {
// Getters
String getRegistrationID();
VehicleClassificationEnum getVehicleClassification();

// Create a new builder instance
public static VehicleBuilder builder() { ... }

// Visitor pattern to traverse the object graph
void process(RosettaPath path, Processor processor);

// Builder interface
interface VehicleBuilder extends Vehicle, RosettaModelObjectBuilder {
// Setter methods
VehicleBuilder setRegistrationID(String registrationID);
VehicleBuilder setVehicleClassification(VehicleClassificationEnum vehicleClassification);

// Build method
Vehicle build();

// Pruning method
VehicleBuilder prune();

// Other methods
boolean hasData();
}
}

1.2. Pruning

Pruning removes empty nested objects from a builder. This is useful for:

  • reducing serialized size
  • simplifying comparisons
  • avoiding storing empty structures

How pruning works

An attribute is considered empty if:

  • it’s null, or
  • it’s an empty list, or
  • it’s optional and all its fields are empty Required fields are never considered empty.

Pruning steps

  1. Recursively call prune() on all nested RosettaModelObjects
  2. Remove null items from lists
  3. If an optional object has no data after pruning → set it to null

Example A simple object graph before pruning:

Vehicle vehicle = Vehicle.builder()
.setRegistrationID("ABC123")
.setOwner(Person.builder()
.setAddress(Address.builder().build()) // Empty address
.build())
.build();

After pruning:

Vehicle.VehicleBuilder builder = vehicle.toBuilder();
builder.prune();
Vehicle prunedVehicle = builder.build();

// The empty Address object has been pruned (set to null)
assert prunedVehicle.getOwner().getAddress() == null;

1.3. Label providers

When a type carries a [label ...] annotation on one of its own, inherited, or overridden attributes, its generated pojo interface carries a @RuneLabelProvider annotation pointing at a generated LabelProvider class rooted at that type:

@RuneDataType(value="EuropeanParliamentReport", builder=EuropeanParliamentReport.EuropeanParliamentReportBuilderImpl.class, version="x.y.z")
@RuneLabelProvider(labelProvider=EuropeanParliamentReportLabelProvider.class)
public interface EuropeanParliamentReport extends RosettaModelObject {
// Getters, builder, process(...)as in any other generated interface
}

The provider class is generated at <namespace>.labels.types.<Type>LabelProvider, for any type in the model with at least one direct label on its own, inherited or overridden attributes — not only for report and function output types. This exists primarily for users of the serialiser outside of a transform function — for example, working with a flat CSVCSV (Comma-Separated Values). Comma-Separated Values. Simple file format used to store tabular data (like spreadsheets or databases) in plain text.-shaped type directly — so the gate is narrow by design: a type that merely contains a labelled descendant, with no label of its own, gets no provider.

A transform function or report also generates its own label provider, rooted at the function's output type (see Reports and rules). This is a separate, permanent provider, not a fallback for the type-rooted one: an output type whose labels are only on nested descendants never gets a type-rooted provider under the gate above, in any DSLDSL (Domain-Specific Language). A programming language for a narrow purpose e.g. for financial regulatory reporting. version, so the function/report provider is the only one that ever exists for that shape. Where a transform's output type does carry a direct label of its own, both providers are generated with identical content — prefer either.

2. Functions

Coming soon.

3. Validation

Coming soon.

4. Reports and rules

Reports and rules behave like functions. They implement the ReportFunction<IN, OUT> interface, which extends from RosettaFunction, and additionally exposes an evaluate method that takes in a single parameter of type IN (the input of a report, e.g., a ReportableEvent) and has a result of type OUT (the report output, e.g. a CFTCPart43TransactionReport).

Example: European emission report A simple report definition

report EuropeanParliament EmissionPerformanceStandardsEU in real-time
from VehicleOwnership
when IsEuroStandardsCoverage
with type EuropeanParliamentReport

// Definition for regulatory references:
body Authority EuropeanParliament
corpus Regulation "Regulation (EU) 2019/631" EmissionPerformanceStandardsEU

This report takes an input of type VehicleOwnership, and returns an instance of type EuropeanParliamentReport, defined as:

type VehicleOwnership:
drivingLicence DrivingLicence (1..1)
vehicle Vehicle (1..1)

type EuropeanParliamentReport:
vehicleRegistrationID string (1..1)
[label "Vehicle Registration ID"]
[ruleReference VehicleRegistrationID]
vehicleClassificationType VehicleClassificationEnum (1..1)
[label "Vehicle Classification Type"]
[ruleReference VehicleClassificationType]

type Vehicle:
registrationID string (1..1)
vehicleClassification VehicleClassificationEnum (1..1)

enum VehicleClassificationEnum:
M1_Passengers
M2_Passengers
M3_Passengers
N1I_Commercial
...

type Person:
name string (1..1)

type DrivingLicence:
owner Person (1..1)
countryofIssuance string (1..1)
dateofIssuance date (1..1)
dateOfRenewal date (0..1)
vehicleEntitlement VehicleClassificationEnum (0..*)

The report is supported by these rules:

eligibility rule IsEuroStandardsCoverage from VehicleOwnership:
filter
vehicle -> vehicleClassification = VehicleClassificationEnum -> M1_Passengers
or vehicle -> vehicleClassification = VehicleClassificationEnum -> M2_Passengers
or vehicle -> vehicleClassification = VehicleClassificationEnum -> M3_Passengers
or vehicle -> vehicleClassification = VehicleClassificationEnum -> N1I_Commercial
or ...

reporting rule VehicleRegistrationID from VehicleOwnership:
extract vehicle -> registrationID

reporting rule VehicleClassificationType from VehicleOwnership: <"Classification type of the vehicle">
extract vehicle -> vehicleClassification

Generated Java code for the report

The report becomes an abstract Java class. This @RuneLabelProvider is the report's own label provider — see Label providers — generated independently of (and, where EuropeanParliamentReport itself carries direct labels, alongside) the type-rooted provider:

@RosettaReport(namespace="test.reg", body="EuropeanParliament", corpusList={"EmissionPerformanceStandardsEU"})
@RuneLabelProvider(labelProvider=EuropeanParliamentEmissionPerformanceStandardsEULabelProvider.class)
@ImplementedBy(EuropeanParliamentEmissionPerformanceStandardsEUReportFunction.EuropeanParliamentEmissionPerformanceStandardsEUReportFunctionDefault.class)
public abstract class EuropeanParliamentEmissionPerformanceStandardsEUReportFunction implements ReportFunction<VehicleOwnership, EuropeanParliamentReport> {
@Override
public EuropeanParliamentReport evaluate(VehicleOwnership input) {
EuropeanParliamentReport.EuropeanParliamentReportBuilder outputBuilder = doEvaluate(input);

... // build the output and perform validation

return output;
}

protected abstract EuropeanParliamentReport.EuropeanParliamentReportBuilder doEvaluate(VehicleOwnership input);

public static class EuropeanParliamentEmissionPerformanceStandardsEUReportFunctionDefault extends EuropeanParliamentEmissionPerformanceStandardsEUReportFunction {
@Override
protected EuropeanParliamentReport.EuropeanParliamentReportBuilder doEvaluate(VehicleOwnership input) { ... }
}
}

Dependency injection

Guice is used to separate specification (EuropeanParliamentEmissionPerformanceStandardsEUReportFunction) from implementation (EuropeanParliamentEmissionPerformanceStandardsEUReportFunctionDefault). The default implementation will delegate to the VehicleRegistrationID and VehicleClassificationType reporting rules, as specified in the Rune model.

Running a report

To run the report, we first need to inject a report function instance using any conventional method delivered by Guice.

Example

@Inject
private EuropeanParliamentEmissionPerformanceStandardsEUReportFunction reportFunction;

@Test
private void testReportFunction() {
VehicleOwnership input = ... // create or read a vehicle ownership instance
EuropeanParliamentReport reportOutput = reportFunction.evaluate(input);

assertEquals(input.getVehicle().getRegistrationID(), reportOutput.getVehicleRegistrationID());
}