refactor: migrate Xtend to Java - xtext.expression/export/scope jvmmodel (final batch) - #1536
Open
joaodinissf wants to merge 7 commits into
Open
refactor: migrate Xtend to Java - xtext.expression/export/scope jvmmodel (final batch)#1536joaodinissf wants to merge 7 commits into
joaodinissf wants to merge 7 commits into
Conversation
…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>
rubenporras
approved these changes
Sep 11, 2026
joaodinissf
added this pull request to stack #1538
September 11, 2026 12:18
joaodinissf
marked this pull request as ready for review
September 11, 2026 12:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migrates the last ten Xtend sources in the repository — the Xbase-based
jvmmodellayer ofcom.avaloq.tools.ddk.xtext.expression,com.avaloq.tools.ddk.xtext.exportandcom.avaloq.tools.ddk.xtext.scope— to Java 21. After this PR no.xtendfile 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 mvrename (keepsgit log --follow/blameconnected to the Xtend history; intentionally does not compile) followed by the in-place translation. A finalbuild:commit bumpsxtext.exportandxtext.scopeto 17.3.5 for the Tycho baseline gate (xtext.expressionwas already ahead of the 19.2.0 baseline).ExpressionJvmModelInferrerExportExpressionCompiler,ExportExpressionTranslator,ExportJvmModelInferrer,ExportTranslationContextScopeExpressionCompiler,ScopeExpressionMethodRequest,ScopeExpressionTranslator,ScopeJvmModelInferrer,ScopeTranslationContextPublic and protected signatures are unchanged in all ten classes (the Guice bindings in
src-gen/Abstract*RuntimeModuleand the callers inxtext.generator.testcompile untouched).Faithfulness notes
OperationCall/TypeSelectExpressionextend bothExpressionandFeatureCall).Voidcases and the terminalIllegalArgumentExceptionare kept.+=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 scopeInjection— keepsoperator_add's null-skip through an explicit guard.newLineIfNotEmptywhitespace retraction or two-argument indented appends stay onStringConcatenation(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 inStrings.emptyIfNull.?./?:chains became explicit null checks preserving short-circuit order; Xtend type-guardswitches becameinstanceofchains in source order..toString()onString-typed values (PMD), long boolean guards split into guard clauses with unchanged operand order (Checkstyle), private helpers extracted where CPD orMultipleStringLiteralsfired,@paramtags completed on existing Javadoc (CheckstyleJavadocMethod).Verification
xtend-genbuilt fromd3b6e083a.instanceoforder identical.Sample.scopeandSample.exportcompiled through the reference bundles and through the migrated bundles produce byte-identical generated Java (eight files, 15,441 bytes, zero diff).-amd); PMD, CPD, Checkstyle, SpotBugs clean; fullmvn 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