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