Skip to content

Convert RuleGenerator, ReportGenerator and FunctionGenerator to Java and migrate them to the fluent API - #1293

Merged
davidalk merged 9 commits into
finos:mainfrom
REGnosys:xtend-to-java/rule-generator
Aug 27, 2026
Merged

Convert RuleGenerator, ReportGenerator and FunctionGenerator to Java and migrate them to the fluent API#1293
davidalk merged 9 commits into
finos:mainfrom
REGnosys:xtend-to-java/rule-generator

Conversation

@SimonCockx

@SimonCockx SimonCockx commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Continues the Xtend → Java migration of the code generators, moving them onto the fluent CodeRenderer/CodeWriter API. Migrates the report generators (RuleGenerator, ReportGenerator) and the big one, FunctionGenerator.

What changed

  • RuleGenerator, ReportGenerator, FunctionGenerator: converted .xtend.java and moved onto FluentRObjectJavaClassGenerator; generateClass/rBuildClass now return a CodeRenderer.
  • FunctionGenerator is rendered with the fluent CodeWriter API (out.writeln/out.indented/…) instead of Xtend '''…''' templates. Its annotation map and the report annotations are now CodeRenderers.
  • Because FunctionGenerator.rBuildClass returns a CodeRenderer, RuleGenerator/ReportGenerator no longer wrap its result — the call-site bridging is gone.

Bridges

The migration is incremental, so two small bridges — both extracted out of JavaExpression, counterparts of each other — let fluent generators interoperate with code that still produces Xtend StringConcatenationClients:

  • CodeWriterTargetStringConcatenation — a streaming bridge that forwards each appended object to the CodeWriter, so a RecordingCodeWriter registers imports and defers identifier resolution (keeping the scope open). FunctionGenerator uses it to embed the few fragments still generated as templates by not-yet-migrated code (method parameter lists, the nested class declaration, alias call arguments).
  • TargetStringConcatenationCodeWriter — the reverse bridge: it exposes a CodeWriter that appends into the legacy Xtend template machinery, so a value produced by the fluent API can still be embedded in a not-yet-migrated Xtend template (e.g. RGeneratedJavaClass#asClassDeclaration, used by generators that haven't moved to the fluent API yet).

Both are to be removed once all generators use the fluent API.

FluentImportManager (new) is the fluent counterpart of ImportManagerExtension: given a class body as a CodeRenderer, it assembles the full file (package, resolved imports, body). FluentJavaClassGenerator injects it rather than exposing its own file-assembly logic as a method on the generator; FunctionGeneratorHelper, which needs to drive single-class generation directly to assert on one function's output, injects it the same way the not-yet-migrated tests (TypeCoercionTest, ExpressionGeneratorTest) inject ImportManagerExtension.

Output formatting

The fluent CodeWriter defaults to four-space indentation and emits no trailing whitespace, so generated function/rule code is reformatted accordingly (tabs → spaces, no trailing whitespace, conventional Javadoc). The affected expectations are updated; behaviour is unchanged — all generated code still compiles and runs.

Testing

Full multi-module build passes (mvn install), 0 Checkstyle violations. The full rune-integration-tests suite passes (1537 tests, 0 failures), including the byte-comparison generation regression tests and the 147 FunctionGeneratorTest cases (which compile and execute the generated functions).

@netlify

netlify Bot commented Jun 21, 2026

Copy link
Copy Markdown

Deploy Preview for finos-rune canceled.

Name Link
🔨 Latest commit 23027d7
🔍 Latest deploy log https://app.netlify.com/projects/finos-rune/deploys/6a8ff18bf70cd70008c10789

@SimonCockx SimonCockx changed the title Convert RuleGenerator Xtend file to Java Convert RuleGenerator to Java and migrate it to the fluent API Jun 22, 2026
@SimonCockx SimonCockx changed the title Convert RuleGenerator to Java and migrate it to the fluent API Convert RuleGenerator and ReportGenerator to Java and migrate them to the fluent API Jun 22, 2026
@SimonCockx SimonCockx changed the title Convert RuleGenerator and ReportGenerator to Java and migrate them to the fluent API Convert RuleGenerator, ReportGenerator and FunctionGenerator to Java and migrate them to the fluent API Jun 22, 2026
StringConcatenationClient classCode = generator.generateClass(func, typeRepresentation, "test", classScope);
String javaFileCode = importManager.buildClass(typeRepresentation.getPackageName(), classCode, classScope.getFileScope());
CodeRenderer classCode = generator.generateClass(func, typeRepresentation, "test", classScope);
String javaFileCode = generator.buildClass(typeRepresentation.getPackageName(), classCode, classScope.getFileScope());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this pattern changes? E.g., I would have thought this would just use a similar fluent API service.

@davidalk davidalk Aug 26, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added FluentImportManager in 7aa66d4, the fluent counterpart of ImportManagerExtension. FluentJavaClassGenerator no longer declares a buildClass method at all - it now injects FluentImportManager and delegates to it, the same way XtendJavaClassGenerator injects ImportManagerExtension. FluentImportManager#buildClass is public, matching ImportManagerExtension#buildClass.

Why this was needed: buildClass had been made public on FluentJavaClassGenerator purely so FunctionGeneratorHelper could call it - it's declared in package generator.java, while FunctionGeneratorHelper is in generator.java.function, a different package, and not a subclass, so it wasn't reachable via protected either way. FunctionGeneratorHelper now injects FluentImportManager directly instead of reaching through the generator instance.

Move RuleGenerator from the deprecated XtendJavaClassGenerator base class to
FluentRObjectJavaClassGenerator, so generateClass returns a CodeRenderer.

The class body is still produced as an Xtend template by the (not-yet-migrated)
FunctionGenerator, so add a reusable compatibility bridge, LegacyTemplateRenderer,
that adapts a legacy StringConcatenationClient class body into a CodeRenderer.
Layout is produced by Xtend's StringConcatenation (as the legacy
ImportManagerExtension#buildClass does), while embedded JavaClasses and Methods
are resolved against the RecordingCodeWriter so the fluent file builder collects
their imports and resolves their identifiers. Output is byte-identical, letting
generators move onto the fluent API one at a time, before their templates have
been rewritten with the fluent CodeWriter API.
Like RuleGenerator, move ReportGenerator from the deprecated
RObjectJavaClassGenerator base class to FluentRObjectJavaClassGenerator, with
generateClass returning a CodeRenderer. The class body is still produced as an
Xtend template by FunctionGenerator, so wrap it with LegacyTemplateRenderer.

The @RosettaReport and @RuneLabelProvider annotation bodies, previously Xtend
'''...''' templates, are built as StringConcatenationClient instances (the same
idiom already used in AliasUtil); the label-provider class is embedded as an
object so its import is registered.
Convert FunctionGenerator.xtend to Java, rendering the function class with the
fluent CodeWriter API instead of Xtend templates. generateClass and rBuildClass
now return a CodeRenderer.

Because rBuildClass returns a CodeRenderer, RuleGenerator and ReportGenerator no
longer wrap its result, and ReportGenerator's annotation bodies become
CodeRenderers too. The class-body LegacyTemplateRenderer bridge is therefore no
longer needed and is removed; its expression-level counterpart is extracted from
JavaExpression into a reusable CodeWriterTargetStringConcatenation, which
FunctionGenerator uses to embed the few fragments still produced as Xtend
templates by not-yet-migrated code (method parameter lists, the nested class
declaration, alias call arguments). Unlike the Xtend-backed renderer, this
streaming bridge defers identifier resolution, keeping the scope open while the
rest of the class is rendered.

FluentJavaClassGenerator#buildClass is made public so the function test helper
can drive single-class generation.

Generated function and rule code now uses four-space indentation and no trailing
whitespace (the fluent CodeWriter defaults), so the affected expectations are
updated accordingly; behaviour is unchanged (all generated code still compiles
and runs).
Make classBody return a CodeRenderer (like dispatchClassBody and the original
StringConcatenationClient-returning classBody) instead of being a void method
taking a CodeWriter, so rBuildClass returns it directly.

Render the default class declaration inline (class XDefault extends ...) instead
of via RGeneratedJavaClass#asClassDeclaration, which lets defaultClass be typed
as JavaClass<?> and removes one legacy-template-fragment bridge usage.
Change RGeneratedJavaClass#asClassDeclaration to return a dual TargetLanguage
representation that renders via the fluent CodeWriter API and, through a reverse
bridge, still appends to the legacy Xtend template machinery. FunctionGenerator
now renders the default class via asClassDeclaration() again (so defaultClass is
an RGeneratedJavaClass<?>), and the not-yet-migrated generators that embed it in
Xtend templates keep working unchanged.

Extract the reverse bridge TargetStringConcatenationCodeWriter from JavaExpression
into a reusable public class (the counterpart of CodeWriterTargetStringConcatenation).

For consistency with FunctionGenerator#classBody, DeepPathUtilGenerator#renderClass
now returns a CodeRenderer instead of being a void method taking a CodeWriter.
@davidalk
davidalk force-pushed the xtend-to-java/rule-generator branch from 4efcde7 to a068d54 Compare August 26, 2026 14:34
…sion

FluentJavaClassGenerator#buildClass had been made public so
FunctionGeneratorHelper could call it directly to assemble a single
function's class file from its CodeRenderer. That widened an internal
detail of the generator base class purely to serve one external caller,
instead of exposing an independently-usable service the way
ImportManagerExtension always was for the legacy Xtend path.

Extract buildClass into FluentImportManager, injected the same way
XtendJavaClassGenerator injects ImportManagerExtension, and have
FunctionGeneratorHelper depend on it directly instead of reaching
through the generator instance. buildClass reverts to protected.
@davidalk
davidalk merged commit 8c540ee into finos:main Aug 27, 2026
11 checks passed
davidalk added a commit that referenced this pull request Aug 27, 2026
…to Java and migrate them to the fluent API (#1375)

* Convert RuleGenerator Xtend file to Java

* Migrate RuleGenerator to the fluent API via a legacy-template bridge

Move RuleGenerator from the deprecated XtendJavaClassGenerator base class to
FluentRObjectJavaClassGenerator, so generateClass returns a CodeRenderer.

The class body is still produced as an Xtend template by the (not-yet-migrated)
FunctionGenerator, so add a reusable compatibility bridge, LegacyTemplateRenderer,
that adapts a legacy StringConcatenationClient class body into a CodeRenderer.
Layout is produced by Xtend's StringConcatenation (as the legacy
ImportManagerExtension#buildClass does), while embedded JavaClasses and Methods
are resolved against the RecordingCodeWriter so the fluent file builder collects
their imports and resolves their identifiers. Output is byte-identical, letting
generators move onto the fluent API one at a time, before their templates have
been rewritten with the fluent CodeWriter API.

* Use readable declared type for baseInterface

* Convert ReportGenerator to Java and migrate it to the fluent API

Like RuleGenerator, move ReportGenerator from the deprecated
RObjectJavaClassGenerator base class to FluentRObjectJavaClassGenerator, with
generateClass returning a CodeRenderer. The class body is still produced as an
Xtend template by FunctionGenerator, so wrap it with LegacyTemplateRenderer.

The @RosettaReport and @RuneLabelProvider annotation bodies, previously Xtend
'''...''' templates, are built as StringConcatenationClient instances (the same
idiom already used in AliasUtil); the label-provider class is embedded as an
object so its import is registered.

* Convert FunctionGenerator to Java and migrate it to the fluent API

Ported from #1293, with the choice-type implied-key
handling (#1252) omitted: that logic depends on
RosettaDeepFeatureCall.feature being widened from Attribute to
RosettaFeature, an EMF API change that hasn't been backported to
9.x.x. FunctionOperationGeneratorTest.java, which only tests that
logic, is dropped for the same reason.

* Return a CodeRenderer from classBody; narrow defaultClass to JavaClass

Make classBody return a CodeRenderer (like dispatchClassBody and the original
StringConcatenationClient-returning classBody) instead of being a void method
taking a CodeWriter, so rBuildClass returns it directly.

Render the default class declaration inline (class XDefault extends ...) instead
of via RGeneratedJavaClass#asClassDeclaration, which lets defaultClass be typed
as JavaClass<?> and removes one legacy-template-fragment bridge usage.

* Make asClassDeclaration fluent; align DeepPathUtilGenerator

Change RGeneratedJavaClass#asClassDeclaration to return a dual TargetLanguage
representation that renders via the fluent CodeWriter API and, through a reverse
bridge, still appends to the legacy Xtend template machinery. FunctionGenerator
now renders the default class via asClassDeclaration() again (so defaultClass is
an RGeneratedJavaClass<?>), and the not-yet-migrated generators that embed it in
Xtend templates keep working unchanged.

Extract the reverse bridge TargetStringConcatenationCodeWriter from JavaExpression
into a reusable public class (the counterpart of CodeWriterTargetStringConcatenation).

For consistency with FunctionGenerator#classBody, DeepPathUtilGenerator#renderClass
now returns a CodeRenderer instead of being a void method taking a CodeWriter.

* Add FluentImportManager, the fluent counterpart of ImportManagerExtension

FluentJavaClassGenerator#buildClass had been made public so
FunctionGeneratorHelper could call it directly to assemble a single
function's class file from its CodeRenderer. That widened an internal
detail of the generator base class purely to serve one external caller,
instead of exposing an independently-usable service the way
ImportManagerExtension always was for the legacy Xtend path.

Extract buildClass into FluentImportManager, injected the same way
XtendJavaClassGenerator injects ImportManagerExtension, and have
FunctionGeneratorHelper depend on it directly instead of reaching
through the generator instance. buildClass reverts to protected.

* Update CalculationFunctionGeneratorTest expectations for the new FunctionGenerator output format

FunctionGenerator's fluent-API migration (previous commit) changed the
generator's whitespace conventions (4-space indent, no blank line after
an opening brace, condensed Javadoc). This still-Xtend test file
(never migrated on 9.x.x, unlike its main-branch counterpart
FunctionOperationGeneratorTest) asserts against hardcoded expected
Java source, so it needed its expectations regenerated to match.

No generator or production logic changed; only the literal expected
strings were regenerated from a passing test run's actual output.

---------

Co-authored-by: Simon Cockx <47859223+SimonCockx@users.noreply.github.com>
@davidalk
davidalk deleted the xtend-to-java/rule-generator branch August 27, 2026 13:49
davidalk added a commit to REGnosys/rune-dsl that referenced this pull request Aug 27, 2026
Migrated generators had been dropped from the "Remaining Xtend files"
checklist entirely rather than ticked off (FunctionGenerator,
ReportGenerator and RuleGenerator in finos#1293, CardinalityValidatorGenerator
in this PR). Bring all four back with [x], matching how every other
completed item in the plan is tracked.
davidalk added a commit that referenced this pull request Aug 28, 2026
…erator and TypeFormatValidatorGenerator to Java and migrate them to the fluent API (#1377)

* Convert CardinalityValidatorGenerator to Java and migrate it to the fluent API

Move CardinalityValidatorGenerator off the deprecated Xtend template
machinery onto FluentRObjectJavaClassGenerator, rendering the validator
class with the fluent CodeRenderer/CodeWriter API. AbstractValidatorGenerator
is left untouched since OnlyExistsValidatorGenerator and
TypeFormatValidatorGenerator still depend on its StringConcatenationClient
contract; the small streamObjects/createTypeRepresentation logic is
duplicated here instead.

Also correct the migrate-xtend-generator skill's pitfalls section, which
still described CodeWriterTargetStringConcatenation and
TargetStringConcatenationCodeWriter as living inside JavaExpression --
they were extracted into standalone classes under generator/java/util/
in #1293.

* Use static imports for ValidationResult/checkCardinality calls

Resolve the handful of static method calls the generator writes
(isNullOrEmpty, checkCardinality, ValidationResult.success/failure,
Collectors.toList) as java.lang.reflect.Method objects instead of
Class + literal method name, so RecordingCodeWriter registers static
imports for them instead of regular ones. This keeps the regression
fixture diffs to whitespace only against the pre-migration Xtend
output, matching how the generator previously rendered these calls.

* Convert OnlyExistsValidatorGenerator to Java and migrate it to the fluent API

* Convert TypeFormatValidatorGenerator to Java and migrate it to the fluent API

AbstractValidatorGenerator is deleted as this was its last subclass.
Regenerated *TypeFormatValidator.java expectation files reflect only the
fluent writer's whitespace conventions (4-space indent, no trailing
whitespace), verified with `git diff --ignore-all-space`.
davidalk added a commit that referenced this pull request Aug 28, 2026
…nd TypeFormatValidatorGenerator to Java and migrate them to the fluent API (#1376)

* Convert CardinalityValidatorGenerator to Java and migrate it to the fluent API

Move CardinalityValidatorGenerator off the deprecated Xtend template
machinery onto FluentRObjectJavaClassGenerator, rendering the validator
class with the fluent CodeRenderer/CodeWriter API. AbstractValidatorGenerator
is left untouched since OnlyExistsValidatorGenerator and
TypeFormatValidatorGenerator still depend on its StringConcatenationClient
contract; the small streamObjects/createTypeRepresentation logic is
duplicated here instead.

Also correct the migrate-xtend-generator skill's pitfalls section, which
still described CodeWriterTargetStringConcatenation and
TargetStringConcatenationCodeWriter as living inside JavaExpression --
they were extracted into standalone classes under generator/java/util/
in #1293.

* Use static imports for ValidationResult/checkCardinality calls

Resolve the handful of static method calls the generator writes
(isNullOrEmpty, checkCardinality, ValidationResult.success/failure,
Collectors.toList) as java.lang.reflect.Method objects instead of
Class + literal method name, so RecordingCodeWriter registers static
imports for them instead of regular ones. This keeps the regression
fixture diffs to whitespace only against the pre-migration Xtend
output, matching how the generator previously rendered these calls.

* Restore checked-off rows to the xtend migration plan

Migrated generators had been dropped from the "Remaining Xtend files"
checklist entirely rather than ticked off (FunctionGenerator,
ReportGenerator and RuleGenerator in #1293, CardinalityValidatorGenerator
in this PR). Bring all four back with [x], matching how every other
completed item in the plan is tracked.

* Convert OnlyExistsValidatorGenerator to Java and migrate it to the fluent API

* Convert TypeFormatValidatorGenerator to Java and migrate it to the fluent API

AbstractValidatorGenerator is deleted as this was its last subclass.
Regenerated *TypeFormatValidator.java expectation files reflect only the
fluent writer's whitespace conventions (4-space indent, no trailing
whitespace), verified with `git diff --ignore-all-space`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants