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()– debuggingprune()– remove empty nested objectsprocess(...)– 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
- Recursively call
prune()on all nestedRosettaModelObjects - Remove null items from lists
- 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());
}