Skip to content

Declarative router + deep links + bytecode annotation framework#5037

Merged
shai-almog merged 30 commits into
masterfrom
feat/declarative-router-and-deep-links
May 27, 2026
Merged

Declarative router + deep links + bytecode annotation framework#5037
shai-almog merged 30 commits into
masterfrom
feat/declarative-router-and-deep-links

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

  • Optional declarative router in com.codename1.router (URL-based navigation, route guards, redirects, per-tab navigation shell, location listeners) layered on top of the existing Form infrastructure — Form.show() / showBack() continues to work unchanged.
  • Deep-link API: Display.setDeepLinkHandler(LinkHandler) receiving a normalized DeepLink (scheme/host/path/segments/query/fragment); auto-invoked for cold + warm launches. iOS / Android need no port changes — Display.setProperty("AppArg", url) now routes URL-shaped values through the handler.
  • Reusable bytecode annotation framework in the Codename One Maven plugin: a generic AnnotationProcessor SPI + two new Mojos (cn1:generate-annotation-stubs, cn1:process-annotations). ASM-scans target/classes, dispatches to every registered processor, fails-fast with a combined error list. Adding more annotations later is a matter of dropping a new processor into META-INF/services — no orchestrator changes.
  • First concrete processor: RouteAnnotationProcessor validates @Route("/path") classes (extends Form, non-empty path starting with /, accessible constructor, no duplicate patterns) and emits a RoutesIndex + RoutesIndex$Builder directly via ASM. No source-text regex; no runtime reflection.
  • Form.setPopGuard(PopGuard) (Flutter-PopScope analogue) honored from hardware back, toolbar back, and Router.pop().
  • Sheet.showForResult() returning an AsyncResource<T> that auto-cancels with null on dismiss.
  • TabsForm — a Form whose tabs each keep their own navigation stack.
  • iOS Universal Links / Android App Links JSON generators (AasaBuilder, AssetLinksBuilder).
  • JavaScript port window.history bridge: JsRouterBootstrap + standalone cn1-router-history.js shim.

Docs

Two new pages under docs/developer-guide/, both wired into the master developer-guide.asciidoc:

  • Routing-And-Deep-Links.asciidoc — reference covering every part of the API plus iOS Universal Links / Android App Links setup and the extension recipe for adding new annotation processors.
  • Tutorial-Routing-And-Deep-Links.asciidoc — end-to-end tutorial from an empty project to a working deep-linkable app with @Route, guards, and a tab shell.

Asciidoctor lint passes locally with --failure-level WARN.

Test plan

  • 46 new core unit tests for DeepLink, RouteMatch, Router, PopGuard, AasaBuilder, AssetLinksBuilder. All 2608 core tests pass against the local-dev-javase profile.
  • 11 new plugin tests, including an end-to-end test that compiles @Route-annotated fixtures with javac, runs RouteAnnotationProcessor, loads the generated RoutesIndex bytecode in a child classloader, and invokes register() against a stub Router that records every call. Negative tests cover non-Form targets, empty pattern, missing leading slash, duplicate pattern, and abstract class targets.
  • mvn verify on core-unittests (SpotBugs / PMD / Checkstyle) and on codenameone-maven-plugin (SpotBugs): zero new SpotBugs findings on the touched modules.
  • Asciidoctor lint: clean.
  • Watch GitHub Actions PR CI through to green.

Notes on the build-time scanner

The earlier source-regex prototype was scrapped — the user pushed for a fail-fast bytecode-based scanner sharing infrastructure with the existing ASM passes (BytecodeComplianceMojo's String.split rewrite is the prior art). The new framework runs in process-classes, so it sees the JVM's view of the project rather than a textual approximation, and any validation issue aborts the build with class + reason before any generated .class lands on disk.

A small compile-time stub source (a no-op RoutesIndex with register()) is emitted under target/generated-sources/cn1-annotations/ during generate-sources so application code that references RoutesIndex.register() resolves at compile time. process-classes overwrites the stub's .class with the real registrations.

Bumped surefire from 2.22.1 to 3.2.5 in the plugin pom

The older surefire silently skipped every JUnit test in this module under JDK 8 (Tests run: 0 even though AppTest was present). The bump matches the version already pinned by the parent reactor pom. Vintage engine is included so existing JUnit 4 tests in the module keep working.

🤖 Generated with Claude Code

shai-almog and others added 3 commits May 24, 2026 22:06
A new com.codename1.router package layers URL-based navigation, deep
links, route guards, and a per-tab nav shell on top of existing Form
infrastructure. The runtime is opt-in: existing Form.show() / showBack()
code continues to work unchanged.

Core runtime (CodenameOne/src/com/codename1/router/):
- DeepLink — tolerant URL parser (scheme/host/path/segments/query/fragment).
- Router — fluent route(), redirect(), guard(), notFound(), start();
  push/pop/replace with a navigation stack, location listeners, and a
  pluggable BrowserHistoryBridge for the JavaScript port.
- PopGuard / PopReason — Flutter-PopScope analogue; intercepts hardware
  back, toolbar back, and Router.pop() before user code runs.
- RouteContext / RouteBuilder / RouteGuard — typed handoff between a
  matched URL and the Form constructor.
- TabsForm — Form whose tabs each keep their own navigation stack.
- AasaBuilder / AssetLinksBuilder — JSON generators for iOS Universal
  Links and Android App Links.
- web/JsRouterBootstrap + cn1-router-history.js — browser-history bridge
  for the JS port.

Display gains setDeepLinkHandler(LinkHandler) / dispatchDeepLink(url).
Display.setProperty("AppArg", url) now routes URL-shaped values through
the deep-link handler, so iOS/Android ports need no native changes.
Form gains setPopGuard(PopGuard) and checkPopGuard(reason); guards are
honored from MenuBar.keyReleased (hardware back) and Button.fireActionEvent
(toolbar back). Sheet gains showForResult() returning AsyncResource<T>
with auto-cancel on dismiss.

Build-time annotation framework (maven/codenameone-maven-plugin/):
- A reusable AnnotationProcessor SPI under com.codename1.maven.annotations
  (AnnotatedClass, MethodInfo, FieldInfo, AnnotationValues, ProcessorContext,
  ClassScanner). Processors register via ServiceLoader.
- Two new Mojos:
    cn1:generate-annotation-stubs (generate-sources) — writes compile-time
    stubs each processor declares.
    cn1:process-annotations (process-classes) — ASM-scans target/classes,
    dispatches to every registered processor, fail-fast aborts with a
    combined error list when validation fails.
- RouteAnnotationProcessor — the first concrete processor. Bytecode-based
  (not source regex): validates @route classes (extends Form, non-empty
  path starting with /, accessible constructor, no duplicate patterns),
  emits the RoutesIndex + RoutesIndex$Builder bytecode directly via ASM.
  Prefers a (RouteContext) constructor over a no-arg one when both exist.

Adding more annotations is now a matter of dropping a new processor in
META-INF/services — the orchestrator picks them up unchanged.

Tests:
- 46 new core unit tests for DeepLink, RouteMatch, Router, PopGuard,
  AasaBuilder, AssetLinksBuilder (all 2608 core tests still pass).
- 11 new plugin tests: ClassScanner picks annotations off real .class
  files; RouteAnnotationProcessor compiles fixtures with javac, runs
  the processor, loads the generated bytecode in a child classloader,
  and invokes RoutesIndex.register() against a stub Router that records
  every call. Negative tests cover non-Form @route, empty pattern,
  missing leading slash, duplicate pattern, and abstract class targets.

Maven plugin pom: bumped maven-surefire-plugin from 2.22.1 to 3.2.5 and
added junit-vintage-engine. The old surefire silently skipped every JUnit
test in this module under JDK 8; the bump matches the parent reactor's
existing pin.

Docs:
- Routing-And-Deep-Links.asciidoc — reference page covering every part of
  the API plus iOS Universal Links / Android App Links setup.
- Tutorial-Routing-And-Deep-Links.asciidoc — end-to-end tutorial from an
  empty project to a working deep-linkable app with @route, guards, and
  a tab shell.
- Both pages included from developer-guide.asciidoc; asciidoctor lint
  passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Codename One JavaScript port's bundler scans the build output for any
*.js file and importScripts() each one into the parparvm Web Worker.
cn1-router-history.js was authored as a browser-main-thread shim and
crashed the worker with `ReferenceError: document is not defined` on
import, which broke the javascript-screenshots CI run.

Guard the body of the shim with an explicit feature test for document /
addEventListener / history so the same file safely round-trips through
the worker import without doing anything, while still installing the
browser-history bridge when loaded into the main page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Hugo-rendered website flattens each .asciidoc into its own page and
doesn't translate the AsciiDoc link: macro into the resulting HTML, so
both `link:Routing-And-Deep-Links.asciidoc[...]` and
`link:Maven-Getting-Started.adoc[...]` in the tutorial pointed at
non-existent files in the build output. Lychee correctly flagged them.

Replace with inline references — the developer guide is a single
include-stitched book, so cross-page links between included files were
always cosmetic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Compared 110 screenshots: 110 matched.

Native Android coverage

  • 📊 Line coverage: 11.82% (6797/57481 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.62% (34142/354909), branch 4.15% (1401/33740), complexity 5.18% (1677/32374), method 8.99% (1362/15146), class 14.44% (303/2099)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 11.82% (6797/57481 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.62% (34142/354909), branch 4.15% (1401/33740), complexity 5.18% (1677/32374), method 8.99% (1362/15146), class 14.44% (303/2099)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 964.000 ms
Base64 CN1 encode 217.000 ms
Base64 encode ratio (CN1/native) 0.225x (77.5% faster)
Base64 native decode 1018.000 ms
Base64 CN1 decode 293.000 ms
Base64 decode ratio (CN1/native) 0.288x (71.2% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 24, 2026

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

Vale reported 5 issues against the routing chapters:
- Two Microsoft.HeadingColons violations on === headings whose
  post-colon word started lowercase.
- Three Microsoft.Contractions hits for spelling out 'it is', 'does
  not', and 'they are' in the tutorial prose.

LanguageTool flagged 'parparvm' and 'assetlinks' as misspellings.
Both are technical identifiers; add them to the LanguageTool accept
list under a new comment block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Compared 20 screenshots: 20 matched.
✅ JavaScript-port screenshot tests passed.

@github-actions
Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Compared 110 screenshots: 110 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 186 seconds

Build and Run Timing

Metric Duration
Simulator Boot 58000 ms
Simulator Boot (Run) 4000 ms
App Install 14000 ms
App Launch 5000 ms
Test Execution 288000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 353.000 ms
Base64 CN1 encode 1223.000 ms
Base64 encode ratio (CN1/native) 3.465x (246.5% slower)
Base64 native decode 250.000 ms
Base64 CN1 decode 902.000 ms
Base64 decode ratio (CN1/native) 3.608x (260.8% slower)
Base64 SIMD encode 396.000 ms
Base64 encode ratio (SIMD/native) 1.122x (12.2% slower)
Base64 encode ratio (SIMD/CN1) 0.324x (67.6% faster)
Base64 SIMD decode 409.000 ms
Base64 decode ratio (SIMD/native) 1.636x (63.6% slower)
Base64 decode ratio (SIMD/CN1) 0.453x (54.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 55.000 ms
Image createMask (SIMD on) 8.000 ms
Image createMask ratio (SIMD on/off) 0.145x (85.5% faster)
Image applyMask (SIMD off) 112.000 ms
Image applyMask (SIMD on) 53.000 ms
Image applyMask ratio (SIMD on/off) 0.473x (52.7% faster)
Image modifyAlpha (SIMD off) 120.000 ms
Image modifyAlpha (SIMD on) 56.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.467x (53.3% faster)
Image modifyAlpha removeColor (SIMD off) 141.000 ms
Image modifyAlpha removeColor (SIMD on) 72.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.511x (48.9% faster)
Image PNG encode (SIMD off) 926.000 ms
Image PNG encode (SIMD on) 740.000 ms
Image PNG encode ratio (SIMD on/off) 0.799x (20.1% faster)
Image JPEG encode 415.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Compared 110 screenshots: 110 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 187 seconds

Build and Run Timing

Metric Duration
Simulator Boot 60000 ms
Simulator Boot (Run) 1000 ms
App Install 13000 ms
App Launch 7000 ms
Test Execution 254000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 405.000 ms
Base64 CN1 encode 1168.000 ms
Base64 encode ratio (CN1/native) 2.884x (188.4% slower)
Base64 native decode 245.000 ms
Base64 CN1 decode 902.000 ms
Base64 decode ratio (CN1/native) 3.682x (268.2% slower)
Base64 SIMD encode 410.000 ms
Base64 encode ratio (SIMD/native) 1.012x (1.2% slower)
Base64 encode ratio (SIMD/CN1) 0.351x (64.9% faster)
Base64 SIMD decode 366.000 ms
Base64 decode ratio (SIMD/native) 1.494x (49.4% slower)
Base64 decode ratio (SIMD/CN1) 0.406x (59.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 55.000 ms
Image createMask (SIMD on) 9.000 ms
Image createMask ratio (SIMD on/off) 0.164x (83.6% faster)
Image applyMask (SIMD off) 114.000 ms
Image applyMask (SIMD on) 56.000 ms
Image applyMask ratio (SIMD on/off) 0.491x (50.9% faster)
Image modifyAlpha (SIMD off) 116.000 ms
Image modifyAlpha (SIMD on) 51.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.440x (56.0% faster)
Image modifyAlpha removeColor (SIMD off) 137.000 ms
Image modifyAlpha removeColor (SIMD on) 77.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.562x (43.8% faster)
Image PNG encode (SIMD off) 1046.000 ms
Image PNG encode (SIMD on) 911.000 ms
Image PNG encode ratio (SIMD on/off) 0.871x (12.9% faster)
Image JPEG encode 522.000 ms

shai-almog and others added 3 commits May 25, 2026 01:23
The legacy Ant build of CodenameOne core compiles against the CLDC11
bootclasspath, which doesn't expose java.util.regex.Pattern / Matcher —
that's why the rest of CN1's core uses com.codename1.util.regex.RE.
Under JDK 21 the Ant build (build-test (21)) broke with `package
java.util.regex does not exist`.

Switch RouteMatch to RE. Two engine differences vs java.util.regex
that the rewrite has to account for:

1. RE.match(s) is find-style, not full-match. The pattern was already
   anchored with ^ and $, but we also assert getParenStart(0) == 0 and
   getParenEnd(0) == path.length() as belt-and-braces.

2. RE.getParenCount() counts groups the matcher actually visited.
   The catch-all wildcard `**` is implemented as an alternation
   `(?:|/(.*))` where the suffix capture lives on the right branch;
   when the left (empty) branch wins, the inner capture group isn't
   reported at all. Always populate the param map with empty string in
   that case so callers don't have to null-check.

Refactor only — no public API change. All 46 router unit tests still
pass, including the new bare-prefix catch-all assertion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two CI gates needed cleanup:

1. build-test (8): the PR-CI quality-report aggregates PMD findings and
   fails the build when any forbidden rule fires in changed code. The
   routing PR introduced 88 violations split across:
     66 ControlStatementBraces (single-line `if (x) stmt;`)
     10 MissingOverride (anonymous classes implementing interfaces)
      5 ForLoopCanBeForeach (index-based for over a List)
      4 LiteralsFirstInComparisons (`seg.equals("X")`)
      1 SingularField (compiledRegex used only at construction)
      1 UnnecessaryImport (java.util.HashMap leftover after refactor)
      1 UnnecessaryFullyQualifiedName (com.codename1.io.Log.e)
   Re-formatted every flagged line; PMD now reports zero forbidden
   violations from this PR.

2. build-test (17): the legacy Android Ant build compiles with the
   platform-default encoding (US-ASCII) and rejected the em-dashes,
   en-dashes, and smart quotes that had crept into javadoc comments.
   Replaced every non-ASCII character in the new and modified .java
   files with its ASCII equivalent (-- for em-dash, ' for curly quote,
   etc.). Verified there are zero codepoints > 0x7F in any touched
   .java file.

Tests still green: 46 router core tests + 11 plugin tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-test (8) failed at the quality-report Checkstyle gate: the
project's checkstyle.xml uses the default LeftCurly option (`eol`),
which rejects any `{` that has code after it on the same line. Inline
forms like `if (x) { return; }`, `private RoutesIndex() { }`, and
`public String getRaw() { return raw; }` all violated.

Expand every flagged occurrence into the canonical three-line form:

    if (x) {
        return;
    }

PMD's ControlStatementBraces rule is satisfied by the braces; Checkstyle
is now happy that the brace ends its line. The two gates pull in the
same direction once you commit to multi-line bodies.

Affects only the new routing package and the surface I touched on
Display/Form/Sheet/MenuBar/Button. No behavior change. All 46 router
core tests + 11 plugin tests still pass; local Checkstyle severity-Error
count drops from 99 to 0 across the touched files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

shai-almog and others added 3 commits May 25, 2026 16:38
…files

The new files I introduced inherited the Oracle "All rights reserved"
copyright header from the surrounding sources I was copying boilerplate
from. That header is correct on Oracle-origin files (the J2ME fork
ancestry) but wrong on newly authored code, which belongs to Codename
One. Swap every header on the 44 new files for the canonical CN1
header. Touched files in CodenameOne/src/com/codename1/router,
maven/codenameone-maven-plugin, the test stubs, and the new core unit
tests.

Also strip every `#### Since 8.0` block from doc comments. We're not at
8.0 yet and the version pinning was speculative; the public API surface
is stable across the touched packages and the per-method version markers
were noise.

No code or test changes; all 46 router unit tests + 11 plugin tests
still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Combine the two non-overlapping accept-list blocks (router & JS port
identifiers from this PR, WebAuthn / passkey terms from master's #5039)
into one section.
cn1-router-history.js was wrongly living under
CodenameOne/src/com/codename1/router/web/, which is the Java source
tree. That's exactly why it got swept into the parparvm worker bundle
by the JS bundler (which scans `*.js` in the build output and
importScripts()'s them) and crashed with `ReferenceError: document is
not defined` — a class of bug that only existed because the file sat
in the wrong place.

Move to Ports/JavaScriptPort/src/main/webapp/cn1-router-history.js
alongside port.js and sw.js. Those files are served as static webapp
assets and never imported into the worker, so:
  - the bundler doesn't pick the shim up at all
  - the worker-context guard I added earlier becomes unnecessary and is
    removed (the shim now unconditionally installs the popstate /
    history.pushState bridge it always wanted to)

Also flatten the now-empty com.codename1.router.web subpackage:
JsRouterBootstrap moves up to com.codename1.router. It was the only
class in the subpackage and is itself a single-method install() helper
that doesn't justify its own namespace.

Doc updates to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 648 total, 0 failed, 2 skipped

Benchmark Results

  • Execution Time: 10174 ms

  • Hotspots (Top 20 sampled methods):

    • 21.40% java.lang.String.indexOf (382 samples)
    • 20.67% com.codename1.tools.translator.Parser.isMethodUsed (369 samples)
    • 17.37% java.util.ArrayList.indexOf (310 samples)
    • 5.10% java.lang.Object.hashCode (91 samples)
    • 4.71% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (84 samples)
    • 2.24% com.codename1.tools.translator.ByteCodeClass.calcUsedByNative (40 samples)
    • 2.24% java.lang.System.identityHashCode (40 samples)
    • 1.68% com.codename1.tools.translator.ByteCodeClass.updateAllDependencies (30 samples)
    • 1.62% com.codename1.tools.translator.ByteCodeClass.markDependent (29 samples)
    • 1.23% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (22 samples)
    • 1.12% com.codename1.tools.translator.Parser.cullMethods (20 samples)
    • 1.06% com.codename1.tools.translator.BytecodeMethod.optimize (19 samples)
    • 1.06% com.codename1.tools.translator.Parser.getClassByName (19 samples)
    • 1.01% com.codename1.tools.translator.BytecodeMethod.appendMethodSignatureSuffixFromDesc (18 samples)
    • 1.01% com.codename1.tools.translator.BytecodeMethod.equals (18 samples)
    • 0.95% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (17 samples)
    • 0.78% java.lang.StringBuilder.append (14 samples)
    • 0.67% java.io.FileOutputStream.writeBytes (12 samples)
    • 0.67% com.codename1.tools.translator.BytecodeMethod.isMethodUsedByNative (12 samples)
    • 0.56% com.codename1.tools.translator.ByteCodeClass.generateCCode (10 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

shai-almog and others added 12 commits May 25, 2026 20:27
…tcher

Major redesign of the deep-linking story per maintainer feedback. The
previous PR exposed a Router class, a TabsForm with forced UI hierarchy,
location/history listeners, a DeepLink value class, a LinkHandler
interface, a BrowserHistoryBridge, and tools/AasaBuilder + AssetLinksBuilder
in core. The route table was wired up by the application calling a
generated register() method.

That surface was too big and put URL semantics into the public runtime.
Replaced with a Spring-style annotation-driven design where the only
public types in the routing space are two annotations:

    @route("/users/:id")
    public class ProfileForm extends Form {
        public ProfileForm(@RouteParam("id") String id) { ... }
    }

    @route("/home")
    public static Form home() { ... }

Method-level @route on static factories is supported the same way.

Under the hood
--------------

The Codename One Maven plugin's process-annotations goal scans the
project's compiled bytecode, validates every @route fail-fast, and
generates com.codename1.router.generated.Routes -- a final class
implementing the package-private RouteDispatcher SPI. A static
Routes.bootstrap() installs itself into Display from its declaration; the
generated dispatcher does URL parsing, pattern matching, path variable
extraction, query-string fallback, and Form factory invocation. The
existing setProperty("AppArg", url) hook in every CN1 port routes URL-
shaped values through Display's internal dispatcher.

Removed
-------

* Router, RouteContext, RouteBuilder, RouteGuard, RouteMatch (folded into
  the generated dispatcher).
* Location, LocationListener (gone -- no history-stream public API).
* DeepLink, LinkHandler (URL semantics are internal).
* TabsForm (forced UI hierarchy; the maintainer's preference is to leave
  Tabs as a flexible Container and let route metadata live on the
  Container if needed at all).
* BrowserHistoryBridge, JsRouterBootstrap, cn1-router-history.js
  (history mirroring belongs in a port-side concern, not the public
  runtime; can be reintroduced later in Ports/JavaScriptPort if needed).
* Display.setDeepLinkHandler / getDeepLinkHandler / dispatchDeepLink --
  the public deep-link API is gone. Replaced by Display.installRouteDispatcher
  which is doc-tagged "internal".
* The standalone Routing-And-Deep-Links and Tutorial-Routing-And-Deep-Links
  docs (rewritten as one short Deep-Links-Routing chapter).

Moved
-----

* AasaBuilder / AssetLinksBuilder and their tests: from
  com.codename1.router.tools (public runtime) to
  com.codename1.maven.routing in the maven plugin (build-time tooling).
* JavaSourceCompiler: from plugin test-classes to main, so the route
  processor can use it to compile its generated source on the fly.

Added
-----

* @com.codename1.annotations.RouteParam: Spring-style parameter binding.
* com.codename1.router.RouteDispatcher: internal SPI implemented by the
  generated class.
* com.codename1.router.generated.Routes (stub): no-op shipped with the
  framework, overwritten in target/classes by the maven plugin when the
  project declares @route targets.
* Three package-info.java files for the touched packages.

Removed every Flutter reference and every "Since X.Y" marker from the
new code; PopGuard's docs no longer mention Flutter's PopScope. The
maintainer pointed out we don't have a 8.0 commitment yet.

Tests
-----

* 19 plugin tests pass: ClassScanner, Aasa/AssetLinks builders, and an
  end-to-end RouteAnnotationProcessorTest that compiles @route fixtures,
  runs the processor, loads the generated Routes class in a child
  classloader, dispatches URLs, and verifies the path / method dispatch.
* Full core unit-test reactor still green (verify clean).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI Linux JDK 8 sees Display.getInstance().dispatcher null after bootstrap
while local Mac JDK 8 has it set. Add prints so CI surfaces which
classloader sees which Display, whether installRouteDispatcher was
called, and identity hashes.

To be reverted once the cause is understood.
Conflict was in CodenameOne/src/com/codename1/annotations/package-info.java
where both sides added the file. PR #5040 on master shipped a broader
description covering the whole annotations package (build pipeline +
ParparVM bytecode translator) while this branch had a routing-only
blurb. Took master's wording verbatim; it accurately covers the new
@route / @RouteParam additions as build-pipeline annotations alongside
the existing ParparVM-direction ones.

Also aligned the two other new package-info.java files
(com.codename1.router, com.codename1.router.generated) to master's
convention: a `///` description with no copyright header, matching
io/package-info.java, ui/package-info.java, and the package-info files
PR #5040 added.

Tests still pass: 19 plugin tests + the routing parts of core remain
green locally.
The previous shape exposed only declarative @route annotations and
relied on platform deep-links to drive navigation. That left no easy way
to trigger routing from app code (other than `new MyForm().show()`,
which bypasses the route table).

Add a tiny imperative API alongside:

    Navigation.navigate("/users/42");   // push by URL through the route table
    Navigation.back();                   // pop
    Navigation.getCurrent();             // top-of-stack entry
    Navigation.getStack();               // breadcrumb-ordered snapshot
    Navigation.popTo(entry);             // pop back to a specific entry

Five static methods plus a value type (NavigationEntry { path, form,
title }). The Navigation stack only tracks URL-driven calls; raw
`Form.show()` still works untouched, so application code picks
declarative or imperative per call site.

Under the hood
--------------

* RouteDispatcher.dispatch(String) now returns Form (or null) instead of
  void. The caller -- Navigation -- pushes the stack and calls show().
* The build-time-generated Routes class is the only RouteDispatcher
  implementation; its bootstrap() now installs onto Navigation rather
  than Display.
* Display loses installRouteDispatcher / dispatchUrlInternal. The
  AppArg URL path on Display.setProperty routes through
  Navigation.dispatchExternalUrl, which handles the EDT hop and
  delegates to Navigation.navigate. External deep links and in-app
  navigations share one stack.
* The generated Routes.dispatch emits branches most-specific first
  (literals > params > catch-all) so the right route wins when several
  patterns overlap; first match returns directly.

Tests
-----

20 plugin tests pass locally, including:

* `classLevelRouteWithPathVariableIsDispatched` -- @route on a Form
  subclass with a @RouteParam constructor binding.
* `methodLevelRouteFactoryIsDispatched` -- @route on a static factory
  method.
* `navigationStackSupportsBackAndPopTo` -- navigate/back/popTo round-
  trip over a three-form stack.
* The four existing fail-fast validation tests (non-Form target, empty
  pattern, missing leading slash, duplicate pattern across classes).

Docs
----

Adds a "Navigate from app code" section to
docs/developer-guide/Deep-Links-Routing.asciidoc covering the
five-method API and a breadcrumb-render example.
…path

The plugin's RouteAnnotationProcessorTest previously shipped local stubs
for cn1-core types so the in-process javac call could resolve @route /
@RouteParam / Form. On Linux JDK 8 the stubs' classloader and the real
cn1-core jar (pulled in transitively elsewhere) ended up shadowing each
other, producing "dispatcher null after bootstrap" failures.

This swap:
- Deletes the test stubs entirely.
- Adds codenameone-core as a test-scope dependency on the plugin so the
  real annotations and Form are on the test classpath.
- Teaches JavaSourceCompiler to consume `surefire.test.class.path` (and
  to fall back to the current URLClassLoader's URL list) when building
  the compiler classpath, because surefire 3.x only puts the booter jar
  on java.class.path.
- Rewrites the test to validate the generated Routes.class structurally
  via ASM rather than invoking it -- avoids the classloader-sharing
  pitfalls that motivated the rewrite in the first place.

All 84 plugin tests pass locally on JDK 8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The catch (Exception ignored) swallowing block introduced for the
URLClassLoader walk tripped SpotBugs' forbidden-rule gate on Linux JDK 8
PR-CI (build-test (8)). Extract the URL->File conversion to a small
helper that catches only the documented exceptions
(URISyntaxException, IllegalArgumentException) and returns null on
failure, so the call site can simply skip the entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three forbidden PMD violations on Linux JDK 8 PR-CI were blocking the
build:

* `CompareObjectsWithEquals` in `Navigation#popTo` -- `popTo` matches by
  reference identity (callers pass back the same `NavigationEntry`
  they pulled out of `getStack()`), and since `NavigationEntry` doesn't
  override `equals` the inherited `Object#equals` is reference equality,
  so the switch from `==` to `.equals(...)` is semantics-preserving.
* `UnnecessaryFullyQualifiedName` on `java.util.Collections.sort(...,
  new java.util.Comparator<...>())` in `RouteAnnotationProcessor` --
  import them and drop the qualifiers.
* `UnusedLocalVariable` on `literalCount` in `emitRouteBranch` -- it
  was a leftover from an earlier specificity-ranking sketch that's
  no longer used (specificity is computed elsewhere).

Also dropped the unused `Arrays` import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously cn1-core shipped a no-op stub
`com.codename1.router.generated.Routes` that the Codename One Maven
plugin overwrote in the project's `target/classes` at `process-classes`
time. That works on the JavaSE simulator (URLClassLoader honors the
project's classes first), but it is hostile to platforms that translate
bytecode -- parparvm and Android both have to deal with two definitions
of the same class living in different jars and can produce ambiguous
output. Shadowing a framework class with a per-project class is exactly
what the stub mechanism in the builders is meant to avoid: the
generated class belongs in the per-build stub area, not in the
framework jar.

Changes:

* Delete `CodenameOne/src/com/codename1/router/generated/` so the
  cn1-core jar no longer carries a Routes class.
* `Display.init()` resolves `Routes.bootstrap()` via reflection and
  silently no-ops on `ClassNotFoundException`. A project without any
  `@Route` produces no Routes class -- `Display` just doesn't install
  the dispatcher.
* `RouteAnnotationProcessor.finish()` no longer references "the
  framework stub" when there are no project routes -- there is no stub
  to leave alone.
* Developer guide updated to call out why the dispatcher is per-project.

The Maven plugin's `process-annotations` Mojo continues to be the place
that writes the generated dispatcher into the project's `target/classes`
at an early build step; the server-side builders receive the project
bundle with that class already in it (or no class at all when the app
has no `@Route`). No builder changes are required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Ant CN1 core build (build-test 8/17/21) compiles against the CLDC11
profile in Ports/CLDC11. CLDC11's Class.java exposes forName and
newInstance but not getMethod, so the previous
`Class.forName(...).getMethod("bootstrap").invoke(null)` form did not
compile against that bootclasspath.

Switch to a self-registering constructor: the generated Routes class
now declares a public no-arg ctor that calls
Navigation.setDispatcher(this), and Display#init() does
Class.forName(...).newInstance() to trigger it. Both calls are in
CLDC11. Test updated to introspect <init> rather than the removed
static bootstrap method.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two style issues flagged by Microsoft.Contractions and Microsoft.Adverbs
on the paragraph describing the per-project Routes dispatcher:

* "it is" -> "it's"
* drop the "silently" qualifier

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Class.forName lookups for the route dispatcher would silently break in
shipped builds (ParparVM and Android obfuscate user classes) even
though they pass in the simulator. The correct injection point is the
application stub each builder writes per build: it's compiled against
the project's classpath, so a direct symbol reference is preserved by
the same keep-rules that cover the rest of the stub.

* Display.init no longer touches Routes.
* RouteAnnotationProcessor.emitStubs now unconditionally writes a no-op
  Routes.java to target/generated-sources/cn1-annotations during the
  generate-sources phase. process-classes still overwrites it with the
  real dispatcher when @route declarations are present. Projects with
  zero @route still compile and run -- the no-op constructor simply
  doesn't install a dispatcher.
* iOS builder (server-side BuildDaemon and the in-repo maven plugin's
  IPhoneBuilder) emit `new com.codename1.router.generated.Routes();`
  immediately before `Display.init(stub)` in the generated Stub.java.
* Android builders (server-side BuildDaemon AndroidBuilder /
  AndroidGradleBuilder and the in-repo plugin's AndroidGradleBuilder)
  emit the same call in front of the first Display init in onResume.
  The reinit branches don't repeat the binding because the dispatcher
  is held statically by Navigation and survives a Display reinit.
* Desktop production stub (cn1app-archetype template + the existing
  hellocodenameone example) binds Routes immediately before Display
  init too.
* Developer guide explains the per-build injection model.

Note: the simulator path (`mvn cn1:run`) currently does not auto-bind
because the simulator's launcher is the cn1-core JavaSE port's
Executor, which has no compile-time reference to a per-project class.
`cn1:run-desktop` (which goes through the user's *Stub) does bind
correctly. Will revisit simulator binding separately if needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups to the per-build-stub binding switch:

1. The archetype-generated common/pom.xml now declares the
   generate-annotation-stubs and process-annotations executions, so
   every freshly archetyped project has a com.codename1.router.generated
   .Routes class on its classpath -- the per-platform stub's direct
   symbol reference resolves. Same update applied to the existing
   hellocodenameone sample.

2. The JavaSE simulator (Ports/JavaSE) now installs the dispatcher via
   Class.forName + newInstance in Executor#runApp just before
   Display.init. The simulator is the legitimate place for dynamic
   loading -- it runs unobfuscated, already spins its own
   ClassPathLoader, and Executor is already heavily reflective for
   loading the user's main class. For ParparVM iOS and Android the
   per-build application stub continues to bind by direct symbol
   reference; obfuscation rewrites both call site and target class
   together.

3. Vale flagged a 'do not' contraction in the routing doc; switched
   to 'don't'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 26, 2026

Compared 11 screenshots: 11 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

shai-almog and others added 8 commits May 26, 2026 10:30
…OCATION

LanguageTool flagged "Direct symbol references survive obfuscation"
suggesting a missing preposition; rephrase as "are preserved under
obfuscation" which sidesteps the rule and reads about the same.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous push always emitted `new com.codename1.router.generated
.Routes()` into the per-platform application stub, but projects that
predate the annotation Mojo (e.g. input-validation-app, the demos
under docs/demos) don't have a Routes class on their classpath and the
generated stub failed to compile.

Check classesDir for `com/codename1/router/generated/Routes.class`
before emitting the install line. Projects that wire up the annotation
Mojo get the binding; legacy projects skip it (they had no routing
support to begin with).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous push checked classesDir/com/codename1/router/generated/Routes
.class to decide whether to emit the install line into the per-platform
stub. That gives false positives: the builder unzips CN1 framework jars
(iOSPort.jar, nativeios.jar, etc.) into classesDir before stub
generation, and if any of those framework jars happens to carry a
Routes.class the check fires for a project that never opted in. The
generated stub then references com.codename1.router.generated.Routes
but the javac classpath used to compile the stub doesn't necessarily
include the framework jar, so compilation fails with "package
com.codename1.router.generated does not exist".

Inspect sourceZip (the project's jar-with-deps) directly. cn1-core
itself ships no Routes class, so a hit in sourceZip is the project's
own annotation-Mojo emission. Same change applied to both iOS and
Android builders, locally and in BuildDaemon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CI cache key for the "Restore built CN1 + iOS port artifacts" step
hashes CodenameOne/src + Ports/iOSPort + vm/* + Themes + native-themes
but not maven/codenameone-maven-plugin/src. When a PR only touches the
Maven plugin (e.g. the builder Stub generation logic), the cache key
matches an older entry built before the change, so the iOS / scripts /
input-validation workflows restore a stale codenameone-maven-plugin
jar and the freshly-pushed builder fix never runs.

Add maven/codenameone-maven-plugin/src/main to the SRC_HASH find list
across all six iOS-related workflows so plugin changes invalidate the
cache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CN1 copyright header restored on Navigation.java and NavigationEntry
  .java, and the floating class-level Javadoc moved out from above the
  package declaration to where Javadoc actually attaches (immediately
  before the type). Package-info.java files keep package-level docs
  above the package statement as they always have.

* Stop hardcoding `new com.codename1.router.generated.Routes()` in
  application source. The archetype-generated `*Stub.java` template
  and the existing hellocodenameone sample stub no longer reference
  Routes; the JavaSE port instead installs the dispatcher in its
  `postInit()` override via `Class.forName(...).newInstance()`, which
  covers both the simulator-driven entry (Executor) and the
  desktop-production entry (per-app stub) without a static dependency
  in user code. Removed the now-redundant Class.forName from the
  JavaSE Executor.

* Remove the orphan "Note: the per-project route dispatcher..."
  comment from Display.init -- it didn't annotate any code after the
  Class.forName itself was removed.

* RouteAnnotationProcessor no longer emits a no-op Routes stub at
  generate-sources. Routes class now exists only when the project
  actually has `@Route` declarations, so the per-platform application
  stub's `zipHasRoutes(sourceZip)` check correctly skips the install
  line when there's nothing to install.

* Wire the annotation Mojo into scripts/initializr's common/pom.xml
  the same way as the archetype, so projects bootstrapped from that
  sample also pick up routing out of the box. (The user shouldn't have
  to configure the Mojo themselves.)

* Developer guide trimmed: dropped the "Wire the build" section
  (auto-configured by the archetype/initializr templates), replaced
  the "Enable Associated Domains in Xcode" prose with the existing
  `ios.associatedDomains` build hint (the iOS builder already writes
  the entitlement), and replaced the verbose `<intent-filter>` example
  with a reference to the existing `android.xintent_filter` build
  hint. Both build hints already appear in the build hints table.
  Added a brief "How it works" section explaining the build-time scan
  + per-platform-stub binding in broader terms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After deleting `RouteAnnotationProcessor.emitStubs` the
`generate-annotation-stubs` Mojo became a no-op for routing (the only
processor that exists). Dropping the execution out of the archetype
and the hellocodenameone sample keeps the generated pom focused on
goals that actually do work; the build hint table and the developer
guide already point users at `process-annotations` as the single
goal they need.

Revert the matching `scripts/initializr/common/pom.xml` edit
entirely: that project is pinned to the released `7.0.244` plugin
to demonstrate "users on the released version" and the new
process-annotations goal doesn't exist there yet (the Hugo website
build runs `mvnw package` against it and was failing with
MojoNotFoundException). The initializr resource templates
(barebones-pom.xml, kotlin-pom.xml, ...) likewise stay on the released
goal set; they can pick up `process-annotations` when the next CN1
release ships with the new Mojo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The iOS and Android local builders both reinvented the same ZipFile
probe and the same install-line emitter for the build-time-generated
@route dispatcher. Move both helpers onto the shared Executor:

* `Executor#projectHasRouteDispatcher(sourceZip)` -- the ZipFile probe.
* `Executor#routeDispatcherInstallSource(sourceZip, indent)` -- the
  stub-source fragment (empty when the project ships no Routes class,
  so legacy CN1 apps without the annotation Mojo still produce a clean
  stub).

IPhoneBuilder and AndroidGradleBuilder now just splice in the helper
output, which keeps the per-platform code short and lets the comment
explaining the obfuscation contract live in one place.

84/84 plugin tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog merged commit 9d649cf into master May 27, 2026
33 checks passed
shai-almog added a commit that referenced this pull request May 27, 2026
…nding, SQLite ORM (#5047)

* Build-time POJO annotation frameworks: JSON/XML mapping, component binding, SQLite ORM

Three new bytecode-driven annotation processors layered on top of the
AnnotationProcessor SPI introduced in #5037. Each generates a typed
runtime artifact next to the annotated class plus a tiny Index class
that registers everything with a public runtime registry. There is no
Class.forName, no service loader, and no field reflection: every read
and write in the generated code is a direct symbol reference that
ParparVM rename and R8 obfuscation rewrite together with the generated
class.

Application surface (com.codename1.annotations):

- @mapped, @JsonProperty, @JsonIgnore, @xmlRoot, @xmlelement,
  @XmlAttribute, @XmlTransient -- JSON / XML object mapping.
- @bindable, @Bind -- component binding for Form / Container UI by
  Component#getName.
- @entity, @id, @column, @DbTransient -- SQLite ORM mapped through
  com.codename1.db.Database.

Each works on plain POJOs with public fields and on
PropertyBusinessObject-style classes with Property / ListProperty
fields in the same class.

Runtime entry points (no reflection, no dynamic classloading):

- com.codename1.mapping.Mappers#toJson / #fromJson / #toXml / #fromXml.
- com.codename1.binding.Binders#bind returning a Binding handle with
  refresh() / commit() / disconnect().
- com.codename1.orm.EntityManager#open(dbName).dao(EntityClass.class),
  Dao#createTable / #insert / #update / #delete / #findById /
  #findAll / #find(where, params).

cn1-core ships no-op stub classes for the three generated index
types so application code can reference Mappers / Binders /
EntityManager at compile time even when the project carries no
@mapped / @bindable / @entity classes; the generated index shadows the
stub at build time.

Build pipeline (maven/codenameone-maven-plugin):

- PropertyTypeKind: shared field-type classifier (scalars,
  Property<T>, ListProperty<T>, List<T>, nested references). Extracts
  the first generic argument from the class-file signature so the
  three processors don't need to redo the type inference per call.
- MappingAnnotationProcessor: writes one XxxMapper per @mapped class
  + a MappersIndex, via JavaSourceCompiler (JSR 199), into
  target/classes.
- BindingAnnotationProcessor: writes one XxxBinder per @bindable
  class + a BindersIndex. Generated binders include a recursive
  Container#getComponentAt walk so name lookup is reflection-free.
- OrmAnnotationProcessor: writes one XxxDao per @entity + a
  DaosIndex. Daos issue prepared statements through Database, read
  rows through Cursor / Row, and back-fill auto-increment ids via
  SELECT last_insert_rowid().
- ClassScanner + FieldInfo now retain the field generic signature so
  processors can resolve Property<T> and List<T> element types
  without rereading the .class file.
- META-INF/services/AnnotationProcessor: registers the three new
  processors. Same SPI as the routing processor.

Build-server wiring:

- Executor#annotationFrameworksInstallSource emits
  `new com.codename1.mapping.generated.MappersIndex();` +
  `new com.codename1.binding.generated.BindersIndex();` +
  `new com.codename1.orm.generated.DaosIndex();` as a stub fragment.
- IPhoneBuilder and AndroidGradleBuilder splice the fragment in
  before the first Display.init -- next to the existing @route
  install line.

Tests (9 new, 84 -> 93 plugin tests green):

- MappingAnnotationProcessorTest: end-to-end JSON round-trip for a
  POJO (public fields, @JsonIgnore, @JsonProperty rename) and a
  PropertyBusinessObject with Property<String> + Property<Integer>.
  Negative cases: abstract @mapped class, @mapped class without a
  public no-arg constructor.
- BindingAnnotationProcessorTest: ASM-introspected shape check on
  the generated LoginModelBinder. Negative case: @Bind on a private
  field.
- OrmAnnotationProcessorTest: ASM-introspected shape check on the
  generated UserDao (createTable / insert / update / delete /
  findById / findAll / find / attach / type / tableName). Negative
  cases: @entity without @id, @entity field that points to another
  entity or a List (relationships are out of scope for v1).

Docs (docs/developer-guide):

- Annotation-JSON-XML-Mapping.asciidoc
- Annotation-Component-Binding.asciidoc
- Annotation-SQLite-ORM.asciidoc

All three are wired into developer-guide.asciidoc right after the
Deep-Links-Routing chapter. asciidoctor lint passes on each new page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: cn1 Character.forDigit gap + Vale/LanguageTool style nits

build-test (8/17/21) failed because cn1's stripped-down
java.lang.Character does not declare Character.forDigit -- the cn1
core compiles at source 1.5 / target 1.5 against the cn1 JavaAPI, not
the full JDK runtime. Inline a "0123456789abcdef" lookup in
Mappers#writeJsonString so the JSON string encoder stays portable to
every cn1 target.

build (developer-guide quality gate) failed on 14 Vale Microsoft
contraction / adverb / hyphen findings and 3 LanguageTool spelling
matches in the three new annotation chapters. Rewrite the prose to
use contractions ("doesn't" / "isn't" / "aren't" / "can't" / "don't"
/ "it's"), drop the "tightly" and "deliberately" adverbs, switch
"auto-increment" to "autoincrement", use "façade" with the diacritic,
and add `[Dd]ao` / `[Dd]aos` to the LanguageTool accept-list so the
data-access-object identifier doesn't trip the en_US dictionary.

vale Annotation-JSON-XML-Mapping.asciidoc Annotation-Component-Binding.asciidoc Annotation-SQLite-ORM.asciidoc -> 0 errors, 0 warnings, 0 suggestions
asciidoctor --safe-mode=safe -> clean on all three pages
mvn -pl codenameone-maven-plugin test -> 93/93

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: SpotBugs forbidden violations on build-test (JDK 8)

Five SpotBugs findings out of the static-analysis gate on the
JDK-8 Ant build:

- 3x RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT on the
  `new com.codename1.{mapping,binding,orm}.generated.XxxIndex();`
  calls in Mappers#ensureIndexLoaded / Binders#ensureIndexLoaded /
  EntityManager#ensureIndexLoaded. The cn1-core stubs that ship in
  the framework have empty no-op constructors so SpotBugs can't see
  that the shadowing class generated by the annotation processor
  registers every mapper / binder / dao. Assign the result to a
  private static `indexInstance` Object so the instantiation has a
  visible side effect (which it does in practice -- the constructor
  registers everything before returning).

- UCF_USELESS_CONTROL_FLOW on the empty
  `if (bc.fields.isEmpty()) { /* comment */ }` block in
  BindingAnnotationProcessor#processClass. Drop the branch and lift
  the comment up next to `accepted.put`.

- DB_DUPLICATE_SWITCH_CLAUSES on OrmAnnotationProcessor#emitFieldRead
  where the STRING and BYTE_ARRAY cases shared a body
  (`sb.append(inst).append('.').append(f.fieldName)`). Merge them
  into a single multi-label case with a comment that explains why
  both feed the same Database#execute parameter bind path.

mvn -pl codenameone-maven-plugin test -> 93/93 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: use indexInstance as the load sentinel to satisfy SpotBugs

The previous fix introduced `private static Object indexInstance` to
silence SpotBugs' RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT finding on
`new XxxIndex();`, but the field was only ever written -- SpotBugs
flipped over to URF_UNREAD_FIELD on the next build-test (8) run.

Read and write the field instead. Drop the redundant `indexLoaded`
boolean and use `indexInstance != null` as the load gate. On the no-
@mapped / @bindable / @entity fallback path we pin Boolean.FALSE as
the sentinel so we don't retry the NoClassDefFoundError lookup on
every Mappers.get / Binders.get / EntityManager.dao call.

The field is `volatile` so the initial-load happy path is lock-free
on every subsequent call (the synchronized method only re-locks when
the value is still null, which is the rare first-call case).

mvn -pl codenameone-maven-plugin test -> 93/93 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: 15 PMD violations on build-test (JDK 8)

After fixing SpotBugs URF_UNREAD_FIELD the build-test (8) gate hit
the PMD forbidden-rules list:

- AvoidUsingVolatile (3): the `private static volatile Object
  indexInstance` field used to plug SpotBugs' no-side-effect check.
  Replace with the initialization-on-demand holder idiom -- a nested
  `IndexHolder` class whose static initializer instantiates the
  build-time-generated `MappersIndex` / `BindersIndex` / `DaosIndex`.
  The JVM defers class init until the first field reference and runs
  it exactly once under the class-init monitor, so we get a race-free
  lazy singleton without `volatile`.
- ControlStatementBraces (11): every `if (cond) return X;` one-liner
  in Mappers.java and EntityManager.java now uses the full three-line
  brace form, per the CN1 PMD style gate (which interacts with
  Checkstyle's LeftCurly = eol option to require the explicit form).
- UnnecessaryConstructor (3): drop the explicit no-op
  `public XxxIndex() { }` constructors from the three cn1-core stub
  Index classes. The compiler-generated default public no-arg
  constructor takes their place; the build-time-shadowing class
  declares its own constructor body that does the actual
  `register(...)` work.

Also clean up the public surface: add `bootstrap()` static methods on
`Mappers`, `Binders`, and `EntityManager` so the per-build application
stub can call `Mappers.bootstrap()` etc. instead of forcing the
holder load through `new XxxIndex();`. Update
`Executor#annotationFrameworksInstallSource` to emit the new form.

mvn -pl codenameone-maven-plugin test -> 93/93 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: PMD UseUtilityClass on the nested IndexHolder classes

PMD's UseUtilityClass rule flagged the private `IndexHolder` nested
class inside each runtime registry: it has only a static field and
static methods, so PMD wants an explicit private constructor (or
abstract). Add one to each Holder. The body is intentionally empty
(plus a comment) -- the class is never instantiated; its sole job is
to expose `static final INDEX` and `static touch()` for lazy class
init.

mvn -pl codenameone-maven-plugin test -> 93/93 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: PMD UnnecessaryConstructor wants a non-empty body

PMD's `UseUtilityClass` rule fires when a class has only static
members and no constructor; it asks for `private` constructor or
abstract. Adding the private constructor (previous commit) then made
`UnnecessaryConstructor` fire because the body was empty (only a
comment).

Reconcile the two rules with the canonical "never instantiate"
pattern: `throw new AssertionError(...)` inside the private
constructor. The throw documents the contract and defends against
reflection-based instantiation; PMD's `UnnecessaryConstructor` is
satisfied because the body is now non-empty.

mvn -pl codenameone-maven-plugin test -> 93/93 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address review feedback: drop stubs, JavaBeans accessors, setter instrumentation, loop guard

Significant architecture revision based on PR review:

1. No more cn1-core stubs. The three com.codename1.{mapping,binding,orm}
   .generated.XxxIndex stubs are gone -- they pretended to be shadowed
   by target/classes, but cn1 has no shadowing mechanism. The generated
   bootstrap class now lives at cn1app.{Mapper,Binder,Dao}Bootstrap and
   is generated only when the project actually uses each annotation;
   cn1-core never references it directly.

2. Bootstrap is purely external. cn1-core's Mappers / Binders /
   EntityManager are plain registries (no IndexHolder, no
   ensureIndexLoaded, no `new XxxIndex()` ref). iOS and Android stubs
   get the install line spliced in by the build server only when the
   bootstrap class is in the project zip (per-feature probe).
   JavaSEPort.postInit Class.forName-loads each bootstrap (the
   legitimate classloading path -- JavaSE runs unobfuscated, mirrors
   the @route pattern).

3. String-keyed registries. Map<Class, ...> performs badly across
   platforms; switch every registry to Map<String, ...> keyed on
   getClass().getName(). Obfuscation renames the registration and
   lookup sites together within a single execution, and the keys are
   never persisted.

4. Per-class generated artifacts live alongside the source. The
   processor now emits com.example.UserCn1Mapper next to
   com.example.User (and ItemCn1Binder, OrderCn1Dao, ...). Each has a
   public static `register()` hook that the bootstrap class invokes.

5. @Bind gains getter() / setter() members; the processor resolves
   accessors in priority order: explicit override, JavaBeans
   get<F>/is<F>/set<F>, direct public-field access. Private fields
   with JavaBeans accessors work without making them public.

6. Setter instrumentation. For every two-way @Bind field whose write
   path is a method (explicit or detected), the processor reads the
   source class's .class file with ASM and inserts
   `ALOAD 0; INVOKESTATIC Binders.notifyChanged(Object)V` before every
   XRETURN. Mutating the model through the setter from anywhere
   triggers the binding fan-out automatically.

7. Two-way loop guard. Binders gains a thread-local update-depth
   counter (enterUpdate / exitUpdate / isInUpdate); every
   framework-initiated mutation runs inside an update region. The
   instrumented setter's notifyChanged is a no-op while the depth is
   positive, and component listeners short-circuit, so the
   model->component->model cycle terminates. New NotifiableBinding
   interface lets notifyChanged dispatch refreshes to the bindings
   that observe a given model.

   Documented limitation: a setter that synchronously mutates a
   *second* bound field won't propagate the second change to its
   component until something exits the region; the user must call
   binding.refresh() explicitly. Explained in
   Annotation-Component-Binding.asciidoc#annotation-binding-loop.

JavaSE port: postInit now loads cn1app.MapperBootstrap /
BinderBootstrap / DaoBootstrap via Class.forName, mirroring the
existing Routes block.

Build server (Executor.java in both this repo and BuildDaemon): the
per-feature `projectHasBootstrap` probe gates each install line so
projects that use only @mapped (no @bindable, no @entity) get just
the mapper bootstrap and nothing else.

Docs: all three pages rewritten to describe the new layout, the
JavaSE Class.forName / build-server probe split, the JavaBeans
accessor resolution order, the ASM setter instrumentation, and the
two-way loop-guard contract + cross-field-update limitation.

Tests: 84 -> 95 plugin tests (two new accessor-detection cases on
private-field binders + an ASM-level assertion that setUser carries
the injected `INVOKESTATIC Binders.notifyChanged`). The mapping and
ORM tests assert the new layout
(com.example.UserCn1Mapper + cn1app.MapperBootstrap vs the old
.generated paths).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: LanguageTool grammar/spelling nits in the three annotation chapters

Six LanguageTool matches across the new docs:

- 3x ATD_VERBS_TO_COLLOCATION on "Direct symbol references survive
  ParparVM rename..." -- LT reads "references" as a verb and expects
  a preposition. Rephrase as "ParparVM rename and R8 obfuscation
  rewrite the call site and the generated class together, so the
  direct symbol reference stays valid after the pass."
- 2x MORFOLOGIK_RULE_EN_US on "Classloading" -- not in LT's en_US
  dictionary. Hyphenate as "Class-loading" (also more correct).
- 1x UPPERCASE_SENTENCE_START on a paragraph that begins with the
  lowercase phrase "at every `return` point..." after a code listing.
  Rephrase to start the sentence with "The injection lands before
  every `return` point...".

vale Annotation-*.asciidoc -> 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: bump SpotBugs maxHeap so the plugin module's analysis doesn't OOM

The archetype-smoke job runs `mvn install -Pall` over the whole tree,
which invokes SpotBugs against codenameone-maven-plugin. After the
BindingAnnotationProcessor gained the ASM bytecode-instrumentation
pass, the plugin module crossed a size threshold and the
FindOpenStream dataflow detector exhausted the default 512MB heap on
CI ("GC overhead limit exceeded").

Set <maxHeap>1536</maxHeap> on the SpotBugs plugin configuration so
the forked JVM has room to finish. The effort/threshold settings stay
at Max/Low to preserve coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: missed lowercase "classloading" in the binding chapter

Last LT pass fixed two "Classloading" (capital C) occurrences in the
mapping and ORM chapters but missed the lowercase "classloading path"
prepositional phrase in the binding chapter. Hyphenate as
"class-loading" to match the dictionary form already used elsewhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix CI: two PMD violations in Binders introduced by the live-bindings registry

- ForLoopCanBeForeach at Binders.java:174 -- the snapshot iteration in
  notifyChanged was index-based; switch to foreach so PMD's
  ForLoopCanBeForeach (a forbidden rule) stops firing.
- CompareObjectsWithEquals at Binders.java:217 -- unregisterBinding
  removes the exact binding instance from the live list, so the
  identity comparison is intentional. Add the NOPMD suppression
  comment cn1-core already uses elsewhere (see MapComponent /
  CodenameOneImplementation) so the build gate stops failing on
  the deliberate `==` check.

mvn -pl codenameone-maven-plugin test -> 95/95 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog added a commit that referenced this pull request May 29, 2026
Final consolidated follow-up to the May 29 weekly index. Pulls
together six PRs that share the same architectural shape: emit Java
at build time, validate at build time, fail fast, and let R8 /
ParparVM rename the generated code together with the rest of the app.

- PR #5037: bytecode AnnotationProcessor SPI in the Maven plugin,
  the declarative router that is its first consumer (@route with
  guards, redirects, per-tab navigation shell, location listeners),
  the unified cold + warm DeepLink API, iOS Universal Links /
  Android App Links JSON generators, and the JavaScript-port
  window.history bridge.
- PR #5047: three more processors on the same SPI -- SQLite ORM
  (@entity / @id / @column), JSON / XML mapping (@mapped /
  @JsonProperty / @xmlelement), and component binding (@bindable /
  @Bind with the new BindAttr enum).
- PR #5062: validation annotations (@required, @Length, @regex,
  @Email, @url, @Numeric, @existin, @Validate) that compose with
  @Bind and surface through Binding.getValidator().
- PR #5055: the Immich-port baseline -- Map default methods,
  BiFunction, atomics, Rest.fetchAsJsonList / fetchAsMapped(List),
  URLImage.RequestDecorator / setDefaultBearerToken, JSONWriter,
  modern animated tab indicator + arc-spinner pull-to-refresh,
  MorphTransition.snapshotMode, WebSocket in core, and the new
  cn1:generate-openapi-client mojo.
- PR #5042: build-time SVG transcoder that lowers SVG (and SMIL
  animations) into Codename One Image subclasses via the shape API.
- PR #5066: Lottie / Bodymovin transcoder reusing the same
  SVGDocument model, JavaCodeGenerator, and SVGRegistry.
- PR #5049: the iOS Metal stencil-clip + drawString and Android
  LinearGradientPaint fixes the SVG screenshot tests exposed.

Calls out the Metal-only caveat on iOS for SVG / Lottie (the GL ES 2
path does not have the shape coverage) -- a non-issue on most apps
now that Metal is the default.
shai-almog added a commit that referenced this pull request May 29, 2026
Final consolidated follow-up to the May 29 weekly index. Pulls
together six PRs that share the same architectural shape: emit Java
at build time, validate at build time, fail fast, and let R8 /
ParparVM rename the generated code together with the rest of the app.

- PR #5037: bytecode AnnotationProcessor SPI in the Maven plugin,
  the declarative router that is its first consumer (@route with
  guards, redirects, per-tab navigation shell, location listeners),
  the unified cold + warm DeepLink API, iOS Universal Links /
  Android App Links JSON generators, and the JavaScript-port
  window.history bridge.
- PR #5047: three more processors on the same SPI -- SQLite ORM
  (@entity / @id / @column), JSON / XML mapping (@mapped /
  @JsonProperty / @xmlelement), and component binding (@bindable /
  @Bind with the new BindAttr enum).
- PR #5062: validation annotations (@required, @Length, @regex,
  @Email, @url, @Numeric, @existin, @Validate) that compose with
  @Bind and surface through Binding.getValidator().
- PR #5055: the Immich-port baseline -- Map default methods,
  BiFunction, atomics, Rest.fetchAsJsonList / fetchAsMapped(List),
  URLImage.RequestDecorator / setDefaultBearerToken, JSONWriter,
  modern animated tab indicator + arc-spinner pull-to-refresh,
  MorphTransition.snapshotMode, WebSocket in core, and the new
  cn1:generate-openapi-client mojo.
- PR #5042: build-time SVG transcoder that lowers SVG (and SMIL
  animations) into Codename One Image subclasses via the shape API.
- PR #5066: Lottie / Bodymovin transcoder reusing the same
  SVGDocument model, JavaCodeGenerator, and SVGRegistry.
- PR #5049: the iOS Metal stencil-clip + drawString and Android
  LinearGradientPaint fixes the SVG screenshot tests exposed.

Calls out the Metal-only caveat on iOS for SVG / Lottie (the GL ES 2
path does not have the shape coverage) -- a non-issue on most apps
now that Metal is the default.
shai-almog added a commit that referenced this pull request May 29, 2026
Final consolidated follow-up to the May 29 weekly index. Pulls
together six PRs that share the same architectural shape: emit Java
at build time, validate at build time, fail fast, and let R8 /
ParparVM rename the generated code together with the rest of the app.

- PR #5037: bytecode AnnotationProcessor SPI in the Maven plugin,
  the declarative router that is its first consumer (@route with
  guards, redirects, per-tab navigation shell, location listeners),
  the unified cold + warm DeepLink API, iOS Universal Links /
  Android App Links JSON generators, and the JavaScript-port
  window.history bridge.
- PR #5047: three more processors on the same SPI -- SQLite ORM
  (@entity / @id / @column), JSON / XML mapping (@mapped /
  @JsonProperty / @xmlelement), and component binding (@bindable /
  @Bind with the new BindAttr enum).
- PR #5062: validation annotations (@required, @Length, @regex,
  @Email, @url, @Numeric, @existin, @Validate) that compose with
  @Bind and surface through Binding.getValidator().
- PR #5055: the Immich-port baseline -- Map default methods,
  BiFunction, atomics, Rest.fetchAsJsonList / fetchAsMapped(List),
  URLImage.RequestDecorator / setDefaultBearerToken, JSONWriter,
  modern animated tab indicator + arc-spinner pull-to-refresh,
  MorphTransition.snapshotMode, WebSocket in core, and the new
  cn1:generate-openapi-client mojo.
- PR #5042: build-time SVG transcoder that lowers SVG (and SMIL
  animations) into Codename One Image subclasses via the shape API.
- PR #5066: Lottie / Bodymovin transcoder reusing the same
  SVGDocument model, JavaCodeGenerator, and SVGRegistry.
- PR #5049: the iOS Metal stencil-clip + drawString and Android
  LinearGradientPaint fixes the SVG screenshot tests exposed.

Calls out the Metal-only caveat on iOS for SVG / Lottie (the GL ES 2
path does not have the shape coverage) -- a non-issue on most apps
now that Metal is the default.
shai-almog added a commit that referenced this pull request May 29, 2026
Final consolidated follow-up to the May 29 weekly index. Pulls
together six PRs that share the same architectural shape: emit Java
at build time, validate at build time, fail fast, and let R8 /
ParparVM rename the generated code together with the rest of the app.

- PR #5037: bytecode AnnotationProcessor SPI in the Maven plugin,
  the declarative router that is its first consumer (@route with
  guards, redirects, per-tab navigation shell, location listeners),
  the unified cold + warm DeepLink API, iOS Universal Links /
  Android App Links JSON generators, and the JavaScript-port
  window.history bridge.
- PR #5047: three more processors on the same SPI -- SQLite ORM
  (@entity / @id / @column), JSON / XML mapping (@mapped /
  @JsonProperty / @xmlelement), and component binding (@bindable /
  @Bind with the new BindAttr enum).
- PR #5062: validation annotations (@required, @Length, @regex,
  @Email, @url, @Numeric, @existin, @Validate) that compose with
  @Bind and surface through Binding.getValidator().
- PR #5055: the Immich-port baseline -- Map default methods,
  BiFunction, atomics, Rest.fetchAsJsonList / fetchAsMapped(List),
  URLImage.RequestDecorator / setDefaultBearerToken, JSONWriter,
  modern animated tab indicator + arc-spinner pull-to-refresh,
  MorphTransition.snapshotMode, WebSocket in core, and the new
  cn1:generate-openapi-client mojo.
- PR #5042: build-time SVG transcoder that lowers SVG (and SMIL
  animations) into Codename One Image subclasses via the shape API.
- PR #5066: Lottie / Bodymovin transcoder reusing the same
  SVGDocument model, JavaCodeGenerator, and SVGRegistry.
- PR #5049: the iOS Metal stencil-clip + drawString and Android
  LinearGradientPaint fixes the SVG screenshot tests exposed.

Calls out the Metal-only caveat on iOS for SVG / Lottie (the GL ES 2
path does not have the shape coverage) -- a non-issue on most apps
now that Metal is the default.
shai-almog added a commit that referenced this pull request May 29, 2026
Final consolidated follow-up to the May 29 weekly index. Pulls
together six PRs that share the same architectural shape: emit Java
at build time, validate at build time, fail fast, and let R8 /
ParparVM rename the generated code together with the rest of the app.

- PR #5037: bytecode AnnotationProcessor SPI in the Maven plugin,
  the declarative router that is its first consumer (@route with
  guards, redirects, per-tab navigation shell, location listeners),
  the unified cold + warm DeepLink API, iOS Universal Links /
  Android App Links JSON generators, and the JavaScript-port
  window.history bridge.
- PR #5047: three more processors on the same SPI -- SQLite ORM
  (@entity / @id / @column), JSON / XML mapping (@mapped /
  @JsonProperty / @xmlelement), and component binding (@bindable /
  @Bind with the new BindAttr enum).
- PR #5062: validation annotations (@required, @Length, @regex,
  @Email, @url, @Numeric, @existin, @Validate) that compose with
  @Bind and surface through Binding.getValidator().
- PR #5055: the Immich-port baseline -- Map default methods,
  BiFunction, atomics, Rest.fetchAsJsonList / fetchAsMapped(List),
  URLImage.RequestDecorator / setDefaultBearerToken, JSONWriter,
  modern animated tab indicator + arc-spinner pull-to-refresh,
  MorphTransition.snapshotMode, WebSocket in core, and the new
  cn1:generate-openapi-client mojo.
- PR #5042: build-time SVG transcoder that lowers SVG (and SMIL
  animations) into Codename One Image subclasses via the shape API.
- PR #5066: Lottie / Bodymovin transcoder reusing the same
  SVGDocument model, JavaCodeGenerator, and SVGRegistry.
- PR #5049: the iOS Metal stencil-clip + drawString and Android
  LinearGradientPaint fixes the SVG screenshot tests exposed.

Calls out the Metal-only caveat on iOS for SVG / Lottie (the GL ES 2
path does not have the shape coverage) -- a non-issue on most apps
now that Metal is the default.
shai-almog added a commit that referenced this pull request May 29, 2026
Final consolidated follow-up to the May 29 weekly index. Pulls
together six PRs that share the same architectural shape: emit Java
at build time, validate at build time, fail fast, and let R8 /
ParparVM rename the generated code together with the rest of the app.

- PR #5037: bytecode AnnotationProcessor SPI in the Maven plugin,
  the declarative router that is its first consumer (@route with
  guards, redirects, per-tab navigation shell, location listeners),
  the unified cold + warm DeepLink API, iOS Universal Links /
  Android App Links JSON generators, and the JavaScript-port
  window.history bridge.
- PR #5047: three more processors on the same SPI -- SQLite ORM
  (@entity / @id / @column), JSON / XML mapping (@mapped /
  @JsonProperty / @xmlelement), and component binding (@bindable /
  @Bind with the new BindAttr enum).
- PR #5062: validation annotations (@required, @Length, @regex,
  @Email, @url, @Numeric, @existin, @Validate) that compose with
  @Bind and surface through Binding.getValidator().
- PR #5055: the Immich-port baseline -- Map default methods,
  BiFunction, atomics, Rest.fetchAsJsonList / fetchAsMapped(List),
  URLImage.RequestDecorator / setDefaultBearerToken, JSONWriter,
  modern animated tab indicator + arc-spinner pull-to-refresh,
  MorphTransition.snapshotMode, WebSocket in core, and the new
  cn1:generate-openapi-client mojo.
- PR #5042: build-time SVG transcoder that lowers SVG (and SMIL
  animations) into Codename One Image subclasses via the shape API.
- PR #5066: Lottie / Bodymovin transcoder reusing the same
  SVGDocument model, JavaCodeGenerator, and SVGRegistry.
- PR #5049: the iOS Metal stencil-clip + drawString and Android
  LinearGradientPaint fixes the SVG screenshot tests exposed.

Calls out the Metal-only caveat on iOS for SVG / Lottie (the GL ES 2
path does not have the shape coverage) -- a non-issue on most apps
now that Metal is the default.
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.

1 participant