Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .agents/skills/xtend-to-java/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ Use this table for quick mechanical transforms. Full details in the rule files.
| `obj?.method()` | `obj != null ? obj.method() : null` (or guard clause) |
| `x ?: default` | `x != null ? x : default` |
| `===` / `!==` (identity) | `==` / `!=` |
| `==` / `!=` (equality) | `.equals()` / `!Objects.equals(a, b)` |
| `==` / `!=` (equality, object operands) | `.equals()` / `!Objects.equals(a, b)` — primitives keep `==`/`!=` (see [`rules/08`](rules/08-operator-overloads.md) §8.1) |
| `a..b` (range) | `IntStream.rangeClosed(a, b)` |

### Lambdas and collections

| Xtend | Java |
|-------|------|
| `[param \| body]` | `(param) -> { return body; }` |
| `[body]` (implicit `it`) | `(it) -> { return body; }` — name the parameter explicitly |
| `[param \| body]` | `(param) -> body` (braces + `return` only for multi-statement) |
| `[body]` (implicit `it`) | `(it) -> body` — name the parameter explicitly |
| `list.filter[condition]` | `list.stream().filter(x -> condition).toList()` |
| `list.map[transform]` | `list.stream().map(x -> transform).toList()` |
| `list.forEach[action]` | `list.forEach(x -> action)` (or `for` loop) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@ public class MyGenerator {
- **Template → StringBuilder** (tier 4) because the template has `«IF»` and `«FOR»` control flow.
- **`it` parameter renamed** to `model` (descriptive name).
- **`Iterables.filter(iter, Type.class)`** kept as Guava — genuinely more concise for type-safe filtering.
- **Whitespace verified against `xtend-gen/`** — the template output was confirmed by reading the generated code.
- **Whitespace must be verified against `xtend-gen/`** — for a real migration, confirm the template output by reading the generated code (this example illustrates the shape).
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ When the body is a simple expression, use `Collectors.joining()`:

```java
public CharSequence renderArgs(final List<Argument> args) {
final StringBuilder builder = new StringBuilder();
final StringBuilder builder = new StringBuilder(512);
builder.append("(");
builder.append(args.stream()
.map(a -> a.getType() + " " + a.getName())
Expand All @@ -32,7 +32,7 @@ When the body has multi-statement logic, use a flag:

```java
public CharSequence renderArgs(final List<Argument> args) {
final StringBuilder builder = new StringBuilder();
final StringBuilder builder = new StringBuilder(512);
builder.append("(");
boolean first = true;
for (final Argument a : args) {
Expand All @@ -52,4 +52,4 @@ public CharSequence renderArgs(final List<Argument> args) {
- The `«FOR … SEPARATOR ", "»` block became either a stream with `Collectors.joining(", ")` or a boolean flag.
- The `«a.type»` and `«a.name»` interpolations became `a.getType()` and `a.getName()`.
- The trailing newline (implicit `\n` at end of template `'''`) is preserved by explicit `"\n"`.
- **Whitespace was verified against `xtend-gen/`** — the template starts with `(` directly (no leading newline because the first `'''` line has content after the opening mark).
- **Whitespace must be verified against `xtend-gen/`** — note the template starts with `(` directly (no leading newline because the first `'''` line has content after the opening mark).
8 changes: 1 addition & 7 deletions .agents/skills/xtend-to-java/rules/00-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Use Java standard library for collections and streams. Keep Guava only where it

## String building — 4-tier decision tree

Choose the most readable Java idiom based on the template pattern:
Choose the most readable Java idiom based on the template pattern (evaluate top-down, first match wins):

| # | Template pattern | Java idiom |
|---|---|---|
Expand All @@ -53,12 +53,6 @@ Choose the most readable Java idiom based on the template pattern:
| 3 | Interpolation, no control flow | `.formatted()` (text block if multi-line, literal if single-line) |
| 4 | Control flow (`«IF»`, `«FOR»`) | `StringBuilder` with explicit `if`/`for` |

**Decision rules — in order:**
1. **No interpolation, single line** → string literal
2. **No interpolation, multi-line** → text block
3. **Interpolation, no control flow** → `.formatted()` (on text block if multi-line, on literal if single-line)
4. **Control flow** → `StringBuilder` — always

**`.formatted()` limitations — fall back to concatenation ONLY when:**
- The interpolated expression is glued to adjacent text with no whitespace/delimiter boundary, making `%s` ambiguous (e.g., `"pre" + expr + "suf"` where `"pre%ssuf"` is confusing)
- The template contains literal `%` characters (would need escaping as `%%`)
Expand Down
16 changes: 8 additions & 8 deletions .agents/skills/xtend-to-java/rules/01-imports-and-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@ Keep identical. Add a trailing semicolon if missing.

## 1.3 Import order convention

Follow the project's Eclipse import order — **standard/framework first, project-specific second**:
Canonical order (ground truth from migrated files): **`java.*` → `org.*` → `com.*`**, one blank
line between groups, alphabetical by full path within each group. `javax.*` sorts with `java.*`;
`junit.*` sorts inside `org.*` (it is `org.junit.*`). Static imports form their own block.

```java
// Group 1: org.*, java.*, javax.*, junit.* (standard/framework)
import org.eclipse.xtext.testing.InjectWith;
import org.junit.jupiter.api.Test;
// Group 1: java.* / javax.*
import java.util.List;

// Blank line separator
// Group 2: org.* (incl. org.junit.*)
import org.eclipse.xtext.testing.InjectWith;
import org.junit.jupiter.api.Test;

// Group 2: com.* (project-specific: com.avaloq.*, com.google.*, etc.)
// Group 3: com.* (com.avaloq.* before com.google.*)
import com.avaloq.tools.ddk.check.core.test.util.CheckTestUtil;
import com.google.inject.Inject;
```

Within each group, imports are sorted alphabetically. There is always exactly one blank line between the two groups.

## 1.4 Class declaration

- Xtend classes are `public` by default. Make this explicit in Java.
Expand Down
6 changes: 5 additions & 1 deletion .agents/skills/xtend-to-java/rules/04-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ The `\` suppresses the line terminator. Place the closing `"""` at column 0 to p
**When to use this pattern:** check the `xtend-gen/` output. If the original string starts with `\n`
and does NOT end with `\n`, the `\` escape is the correct approach.

Text blocks also strip trailing whitespace on every content line; if `xtend-gen/` shows
significant trailing spaces, preserve them with the `\s` escape. See
[`workflow/known-pitfalls.md`](../workflow/known-pitfalls.md) (text-block row).

## 4.4 Return type

Methods that return templates should return `CharSequence` (matches `StringBuilder`).
Expand Down Expand Up @@ -241,4 +245,4 @@ these source-verified semantics decide what is safe:

**Verification:** each coalesced run must be proven byte-identical with an executable old-vs-new
harness over an input battery (empty / single-line / multi-line / newline-terminated / `%`-bearing
values), per the ledger's Method 1.
values).
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- `===` (Xtend identity-equals) → `==` (Java)
- `!==` (Xtend identity-not-equals) → `!=` (Java)
- `==` in Xtend is `.equals()` — convert to `.equals()` or `Objects.equals()` (use `Objects.equals()` when either operand could be null).
- **Applies to object/boxed operands.** `==`/`!=` between primitive-typed operands (int, long, short, byte, char, float, double, boolean) compile to Java `==`/`!=`, not `.equals()` — check operand types in `xtend-gen/` first. (Enums are NOT primitives: enum `==` follows the object form shown in `xtend-gen/`.)

## 8.2 Null-safe navigation `?.`

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/xtend-to-java/rules/09-misc-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ Rules:
- **Multiple return paths**: Xtend implicitly returns the last expression. Add explicit `return` on **every** non-void path.
- **`class` keyword as literal**: `SomeClass` used as a class literal → `SomeClass.class`.
- **Static method reference `::`**: `ClassName::methodName` → `ClassName.methodName()` (or keep as a Java method reference where the receiving API accepts one).
- **Pairs**: `key -> value` (in pair-construction context) → `Pair.of(key, value)` or `Map.entry(key, value)`.
- **Pairs**: `key -> value` compiles to `org.eclipse.xtext.xbase.lib.Pair` — never keep it in migrated Java. Use a small `private record` (nulls OK) or `Map.entry` (rejects null); see [`workflow/known-pitfalls.md`](../workflow/known-pitfalls.md) (xbase.lib row).
- **`^keyword`** (escaped reserved word in Xtend): Drop the `^` — most Xtend escapes aren't Java keywords.

## 9.9 Guice DI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,10 @@ refactor: drop Xtend build infrastructure from <plugin name> # when applicable
The rename commit body must state that it is a pure `git mv` and intentionally non-compiling.

PR title: `refactor: migrate Xtend to Java - <plugin name>`.

## Rollback

Undo the whole slice: `git reset --hard HEAD~N` where N = commits in the slice (2, or 3 with
infra). Plain `HEAD~1` reverts only the translate and strands the pure-`git mv` rename commit —
a `.java` holding Xtend that won't compile. If already pushed: `git revert <translate-sha>
<rename-sha>` — not `-m 1` (the rebase-merged commits are not merge commits).
12 changes: 4 additions & 8 deletions .agents/skills/xtend-to-java/workflow/known-pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,23 @@ Consolidated table of common mistakes and their fixes. Review before and after e
| **Implicit returns** | Xtend methods return the last expression. The compiler catches missing returns but not wrong ones. |
| **Property access vs getter** | Xtend `obj.name` may call `getName()`. In Java, write `obj.getName()` explicitly. Check `xtend-gen/` if unsure. |
| **`CoreException` handling** | Xtend silently wraps checked exceptions. Java doesn't. Add explicit `try/catch` — the `xtend-gen/` file shows what was generated. |
| **`val` leaks** | Never use `var`. Use the explicit type. |
| **Invented Javadoc** | Never add **class/member Javadoc** that wasn't in the original. This is a migration, not a rewrite. (The file copyright header is the one exception — see next row — it is always normalised, not preserved.) |
| **Generated supertypes live in `src-gen/`, not `xtend-gen/`** | When the class extends/overrides a generated `Abstract*` base (Module/Setup/runtime/UI), read that base in `src-gen/` (committed, present without a build — unlike `xtend-gen/`) for inherited constructor signatures, the real `@Override` targets, and the return types Xtend inferred. Don't guess the supertype API. |
| **Copyright header ≠ "preserve original"** | The file MUST start with the Avaloq banner header, **replacing** whatever the source had — including a `/* generated by Xtext x.y */` stub marker or a `/** … */` Javadoc-style copyright block (these are the easy ones to wrongly "preserve", especially on IDE module/setup stubs). Normalising to the banner is required, not inventing. Match a sibling `.java` in the module. |
| **Missing `@throws` tags** | When Java migration adds `throws` and method already has Javadoc, Checkstyle requires `@throws`. Add it; don't create Javadoc just for the tag. |
| **Duplicate string literals** | Checkstyle flags strings appearing 2+ times. In tests, extract to constants. In generators, use `CHECKSTYLE:CONSTANTS-OFF/ON`. |
| **Copyright header ≠ "preserve original"** | Always normalise to the Avaloq banner, replacing whatever the source had — see [`formatting-and-commit.md`](./formatting-and-commit.md) §Copyright header. |
| **`@Data` / `@Accessors`** | These generate code at compile time. The `xtend-gen/` output shows exactly what — copy equals/hashCode/toString/getters from there. |
| **`@Tag` fields must not be `final`** | `TagExtension` assigns tag values via `Field.setInt()` at runtime. `Field.setInt()` on a `final` field fails on Java 9+ even after `setAccessible(true)`. IDE formatters and save-actions silently add `final` to `int` fields — always strip it from `@Tag` fields. See [`rules/09-misc-syntax.md`](../rules/09-misc-syntax.md) §9.6. |
| **`BasicEList` in generic code** | Needs explicit type parameter — `new BasicEList<X>()`. |
| **StringBuilder in `xtend-gen/`** | If `xtend-gen/` has `StringConcatenation` but Xtend has a template, that's the signal to use text block or `.formatted()` (tier 1–3) or `StringBuilder` (tier 4). |
| **Non-parameterized logging** | Xtend files often have `"msg" + x` in log calls. Fix to `{}` placeholders. |
| **PMD missing type-resolution** | Always `compile` before `pmd:check` or you'll miss `MissingOverride`, `LooseCoupling` etc. |
| **`--fail-at-end` hides failures** | Check the final BUILD line, not intermediate output. |
| **Dispatch method names** | Keep underscores. Suppress with `@SuppressWarnings`. Never rename. |
| **IDE save actions** | "Organize Imports" in Eclipse may trigger save actions that auto-convert string concatenation to text blocks. Auto-conversion produces wrong results. Review `git diff` after any IDE action. |
| **Import order** | Two groups (blank line between, no wildcards): framework (`java.*`/`javax.*`/`org.*`) first, then `com.*` alphabetically — so `com.avaloq.*` precedes `com.google.*`. Not enforced by checkstyle (no `ImportOrder` module); wrong order is diff churn only. |
| **Import order** | See [`rules/01-imports-and-package.md`](../rules/01-imports-and-package.md) for the canonical order. Not enforced by checkstyle (no `ImportOrder` module); wrong order is diff churn only — `com.avaloq.*` precedes `com.google.*`. |
| **Eclipse CLI formatter** | Does NOT organize imports — only code formatting. Import order must be correct from the start. |
| **IllegalCatch / IllegalThrows** | checkstyle `IllegalCatch` bans `catch (Exception/Throwable/RuntimeException)` — use the specific type, or a multi-catch (`catch (BadLocationException \| TemplateException e)`) re-thrown as `new IllegalStateException(e)`. `IllegalThrows` bans `throws Throwable/RuntimeException/Error` (plain `throws Exception` IS allowed — acceptable on a `@Test` when the JUnit-invoked API declares it; otherwise narrow to the actual checked type). Don't suppress `PMD.AvoidCatchingGenericException`. |
| **Rollback** | If already pushed: `git revert -m 1 <sha>`. If local only: `git reset --hard HEAD~1`. After reverting, build and test. |
| **Rollback** | A slice is 2-3 commits — plain `HEAD~1` strands the rename commit. Use the recipe in [`formatting-and-commit.md`](./formatting-and-commit.md) §Rollback. After reverting, build and test. |
| **`ByteArrayInputStream.close()`** | It's a no-op. Safe to remove entirely. |
| **`==` in Xtend** | Xtend `==` is `.equals()`, not identity. Convert to `.equals()` or `Objects.equals()`. Only `===`/`!==` are identity. |
| **`==` in Xtend** | On object/boxed operands, Xtend `==` is `.equals()` — convert to `.equals()`/`Objects.equals()`; only `===`/`!==` are identity. Between primitive-typed operands it compiles to Java `==` — check operand types in `xtend-gen/` (see [`rules/08-operator-overloads.md`](../rules/08-operator-overloads.md) §8.1). |

## Learnings from the per-module migration campaign

Expand Down
10 changes: 3 additions & 7 deletions .agents/skills/xtend-to-java/workflow/multi-file-batch.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,15 @@ A red gate means you do not start the next batch. Diagnose first.
a pure `git mv` rename commit for all `.xtend` in the slice, an in-place translate commit, and —
when the module is fully off Xtend — an infrastructure-cleanup commit.

Commit message format:
```
refactor: migrate Xtend to Java - <plugin name> (1/2: rename sources)
refactor: migrate Xtend to Java - <plugin name> (2/2: translate to Java 21)
```
Example: `refactor: migrate Xtend to Java - com.avaloq.tools.ddk.check.core.test`
Commit message format: see [`formatting-and-commit.md`](./formatting-and-commit.md).
Example: `refactor: migrate Xtend to Java - com.avaloq.tools.ddk.check.core.test (1/2: rename sources)`

### Rollback strategy

If a batch fails any validation gate:

1. **Do not force-commit.**
2. Revert: `git reset --hard HEAD~1` (or `git revert -m 1 <sha>` if already pushed).
2. Revert the whole slice per [`formatting-and-commit.md`](./formatting-and-commit.md) §Rollback (a slice is 2-3 commits — plain `HEAD~1` strands the rename commit).
3. Diagnose the failure file-by-file.
4. **Pin** any individually-problematic file for separate investigation. Skip it; do the remaining files.
5. Re-attempt without the pinned files. Tackle pins as one-off conversions afterwards.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Use this when converting a single `.xtend` to its `.java` counterpart.
5. **Apply rules in order.** Walk [`rules/01-...`](../rules/01-imports-and-package.md) through [`rules/09-...`](../rules/09-misc-syntax.md) for the constructs you identified.
6. **Write the `.java` file.** Match `xtend-gen/` behavior exactly while using idiomatic Java.
7. **Run the validation checklist.** [`workflow/validation-checklist.md`](./validation-checklist.md). Every item must pass.
8. **Delete the `.xtend` file** as part of the same commit. Don't leave both.
8. **Commit as two steps** — a pure `git mv` rename commit, then an in-place translate commit; see [`formatting-and-commit.md`](./formatting-and-commit.md) §Commit structure. For a single file the `git mv` IS the removal; do not delete+re-add.
9. **Verify the file compiles:**
```bash
mvn -pl :<module> -am -DskipTests compile -f ./ddk-parent/pom.xml > mvn-output.txt 2>&1
Expand Down
24 changes: 12 additions & 12 deletions .agents/skills/xtend-to-java/workflow/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ This is the full end-to-end workflow for migrating Xtend files to Java in dsl-de

To get a deterministic overview of remaining Xtend work, run one of these commands from the repository root.

On macOS / Linux (bash):

```bash
find . -name "*.xtend" -path "*/src/*" -not -path "*/bin/*" \
| awk -F'/src/' '{print $1}' \
| sort | uniq -c | sort -n
```

On Windows (PowerShell):

```powershell
Expand All @@ -23,14 +31,6 @@ Get-ChildItem -Recurse -Filter "*.xtend" -Path "." |
Format-Table -AutoSize
```

On macOS / Linux (bash):

```bash
find . -name "*.xtend" -path "*/src/*" -not -path "*/bin/*" \
| awk -F'/src/' '{print $1}' \
| sort | uniq -c | sort -n
```

Both list per-module Xtend file counts so you can plan slice scope.

### Scoping the session
Expand All @@ -56,8 +56,8 @@ Always branch from **master** (or the project's main integration branch):

```bash
SLICE=migrate/xtend-to-java/<slice-name>
git fetch origin
git checkout -b "$SLICE" origin/master
git fetch upstream
git checkout -b "$SLICE" upstream/master
```

For stacked multi-slice migrations, suffix the branch name with `-step-N`
Expand Down Expand Up @@ -168,14 +168,14 @@ Xtend's template whitespace rules:
After reading both references, write Java that:
1. **Matches the `xtend-gen/` behavior exactly** for all string outputs, method signatures, and control flow
2. **Uses idiomatic Java** (text blocks, `.formatted()`, concatenation) instead of `StringConcatenation`
3. **Preserves the original class/member Javadoc exactly** — never invent. (The file copyright header is the exception: always normalise it to the Avaloq banner per [`formatting-and-commit.md`](./formatting-and-commit.md) — replacing any generated-stub or Javadoc-style header — which is not "inventing".)
3. **Preserves the original class/member Javadoc exactly** — never invent. (Copyright header excepted: always normalise it to the Avaloq banner per [`formatting-and-commit.md`](./formatting-and-commit.md).)
4. **Follows the quality checklist** in [`workflow/validation-checklist.md`](./validation-checklist.md)

---

## Step 4 — Apply the quality checklist

See [`workflow/validation-checklist.md`](./validation-checklist.md) — all 30 rules must pass.
See [`workflow/validation-checklist.md`](./validation-checklist.md) — every rule must pass.

---

Expand Down
Loading