Skip to content

refactor: migrate Xtend to Java - com.avaloq.tools.ddk.check.core - #1452

Merged
joaodinissf merged 8 commits into
dsldevkit:masterfrom
joaodinissf:migrate/xtend-to-java/check-core
Aug 14, 2026
Merged

refactor: migrate Xtend to Java - com.avaloq.tools.ddk.check.core#1452
joaodinissf merged 8 commits into
dsldevkit:masterfrom
joaodinissf:migrate/xtend-to-java/check-core

Conversation

@joaodinissf

@joaodinissf joaodinissf commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

What

Migrates the remaining 8 .xtend files of com.avaloq.tools.ddk.check.core to Java 21 (generator, generator-extensions, generator-naming, JVM model inferrer, formatter, scope provider, type computer, generator-config), and drops the module's Xtend build infrastructure. Commit structure: pure git mv rename → in-place translate → infrastructure cleanup → separate style pass (Java 21 pattern matching in the hand-written dispatchers, kept out of the faithful translation commit for clean revertability).

How it was validated

Two independent lines of evidence, both against the authoritative ground truth — a freshly built xtend-gen/ (what the Xtend compiler itself produced from these sources, built off current master):

  • Blind re-migration + three-way reconciliation: a second migration was produced independently from the .xtend sources without sight of this branch, then reconciled file-by-file against this branch with xtend-gen as arbiter. The two migrations converged on identical semantics at every contested site (dispatcher contracts, filterNull null-skip preservation, issue-code sort order, template whitespace, sentinel returns).
  • Adversarial per-file verification: each file was line-verified against xtend-gen (every one-arg vs two-arg append, newLineIfNotEmpty occurrence, null-handling quirk, iteration order, and exception surface), with each candidate finding subjected to a refutation pass. Two real defects were found and fixed on this branch: fabricated Javadoc in the generator files (now restored to the originals' exact comment state) and a malformed .classpath from the infra cleanup (now xmllint-valid).

Deliberate, output-neutral divergences from xtend-gen, kept and documented: explicit UTF-8 in getContents (PMD RelianceOnDefaultCharset), checked-exception wrapping in IllegalStateException, and the CPD-OFF marker around the faithful toMethod duplication in CheckJvmModelInferrer (master's stricter pmd.cpd.min=100).

Verification

Full local gate from ddk-parent (verify checkstyle:check pmd:check spotbugs:check, check.core + test bundle + upstream deps): BUILD SUCCESS, test suite green, all static analysis clean. CI green on the pushed stack.

🤖 Generated with Claude Code

@joaodinissf
joaodinissf force-pushed the migrate/xtend-to-java/check-core branch 2 times, most recently from 0f48915 to 72b635e Compare July 6, 2026 11:37
@joaodinissf
joaodinissf force-pushed the migrate/xtend-to-java/check-core branch from 72b635e to 2fa945b Compare July 19, 2026 11:17
Pure git mv of the 8 remaining .xtend sources to .java, content
unchanged, so the rename edge has 100% similarity and git log --follow
and git blame permanently traverse the migration boundary. This
intermediate commit intentionally does not compile; the translation
follows in the next commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joaodinissf
joaodinissf force-pushed the migrate/xtend-to-java/check-core branch from 2fa945b to f43aba0 Compare August 2, 2026 05:36
joaodinissf and others added 3 commits August 2, 2026 07:51
In-place translation of the 8 renamed sources, faithful to the Xtend
compiler's own xtend-gen output (fresh ground-truth build). The three
byte-critical emitters (CheckGeneratorExtensions, CheckGenerator,
CheckJvmModelInferrer) keep the exact StringConcatenation call
sequences incl. two-arg append(value, indent) and newLineIfNotEmpty.

Notable per-file points:
- CheckJvmModelInferrer: Xtend's JvmTypesBuilder '+=' silently skips
  null elements; the plain Iterables.addAll translation let null
  JvmMembers reach the type resolver (IllegalArgumentException:
  element: null, reproduced by the CheckValidationTest severity-range
  stubs). The two affected call sites are wrapped in
  IterableExtensions.filterNull like every sibling site.
- CheckGeneratorExtensions.getContents: two deliberate output-neutral
  hardenings (explicit UTF-8; IllegalStateException wrapping of checked
  exceptions), documented in the PR.
- splitCamelCase uses .formatted() per repo convention
  (behavior-identical to the String.format xtend-gen emitted).
- Comments preserve the originals' exact state: block comments stay
  block comments, and no Javadoc is invented (checkstyle only validates
  Javadoc that exists; it does not require any).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The module is fully off Xtend: remove the xtend-gen source folder from
build.properties and .classpath, the xtextBuilder/xtextNature from
.project, and the xtend-gen directory marker - matching the other fully
migrated modules. No Require-Bundle change needed:
org.eclipse.xtext.xbase.lib stays as a direct dependency because the
migrated sources still use IterableExtensions/ListExtensions/
StringExtensions and StringConcatenation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Post-verification style pass, kept separate from the faithful translation
commit for clean revertability:
- adopt Java 21 pattern matching in the hand-written dispatchers
  (CheckTypeComputer, CheckScopeProvider, CheckGeneratorExtensions,
  CheckFormatter — 62 branches)
- CheckFormatter: rename the generic dispatcher parameter xlistliteral to
  element, collapse the tail's impossible final else (provably unreachable:
  requires x == null and x != null both false), drop the dead Arrays import,
  restore final on loop variables
- CheckGenerator: fix misspelled local formattedCateogryDescription
- CheckGeneratorExtensions: build the splitCamelCase regex from named
  compile-time String constants folded by the compiler instead of a runtime
  .formatted() call

Behavior is identical by construction; verified against the fresh xtend-gen
ground truth as part of the migration verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joaodinissf
joaodinissf force-pushed the migrate/xtend-to-java/check-core branch from f43aba0 to 74444ba Compare August 2, 2026 05:52
@joaodinissf
joaodinissf marked this pull request as ready for review August 2, 2026 05:52
@joaodinissf
joaodinissf requested a review from rubenporras August 2, 2026 05:52
}

@Override
@XbaseGenerated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should remove @XbaseGenerated

@Override
@XbaseGenerated
public void format(final Object element, final IFormattableDocument document) {
if (element instanceof JvmTypeParameter jvmTypeParameter) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can do better for this number of elements and be more concise. What about

switch (element) {
case JvmTypeParameter j -> _format(j, document);
case JvmFormalParameter p -> _format(p, document);
case XtextResource r -> _format(r, document);
case XAssignment a -> _format(a, document);
case XBinaryOperation b -> _format(b, document);
case XDoWhileExpression dw -> _format(dw, document);
case XFeatureCall f -> _format(f, document);
case XListLiteral ll -> _format(ll, document);
case XMemberFeatureCall m -> _format(m, document);
case XPostfixOperation pf -> _format(pf, document);
case XUnaryOperation u -> _format(u, document);
case XWhileExpression w -> _format(w, document);
case XFunctionTypeRef ft -> _format(ft, document);
case Category c -> _format(c, document);
case Check ch -> _format(ch, document);
case CheckCatalog cc -> _format(cc, document);
case Context ctx -> _format(ctx, document);
case Implementation impl -> _format(impl, document);
case Member mem -> _format(mem, document);
case XGuardExpression ge -> _format(ge, document);
case XIssueExpression ie -> _format(ie, document);
case JvmGenericArrayTypeReference gar -> _format(gar, document);
case JvmParameterizedTypeReference ptr -> _format(ptr, document);
case JvmWildcardTypeReference wtr -> _format(wtr, document);
case XBasicForLoopExpression bf -> _format(bf, document);
case XBlockExpression bl -> _format(bl, document);
case XCastedExpression ce -> _format(ce, document);
case XClosure cl -> _format(cl, document);
case XCollectionLiteral col -> _format(col, document);
case XConstructorCall cc -> _format(cc, document);
case XForLoopExpression fl -> _format(fl, document);
case XIfExpression iff -> _format(iff, document);
case XInstanceOfExpression io -> _format(io, document);
case XReturnExpression ret -> _format(ret, document);
case XSwitchExpression sx -> _format(sx, document);
case XSynchronizedExpression sxn -> _format(sxn, document);
case XThrowExpression tx -> _format(tx, document);
case XTryCatchFinallyExpression tcf -> _format(tcf, document);
case XTypeLiteral tl -> _format(tl, document);
case XVariableDeclaration vd -> _format(vd, document);
case XAnnotation xa -> _format(xa, document);
case ContextVariable cv -> _format(cv, document);
case FormalParameter fp -> _format(fp, document);
case SeverityRange sr -> _format(sr, document);
case JvmTypeConstraint tc -> _format(tc, document);
case XExpression xe -> _format(xe, document);
case XImportDeclaration id -> _format(id, document);
case XImportSection is -> _format(is, document);
case EObject eo -> _format(eo, document);
case null -> _format((Void) null, document);
default -> _format(element, document);
}

?

This is the rewrite Web CoPilot gave me, so do not paste it, it might be incomplete, it is more for you to get an idea.

@rubenporras rubenporras Aug 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It should also execute faster

final EClass eClass = classForJvmType(context, jvmTypeRef.getType());
if (eClass != null) {
final EList<EStructuralFeature> features = eClass.getEAllStructuralFeatures();
final Collection<IEObjectDescription> descriptions = Collections2.transform(features, (final EStructuralFeature f) -> EObjectDescription.create(QualifiedName.create(f.getName()), f));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It looks like https://bugs.eclipse.org/bugs/show_bug.cgi?id=368263 mentioned above is fixed, so this code can be simplified, would you like to ask your friend to simplify it?

@rubenporras rubenporras left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At least remove the @XbaseGenerated annotation. I think also the switch would be nice.

joaodinissf and others added 2 commits August 14, 2026 17:03
Replace the 50-branch if/else instanceof chain in format(Object,
IFormattableDocument) with a Java 21 pattern switch, and drop the
@XbaseGenerated marker (plus its now-unused import) left over from the
Xtend dispatch scaffolding.

Branch order is unchanged, and javac's dominance analysis proves it:
a pattern switch rejects any case dominated by an earlier one, so the
chain's order compiling as-is also establishes that no branch was
unreachable. The null branch moves to an explicit `case null` -- without
it the switch would throw NPE where the chain routed null to
_format((Void) null, document).

The _format method names stay: they override and are called into
XbaseFormatter (see super._format in _format(XMemberFeatureCall, ...)),
so the class-level checkstyle:MethodName suppression stays too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CheckScopeProvider carried a 2012 workaround: "Use dispatch definitions
instead of a switch statement since bug 368263 will otherwise cause the
builder to fail during linking." That bug is an Xtend-compiler
type-inference AssertionError (switch expression containing a closure
that calls a static method) -- the _scope bodies match the trigger
profile exactly, which is why the Xtend original used def dispatch. The
source is Java now; a Java switch shares no compilation path with an
Xtend switch expression, so the constraint is not merely fixed upstream
but inapplicable.

Fold the scope()/_scope() dispatch layer into getScope as a pattern
switch, keeping the observable contract: handled types delegate to
their scope method, unhandled types fall through to super.getScope, and
a null context still throws IllegalArgumentException. Rename the two
surviving overloads to scopeCatalog/scopeIssueExpression -- the
underscore existed only for Xtend dispatch, and deliberately NOT to the
scope_* shape that AbstractDeclarativeScopeProvider resolves
reflectively. Nothing outside the class references the old methods;
only CheckRuntimeModule binds it, and there are no subclasses.

The checkstyle:MethodName and PMD.UnusedFormalParameter suppressions
existed solely for the dispatch scaffolding; both are gone and the
gates stay clean, confirming the cleanup was real.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
if (context.getMarkerObject() != null) {
jvmTypeRef = typeResolver.resolveTypes(context.getMarkerObject()).getActualType(context.getMarkerObject()).toTypeReference();
} else {
jvmTypeRef = EcoreUtil2.<Context> getContainerOfType(context, Context.class).getContextVariable().getType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
jvmTypeRef = EcoreUtil2.<Context> getContainerOfType(context, Context.class).getContextVariable().getType();
jvmTypeRef = EcoreUtil2.getContainerOfType(context, Context.class).getContextVariable().getType();

case CheckCatalog checkCatalog -> scopeCatalog(checkCatalog, reference);
case XIssueExpression xIssueExpression -> scopeIssueExpression(xIssueExpression, reference);
case null -> throw new IllegalArgumentException("Unhandled parameter types: "
+ Arrays.<Object> asList(context, reference).toString());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need <Object>? I would expect we can remove it

// Make sure that only Checks of the current model can be referenced, and if the CheckCatalog includes
// another CheckCatalog, then use that parent as parent scope

final CheckCatalog catalog = EcoreUtil2.<CheckCatalog> getContainerOfType(context, CheckCatalog.class);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
final CheckCatalog catalog = EcoreUtil2.<CheckCatalog> getContainerOfType(context, CheckCatalog.class);
final CheckCatalog catalog = EcoreUtil2.getContainerOfType(context, CheckCatalog.class);

// another CheckCatalog, then use that parent as parent scope

final CheckCatalog catalog = EcoreUtil2.<CheckCatalog> getContainerOfType(context, CheckCatalog.class);
final List<Check> checks = IterableExtensions.<Check> toList(IterableExtensions.<Check> filter(catalog.getAllChecks(), c -> c.getName() != null));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
final List<Check> checks = IterableExtensions.<Check> toList(IterableExtensions.<Check> filter(catalog.getAllChecks(), c -> c.getName() != null));
final List<Check> checks = IterableExtensions.toList(IterableExtensions.filter(catalog.getAllChecks(), c -> c.getName() != null));

// We look first in the workspace for a grammar and then in the registry for a registered grammar
return MapBasedScope.createScope(IScope.NULLSCOPE, Iterables.filter(descriptions, Predicates.notNull()));
} else if (Objects.equals(reference, CheckPackage.Literals.XISSUE_EXPRESSION__CHECK)) {
final List<IEObjectDescription> descriptions = ListExtensions.map(context.getAllChecks(), (final Check c) -> EObjectDescription.create(checkQualifiedNameProvider.getFullyQualifiedName(c), c));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
final List<IEObjectDescription> descriptions = ListExtensions.map(context.getAllChecks(), (final Check c) -> EObjectDescription.create(checkQualifiedNameProvider.getFullyQualifiedName(c), c));
final List<IEObjectDescription> descriptions = ListExtensions.map(context.getAllChecks(), c -> EObjectDescription.create(checkQualifiedNameProvider.getFullyQualifiedName(c), c));

@rubenporras rubenporras left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

some boilerplate to remove

joaodinissf and others added 2 commits August 14, 2026 17:27
Apply the review suggestions on dsldevkit#1452: remove the explicit generic
type witnesses the Xtend compiler always emitted (EcoreUtil2
getContainerOfType x2, IterableExtensions toList/filter, Arrays.asList)
and the explicit lambda parameter type in the ListExtensions.map call.
Java's inference resolves every site to the same types the witnesses
pinned; the compiler is the proof.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vider

Finish the boilerplate sweep from the review round: the three
Collections2.transform lambdas kept their explicit parameter types
after the previous commit removed the type witnesses. Same species,
same fix -- inference resolves the element type from the collection
argument in all three.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joaodinissf
joaodinissf enabled auto-merge (rebase) August 14, 2026 15:46
@joaodinissf
joaodinissf merged commit 0c277d6 into dsldevkit:master Aug 14, 2026
4 checks passed
@joaodinissf
joaodinissf deleted the migrate/xtend-to-java/check-core branch August 14, 2026 15:49
joaodinissf added a commit that referenced this pull request Aug 14, 2026
Apply the review suggestions on #1452: remove the explicit generic
type witnesses the Xtend compiler always emitted (EcoreUtil2
getContainerOfType x2, IterableExtensions toList/filter, Arrays.asList)
and the explicit lambda parameter type in the ListExtensions.map call.
Java's inference resolves every site to the same types the witnesses
pinned; the compiler is the proof.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
joaodinissf added a commit to joaodinissf/dsl-devkit that referenced this pull request Aug 14, 2026
…ore files

Extend the boilerplate cleanup from the dsldevkit#1452 review to the remaining
migrated files: remove explicit generic type witnesses (57) and explicit
lambda parameter types (109) that the Xtend compiler always emits, plus
the five imports orphaned by the removals. Java's inference resolves
every site to the same types; the compiler and the untouched gates are
the proof.

CheckFormatter, CheckJvmModelInferrer, CheckGenerator and
CheckGeneratorExtensions; no semantic change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
joaodinissf added a commit to joaodinissf/dsl-devkit that referenced this pull request Aug 14, 2026
…ore files

Extend the boilerplate cleanup from the dsldevkit#1452 review to the remaining
migrated files: remove explicit generic type witnesses (57) and explicit
lambda parameter types (109) that the Xtend compiler always emits, plus
the five imports orphaned by the removals. Java's inference resolves
every site to the same types; the compiler and the untouched gates are
the proof.

CheckFormatter, CheckJvmModelInferrer, CheckGenerator and
CheckGeneratorExtensions; no semantic change.

Co-Authored-By: Claude Fable 5 <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