Skip to content

refactor: migrate Xtend to Java - xtext.expression/export/scope jvmmodel (final batch) - #1536

Open
joaodinissf wants to merge 7 commits into
masterfrom
migrate/xtend-to-java/jvmmodel-step-1
Open

refactor: migrate Xtend to Java - xtext.expression/export/scope jvmmodel (final batch)#1536
joaodinissf wants to merge 7 commits into
masterfrom
migrate/xtend-to-java/jvmmodel-step-1

Conversation

@joaodinissf

Copy link
Copy Markdown
Collaborator

Migrates the last ten Xtend sources in the repository — the Xbase-based jvmmodel layer of com.avaloq.tools.ddk.xtext.expression, com.avaloq.tools.ddk.xtext.export and com.avaloq.tools.ddk.xtext.scope — to Java 21. After this PR no .xtend file remains; the Xtend build infrastructure is removed in a follow-up PR so this one stays a pure source migration.

What changed

Per module, two commits: a pure git mv rename (keeps git log --follow/blame connected to the Xtend history; intentionally does not compile) followed by the in-place translation. A final build: commit bumps xtext.export and xtext.scope to 17.3.5 for the Tycho baseline gate (xtext.expression was already ahead of the 19.2.0 baseline).

Module Files Notes
expression ExpressionJvmModelInferrer single-case dispatch no-op inferrer
export ExportExpressionCompiler, ExportExpressionTranslator, ExportJvmModelInferrer, ExportTranslationContext 14 dispatch families; 15 inferrer templates
scope ScopeExpressionCompiler, ScopeExpressionMethodRequest, ScopeExpressionTranslator, ScopeJvmModelInferrer, ScopeTranslationContext 13 dispatch families; 3 inferrer templates

Public and protected signatures are unchanged in all ten classes (the Guice bindings in src-gen/Abstract*RuntimeModule and the callers in xtext.generator.test compile untouched).

Faithfulness notes

  • Dispatch order is taken from the Xtend compiler output, not source order (Xtend sorts cases by type specificity; OperationCall/TypeSelectExpression extend both Expression and FeatureCall). Void cases and the terminal IllegalArgumentException are kept.
  • Inferrer += sites (85 across the two inferrers): every producer was checked for null. JvmTypeReferenceBuilder.typeRef(Class) is provably non-null (it returns an unknown-type reference on a miss), to* builders only return null for a null source element or name. The one null-capable site — the field for a nameless scope Injection — keeps operator_add's null-skip through an explicit guard.
  • Templates: the control-flow-bearing inferrer templates whose output depends on newLineIfNotEmpty whitespace retraction or two-argument indented appends stay on StringConcatenation (byte-identical append sequences); the rest became literals, text blocks or .formatted(). StringConcatenation.append(null) appends nothing, so interpolations of a possibly-null model name are wrapped in Strings.emptyIfNull.
  • ?./?: chains became explicit null checks preserving short-circuit order; Xtend type-guard switches became instanceof chains in source order.
  • Behaviour-neutral deviations: dropped .toString() on String-typed values (PMD), long boolean guards split into guard clauses with unchanged operand order (Checkstyle), private helpers extracted where CPD or MultipleStringLiterals fired, @param tags completed on existing Javadoc (Checkstyle JavadocMethod).

Verification

  • Ground truth: fresh xtend-gen built from d3b6e083a.
  • Two independent translations per module, reconciled against that reference: public/protected API identical (javap), string-literal sequences identical, dispatcher instanceof order identical.
  • Golden output: Sample.scope and Sample.export compiled through the reference bundles and through the migrated bundles produce byte-identical generated Java (eight files, 15,441 bytes, zero diff).
  • Gates: dependants compile (-amd); PMD, CPD, Checkstyle, SpotBugs clean; full mvn clean verify … as in CI: BUILD SUCCESS, 730 tests, 0 failures, 0 errors, 4 skipped; the baseline gate ran and accepted both bumps.

🤖 Generated with Claude Code

joaodinissf and others added 7 commits September 11, 2026 08:41
…on (1/2: rename sources)

Pure `git mv` of ExpressionJvmModelInferrer.xtend to .java with content
unchanged, so Git's rename detection permanently links the .java history to
its .xtend past (`git log --follow`, `git blame`).

This commit intentionally does not compile: the renamed file still holds Xtend
syntax. The translation follows in commit 2/2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…on (2/2: translate to Java 21)

In-place translation of the renamed source. Ground truth is the freshly built
xtend-gen output for base commit d3b6e08 (verified byte-identical to the
supplied reference before any edit).

ExpressionJvmModelInferrer

- Dispatch: `def dispatch void infer(Expression, IJvmDeclaredTypeAcceptor,
  boolean)` becomes `protected void _infer(Expression, ...)` plus the public
  `infer(EObject, ...)` dispatcher. Case order follows the xtend-gen
  dispatcher exactly: (1) `element instanceof Expression`, (2) `element !=
  null` delegating to the inherited `AbstractModelInferrer._infer(EObject,
  ...)`, (3) the terminal `IllegalArgumentException("Unhandled parameter
  types: " + Arrays.<Object>asList(element, acceptor, isPreIndexingPhase))`.
  The dispatcher carries `@Override` (the base declares `public void
  infer(EObject, IJvmDeclaredTypeAcceptor, boolean)`), matching
  FormatJvmModelInferrer; xtend-gen instead marks it `@XbaseGenerated`, which
  is an Xtend-compiler marker with no place in hand-maintained Java.
- Public API parity: `_infer` keeps its `protected` visibility, parameter
  types/order and `void` return; the dispatcher keeps `public`/`void` and the
  `(EObject, IJvmDeclaredTypeAcceptor, boolean)` signature the
  `bindIJvmModelInferrer` binding in AbstractExpressionRuntimeModule resolves
  against. No other Java caller references this class.
- `+=` on a JVM-model EList: none. The inferrer body is empty, so rule 36 /
  rules/10 section 10.4 has no site to apply to.
- `?.` / `?:` / Xtend `switch`: none. The only null test is the dispatcher's
  `element != null`, carried over verbatim from xtend-gen.
- Templates: none. No `'''...'''`, no `body =`, no `documentation =`, so no
  StringConcatenation chain is retained anywhere.
- The empty `_infer` body keeps the original `// Here you explain how your
  model is mapped to Java elements, ...` comment, both to preserve the source
  comment and to satisfy PMD UncommentedEmptyMethodBody.
- Suppressions are the sanctioned set only: `"nls"` (module inherits
  nonExternalizedStringLiteral=warning) for the exception message, plus
  `"checkstyle:MethodName"` and `"PMD.UnusedFormalParameter"` for the `_infer`
  dispatch case, combined in one class-level annotation as in
  FormatJvmModelInferrer. No CONSTANTS-OFF/ON or LambdaBodyLength brackets are
  needed: there are no repeated literals and no lambdas.
- The `/* generated by Xtext */` stub header is replaced by the Avaloq banner
  per the skill's copyright rule; class and member Javadoc are carried over
  verbatim.

Deviation from the skill: the module still lists `xtend-gen/` in
build.properties and .classpath. Removing that infrastructure is skill rule 28
/ workflow/infrastructure-cleanup.md, but it is out of scope for this change
set, so it is deliberately left for a follow-up.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…1/2: rename sources)

Pure `git mv` of the four jvmmodel `.xtend` sources to `.java`; file contents
are unchanged. This commit intentionally does not compile - the files still
hold Xtend syntax. The rename edge has 100% content similarity so `git log
--follow` and `git blame` keep the `.java` history connected to its Xtend past.
The in-place translation follows in commit 2/2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…2/2: translate to Java 21)

In-place translation of the four jvmmodel sources renamed in 1/2. The freshly
built xtend-gen output of the base commit is the behavioural ground truth; the
public API (names, parameter types and order, return types, generics) is
identical to it.

ExportTranslationContext
- Plain state holder; newLinkedHashMap/newArrayList become LinkedHashMap/
  ArrayList behind the List/Map interfaces.
- resolveDslType keeps the short-circuit of `typeResolver === null || type ===
  null` as an early `return null`.
- Function1 stays in setTypeResolver: the translator hands it a lambda and the
  signature is part of the public API.

ExportExpressionCompiler
- Dispatch preserved as `_javaExpression`/`_isSimpleFeatureCall`/
  `_isSimpleNavigation`/`_requiresBracketing` (both arities)/
  `_isArithmeticOperatorCall`/`_isPrefixExpression`/`_isInfixExpression`/
  `_isThisCall`/`_isThis`/`_javaEncode` plus dispatchers whose instanceof chains
  keep the exact case order of the generated dispatchers (Xtend orders by type
  specificity, not source order), including the Void overloads and the terminal
  IllegalArgumentException.
- `ctx.modelTypeResolver?.resolve(...)` becomes an explicit null check that keeps
  the `||` short-circuit against resolveDslType.
- `==` on String operands becomes Objects.equals; `===`/`!==` become `==`/`!=`.
- The `switch name` over numeric type names becomes a Java 21 switch expression
  behind an explicit `name == null` guard, reproducing Xtend's null case.
- `toSet`/`head`/`join`/`toFirstUpper` replaced by JDK equivalents; the accessor
  name computation shared by the two identical feature-call branches is factored
  into featureAccessorName.

ExportExpressionTranslator
- doTranslate dispatch preserved with the generated case order; the `?:` chains
  in _doTranslate(OperationCall) and resolveType become explicit null checks with
  the same evaluation order and precedence.
- The Xtend `switch expression` type guards become an instanceof chain in source
  order with the same fall-through and default.
- `?.` chains (parameterType?.type, returnType?.type) become the typeOf/
  returnTypeOf/simpleNameOf helpers, null-safe in the same places.
- findFirst/filter(JvmOperation) lookups become loops over
  Iterables.filter(..., JvmOperation.class) returning the first match, i.e. the
  same short-circuit as IterableExtensions.findFirst.
- Xbase EList `+=` sites (XListLiteral.elements, featureCallArguments,
  memberCallArguments) are plain add: JvmTypesBuilder is NOT an extension in
  this class, so the Xtend `+=` already compiled to EList.add (see xtend-gen).
- Pair stays in canExtractAsValue/newCompilationContext: public API parity.

ExportJvmModelInferrer
- `infer` dispatcher with @OverRide plus `_infer(ExportModel, ...)`; doProfile
  dispatch keeps the generated case order (InterfaceExpression, InterfaceField,
  InterfaceNavigation, InterfaceItem).
- Inferrer `+=` (rule 36 / rules/10 §10.4): every site uses a plain EList.add
  because its producer is provably non-null. For the seven
  `superTypes += typeRef(X)` sites the proof is bytecode: `javap -c` on
  JvmTypeReferenceBuilder.typeRef(Class, JvmTypeReference...) in
  org.eclipse.xtext.xbase-2.44.0 shows findDeclaredType -> ifnonnull ->
  createUnknownTypeReference(name), with areturn on both paths and no
  aconst_null anywhere in the class, so an unresolvable type yields an unknown
  type reference rather than null (FormatJvmModelInferrer adds the same way).
  The remaining sites (members/annotations/parameters/explicitValues/values) add
  the result of toClass/toField/toMethod/toParameter/createJvm*/a string literal
  with a non-null source element and a non-null name, which §10.4 proves
  non-null.
- Templates: the three single-line ones (the two `documentation` templates, the
  INTERESTING_EXTS initializer and the computeSelectorFragmentSegment body)
  become literals/.formatted(). The three _doProfile templates become idiomatic
  Java: every newLineIfNotEmpty() in them follows a static non-whitespace
  literal tail (", FingerprintOrder.UNORDERED, hasher);" / ", hasher);" /
  ", hasher);"), so rule 35 permits an unconditional newline - InterfaceField
  and InterfaceNavigation become a StringBuilder(512) whose `if` selects only
  the tail literal, InterfaceExpression becomes a text block with .formatted().
  The nine remaining control-flow templates keep the reference
  StringConcatenation chains verbatim - §4.8 blocks folding because they combine
  newLineIfNotEmpty after dynamic values, two-arg append(value, indent) of
  possibly multi-line values and, in exportedEClassesInitializer, an
  appendImmediate separator loop. Every append literal, indent argument and
  newLine/newLineIfNotEmpty call of those nine was machine-diffed against
  xtend-gen: zero differences. The three converted ones were checked with an
  executable old-vs-new harness over an input battery (empty, single-line,
  LF-bearing, newline-terminated, %-bearing, padded values x both branches):
  byte-identical for every value the producers can emit. literalIdentifier
  yields a Java qualified name, and javaExpr routes string literals through
  Strings.convertToJavaString, which escapes CR/LF - so the one input class that
  would diverge (a raw CRLF inside an interpolated value, which
  StringConcatenation folds to LF) cannot reach these three templates.
- toSet becomes Collectors.toCollection(LinkedHashSet::new): the set built in
  strategySwitchInitializer is rendered into the generated comment, so encounter
  order is observable. sortBy becomes a stable sorted(Comparator.comparing(...)).
- The Runnable/Function0 pair of renderBody become a Supplier plus an
  AtomicReference, replacing the one-element ArrayList the Xtend closure used to
  publish its result.

Deviations from a literal transliteration, all output-identical:
- Integer.valueOf(int).toString() -> Integer.toString(int) and String.toString()
  dropped (rule 4, PMD StringToString).
- Unreachable `else throw` of the isInfixExpression dispatcher dropped: the
  preceding `it != null` / `it == null` branches are exhaustive.
- Conditions split into guard clauses / named booleans to satisfy Checkstyle
  BooleanExpressionComplexity, preserving operand order and short-circuiting.
- "." extracted to SEGMENT_SEPARATOR (Checkstyle MultipleStringLiterals).
- @PARAM context / @throws tags added where Checkstyle JavadocMethod requires
  them; no class or member documentation was invented beyond those tags.

Gates: `compile` with -amd and `compile pmd:check pmd:cpd pmd:cpd-check
checkstyle:check spotbugs:check` both BUILD SUCCESS.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…/2: rename sources)

Pure `git mv` of the five `.xtend` sources under
com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/
to `.java`. File contents are unchanged, so Git's rename detection keeps
`git log --follow` and `git blame` connected to the Xtend history, and the
follow-up translate commit shows an in-place, side-by-side diff per file.

This commit intentionally does not compile: the renamed files still hold
Xtend syntax. The next commit translates them to Java 21.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…/2: translate to Java 21)

Translates the five renamed jvmmodel sources in place. Public and protected
API (names, parameter types and order, Xtend-inferred return types, generics)
matches the xtend-gen ground truth built from the base commit exactly; the
dispatch families keep the `_name(Type)` cases, the `Void` overloads, the
xtend-gen case order and the identical
`IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object>asList(...))`
message. Dispatchers use the Java 21 pattern-switch shape already established
by the migrated generators in this module.

ScopeJvmModelInferrer
  * `+=` on JVM-model ELists: every site is a plain `add` because its producer
    is provably non-null - `typeRef(Class)` never returns null, `toField`/
    `toMethod`/`toParameter` guard only their source element and name and both
    are literals here, and `typeOnlyAnnotation` always returns a fresh
    reference. The one exception is the injected field, whose name is
    `injection.getName()` and can be unset: that add keeps
    `JvmTypesBuilder.operator_add`'s null skip behind an explicit guard.
  * The two `documentation = '''...'''` templates become `.formatted()` over
    `Strings.emptyIfNull(element.getName())`, which reproduces
    StringConcatenation.append(Object)'s null skip byte for byte; the LOGGER
    initializer template becomes `.formatted()` over a provably non-null
    simple name. No template needed StringConcatenation.
  * The four `doGetScope`/`doGlobalCache` entry points share one private
    `scopeEntryPoint` initializer factory: they differ only in the second
    parameter type and the body producer, and the per-method JvmTypesBuilder
    call sequence is unchanged.

ScopeExpressionCompiler
  * 16-case `javaExpression` family plus the `isSimpleFeatureCall`,
    `isSimpleNavigation`, `requiresBracketing` (1- and 2-argument),
    `isArithmeticOperatorCall`, `isPrefixExpression`, `isInfixExpression`,
    `isThisCall`, `isThis` and `javaEncode` dispatchers, all in xtend-gen order.
  * The non-dispatch `isVariableRef(Expression)`/`isVariableRef(FeatureCall)`
    pair stays a statically bound overload pair, as in xtend-gen.
  * Long boolean conditions become guard clauses or named locals with the
    operand order and short-circuiting preserved, rather than a
    BooleanExpressionComplexity suppression.
  * The accessor-name ternary that the Xtend source repeats in two branches of
    `_javaExpression(FeatureCall)` is extracted to a private `accessorName`.

ScopeExpressionTranslator
  * `?:` elvis chains in `_doTranslate(OperationCall)` and `resolveType`, and
    the `?.` chains on parameter/return type references, become explicit null
    checks with the original short-circuit order; `contextModel`'s `?:` becomes
    an early return.
  * The `resolveType` Xtend type-guard switch becomes an `instanceof` chain in
    source order (TypeSelectExpression and OperationCall before FeatureCall).
  * `IterableExtensions.filter(..., JvmOperation)` lookups become loops over
    Guava `Iterables.filter(..., JvmOperation.class)`, preserving laziness and
    first-match semantics; `map(...).filterNull.head` and `findFirst` become
    the equivalent lazy stream pipelines.
  * `EList` adds here are plain `add`: JvmTypesBuilder is not in scope in this
    class, so the Xtend `+=` bound to the ordinary list operator, and every
    added value is null-checked immediately before.

ScopeTranslationContext, ScopeExpressionMethodRequest
  * Straight field/accessor translation. `org.eclipse.xtext.xbase.lib.Pair` and
    `Functions.Function1` are kept because they are part of the public
    signatures the migrated generators in this module already call.

Documented deviations from the xtend-gen reference, all behaviour preserving:
  * `_javaExpression(RealLiteral)` returns `getVal()` instead of
    `getVal().toString()` (PMD StringToString); differs only for a null value,
    where xtend-gen would throw.
  * Javadoc that the Xtend source carried is preserved verbatim except for
    `@param` tags added for parameters Checkstyle JavadocMethod requires
    (`allowMissingParamTags=false`).
  * Private helpers with no xtend-gen counterpart were introduced to keep the
    gates green without unsanctioned suppressions: `scopeEntryPoint` (CPD),
    `qualifiedName` (MultipleStringLiterals), and `accessorName`,
    `javaExpressions`, `toFirstUpper`, `isInstanceOfTypeCheck`,
    `matchesExtension`, `typeOf`, `returnTypeOf`, `isSingleSegmentFeature`,
    `resolveOperationCallType`, `resolveFeatureCallType`,
    `isIterablesFilterByClass` (readability, all behaviour-identical).

Gates: compile with -amd, and pmd:check/pmd:cpd-check/checkstyle:check/
spotbugs:check both BUILD SUCCESS.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s 19.2.0 baseline)

The jvmmodel migration changes the content of both bundles at a version
equal to the 19.2.0 baseline, which the Tycho compare-version-with-baselines
gate rejects. xtext.expression is already at 17.3.4 against a 17.3.3
baseline and needs no bump; the features are already at 19.2.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

2 participants