Skip to content

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

Merged
shai-almog merged 12 commits into
masterfrom
pojo-annotation-frameworks
May 27, 2026
Merged

Build-time POJO annotation frameworks: JSON/XML mapping, component binding, SQLite ORM#5047
shai-almog merged 12 commits into
masterfrom
pojo-annotation-frameworks

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

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)

  • JSON / XML mapping@Mapped, @JsonProperty, @JsonIgnore, @XmlRoot, @XmlElement, @XmlAttribute, @XmlTransient.
  • Component binding@Bindable, @Bind (with the new BindAttr enum for TEXT / UIID / SELECTED / ...).
  • SQLite ORM@Entity, @Id, @Column, @DbTransient.

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

Runtime entry points

  • 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) plus Dao#createTable / #insert / #update / #delete / #findById / #findAll / #find(where, params)

cn1-core ships no-op stub classes for the three generated index types (MappersIndex, BindersIndex, DaosIndex) so application code compiles even when the project carries no annotated classes. The build-time processor shadows the stub with the real implementation under target/classes.

Build pipeline (maven/codenameone-maven-plugin)

  • PropertyTypeKind — shared field-type classifier (scalars, Property<T>, ListProperty<T>, List<T>, nested references). Pulls the first generic argument out of the class-file signature.
  • Three new processors under com.codename1.maven.processors (Mapping…, Binding…, Orm…), registered in META-INF/services/com.codename1.maven.annotations.AnnotationProcessor.
  • ClassScanner + FieldInfo now retain the field generic signature.

Build-server wiring

  • Executor#annotationFrameworksInstallSource emits three new com.codename1.{mapping,binding,orm}.generated.XxxIndex(); lines as a stub-source fragment.
  • IPhoneBuilder and AndroidGradleBuilder splice the fragment in right before the first Display.init, next to the existing @Route install line.
  • Mirror PR in the BuildDaemon repo: codenameone/BuildDaemon#89.

Tests

84 → 93 plugin tests green. Each new processor has end-to-end coverage (compile fixture with JavaSourceCompiler, run processor, load generated bytecode in a child classloader and exercise it) plus negative cases:

  • MappingAnnotationProcessorTest — POJO round-trip, PropertyBusinessObject round-trip, abstract / no-arg-constructor rejection.
  • BindingAnnotationProcessorTest — ASM shape check, private-field rejection.
  • OrmAnnotationProcessorTest — ASM shape check, missing-@Id and relationship-field rejection.

Docs

  • docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc
  • docs/developer-guide/Annotation-Component-Binding.asciidoc
  • docs/developer-guide/Annotation-SQLite-ORM.asciidoc

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

Test plan

  • mvn install -pl core -am -DskipTests -Dmaven.javadoc.skip=true (cn1-core builds against the new runtime classes + stub index classes)
  • mvn -pl maven/codenameone-maven-plugin test (93/93 plugin tests, including 9 new ones)
  • Run a sample app with one @Mapped POJO + one @Bindable form-model + one @Entity and verify the round-trips work in the JavaSE simulator
  • Run the same sample on iOS (ParparVM) and Android (R8) and confirm the generated index classes survive rename / obfuscation
  • Run asciidoctor on docs/developer-guide/developer-guide.asciidoc and confirm the three new pages render

🤖 Generated with Claude Code

…nding, 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>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 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)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 27, 2026

Compared 24 screenshots: 24 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 27, 2026

Compared 116 screenshots: 116 matched.

Native Android coverage

  • 📊 Line coverage: 12.36% (7161/57934 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.10% (36071/357185), branch 4.25% (1438/33832), complexity 5.26% (1709/32478), method 9.18% (1396/15204), class 15.00% (317/2114)
    • 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: 12.36% (7161/57934 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.10% (36071/357185), branch 4.25% (1438/33832), complexity 5.26% (1709/32478), method 9.18% (1396/15204), class 15.00% (317/2114)
    • 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 1299.000 ms
Base64 CN1 encode 190.000 ms
Base64 encode ratio (CN1/native) 0.146x (85.4% faster)
Base64 native decode 1002.000 ms
Base64 CN1 decode 276.000 ms
Base64 decode ratio (CN1/native) 0.275x (72.5% faster)
Image encode benchmark status skipped (SIMD unsupported)

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>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 27, 2026

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 61000 ms
Simulator Boot (Run) 0 ms
App Install 14000 ms
App Launch 14000 ms
Test Execution 289000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 554.000 ms
Base64 CN1 encode 1357.000 ms
Base64 encode ratio (CN1/native) 2.449x (144.9% slower)
Base64 native decode 274.000 ms
Base64 CN1 decode 944.000 ms
Base64 decode ratio (CN1/native) 3.445x (244.5% slower)
Base64 SIMD encode 420.000 ms
Base64 encode ratio (SIMD/native) 0.758x (24.2% faster)
Base64 encode ratio (SIMD/CN1) 0.310x (69.0% faster)
Base64 SIMD decode 466.000 ms
Base64 decode ratio (SIMD/native) 1.701x (70.1% slower)
Base64 decode ratio (SIMD/CN1) 0.494x (50.6% 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) 141.000 ms
Image applyMask (SIMD on) 61.000 ms
Image applyMask ratio (SIMD on/off) 0.433x (56.7% faster)
Image modifyAlpha (SIMD off) 114.000 ms
Image modifyAlpha (SIMD on) 56.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.491x (50.9% faster)
Image modifyAlpha removeColor (SIMD off) 146.000 ms
Image modifyAlpha removeColor (SIMD on) 84.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.575x (42.5% faster)
Image PNG encode (SIMD off) 1104.000 ms
Image PNG encode (SIMD on) 902.000 ms
Image PNG encode ratio (SIMD on/off) 0.817x (18.3% faster)
Image JPEG encode 410.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 27, 2026

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 103000 ms
Simulator Boot (Run) 4000 ms
App Install 18000 ms
App Launch 9000 ms
Test Execution 391000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 866.000 ms
Base64 CN1 encode 1724.000 ms
Base64 encode ratio (CN1/native) 1.991x (99.1% slower)
Base64 native decode 456.000 ms
Base64 CN1 decode 1354.000 ms
Base64 decode ratio (CN1/native) 2.969x (196.9% slower)
Base64 SIMD encode 544.000 ms
Base64 encode ratio (SIMD/native) 0.628x (37.2% faster)
Base64 encode ratio (SIMD/CN1) 0.316x (68.4% faster)
Base64 SIMD decode 572.000 ms
Base64 decode ratio (SIMD/native) 1.254x (25.4% slower)
Base64 decode ratio (SIMD/CN1) 0.422x (57.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 81.000 ms
Image createMask (SIMD on) 12.000 ms
Image createMask ratio (SIMD on/off) 0.148x (85.2% faster)
Image applyMask (SIMD off) 191.000 ms
Image applyMask (SIMD on) 73.000 ms
Image applyMask ratio (SIMD on/off) 0.382x (61.8% faster)
Image modifyAlpha (SIMD off) 187.000 ms
Image modifyAlpha (SIMD on) 87.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.465x (53.5% faster)
Image modifyAlpha removeColor (SIMD off) 199.000 ms
Image modifyAlpha removeColor (SIMD on) 89.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.447x (55.3% faster)
Image PNG encode (SIMD off) 1577.000 ms
Image PNG encode (SIMD on) 1280.000 ms
Image PNG encode ratio (SIMD on/off) 0.812x (18.8% faster)
Image JPEG encode 769.000 ms

shai-almog and others added 5 commits May 27, 2026 06:33
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>
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>
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>
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>
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>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [HTML preview] [Download]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 1 findings (Normal: 1)
    • 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 2 commits May 27, 2026 13:30
…rumentation, 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>
…apters

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>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 27, 2026

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

shai-almog and others added 3 commits May 27, 2026 14:03
… 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>
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>
… 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>
@shai-almog shai-almog merged commit 671a98e into master May 27, 2026
31 checks passed
shai-almog added a commit that referenced this pull request May 29, 2026
* Improvements driven by the Immich Flutter port comparison

A coherent set of framework, build-tool, and theme additions surfaced
by porting Immich's Flutter client to Codename One. Each item was driven
by a concrete pain point in that port; the Immich port is the
regression fixture for the cn1:compliance-check + Rest builder + typed
binding paths.

Java API subset (Ports/CLDC11 + vm/JavaAPI):
- Map: 11 Java 8 default methods (getOrDefault, putIfAbsent,
  remove(K,V), replace(K,V) / replace(K,V,V), forEach, replaceAll,
  computeIfAbsent, computeIfPresent, compute, merge).
- BiFunction added to java.util.function (was missing).
- Iterable.forEach(Consumer), Collection.removeIf(Predicate),
  List.replaceAll(UnaryOperator), List.sort(Comparator).
- All four primitive atomics: AtomicReference (already in vm/JavaAPI,
  mirrored to CLDC11), AtomicInteger, AtomicLong, AtomicBoolean.

Core framework:
- Rest.fetchAsJsonList -- top-level JSON arrays unwrap automatically
  rather than forcing callers through the {"root":[...]} envelope trick.
- Rest.fetchAsMapped(Class<T>) / fetchAsMappedList(Class<T>) -- typed
  POJO responses via the @mapped binding framework merged in PR #5047.
- Component.setPullToRefresh(Runnable) alias for addPullToRefresh
  (more discoverable; same single-task slot).
- URLImage.RequestDecorator interface + setDefaultRequestDecorator +
  setDefaultBearerToken + per-call decorator overload on
  createToStorage. Eliminates the "URLImage doesn't pass headers"
  workaround for authenticated image endpoints.
- JSONWriter -- mirror of JSONParser. One-shot toJson(Object) plus
  fluent JSONWriter.object().put(...).toJson() / JSONWriter.array()
  builders. Streaming variants for writer / OutputStream.
- Tabs.setAnimatedIndicator(boolean) -- Material 3 NavigationBar /
  iOS 26 sliding-underline indicator. Gated by tabsAnimatedIndicatorBool
  theme constant + duration/thickness constants + TabIndicator UIID
  color. Off by default in framework, on by default in modern native
  themes.
- MorphTransition.snapshotMode(boolean) -- opt-in image-snapshot path
  that captures source/dest as clipped Images at initTransition() and
  tweens those rather than re-painting the live components. Solves the
  source-inside-scrolling-container off-viewport-leak case. Default
  (live-paint) path unchanged.
- DefaultLookAndFeel.drawModernPullToRefresh -- Material 3 arc-spinner
  pull-to-refresh painted directly via Graphics.drawArc. Sweep grows
  0deg -> 330deg as the user pulls, then spins continuously while the
  refresh task runs. Gated by pullToRefreshModernBool theme constant;
  on by default in modern themes.
- com.codename1.io.websocket package -- WebSocket Java API moved from
  the cn1-websockets cn1lib into core. Per-platform native impls
  remain in the cn1lib repo for a follow-up.

Maven plugin:
- StubGenerator.isValidType accepts String[] (was rejected). All
  primitive arrays were already accepted.
- New cn1:generate-openapi-client mojo -- reads an OpenAPI 3.x JSON
  spec (URL or local file), emits one @mapped POJO per
  components.schemas entry and one <Tag>Api.java per tag. Each
  generated method routes through Rest.<verb> + Mappers.toJson +
  fetchAsMapped / fetchAsMappedList. Tested end-to-end against the
  Swagger Petstore reference spec: 6 models + 3 Api classes generated,
  compiles cleanly against codenameone-core. Four JUnit tests in
  OpenApiCodegenTest cover inline-spec generation, identifier /
  class-name sanitisation, and a real-Petstore round-trip.
- codenameone-core moved from test to compile scope in the plugin
  pom so the codegen can use com.codename1.io.JSONParser for spec
  parsing.

Native themes:
- iOS Modern + Android Material both enable tabsAnimatedIndicatorBool,
  pullToRefreshModernBool, plus the supporting duration / thickness /
  diameter constants. New TabIndicator UIID with light + dark variants
  pinned to --accent-color / --accent-color-dark. Binary
  Themes/*.res files regenerated via scripts/build-native-themes.sh.

Tests (scripts/hellocodenameone):
- MorphTransitionTest -- baseline live-paint morph.
- MorphTransitionScrolledSourceTest -- source in a scrolling list
  (the case snapshotMode is designed to solve).
- MorphTransitionSnapshotTest -- snapshot-mode baseline.
- All three registered in Cn1ssDeviceRunner.DEFAULT_TEST_CLASSES and
  the HTML5 skip list.

Documentation:
- docs/developer-guide/io.asciidoc -- new sections for
  fetchAsJsonList / fetchAsMapped / fetchAsMappedList, JSONWriter,
  WebSocket.
- docs/developer-guide/graphics.asciidoc -- URLImage RequestDecorator
  + setDefaultBearerToken.
- docs/developer-guide/Animations.asciidoc -- MorphTransition
  snapshotMode.
- docs/developer-guide/The-Components-Of-Codename-One.asciidoc --
  animated tab indicator under the Tabs section.
- docs/developer-guide/Miscellaneous-Features.asciidoc -- modern
  arc-spinner pull-to-refresh under the existing Pull to refresh
  section.
- docs/developer-guide/Native-Themes.asciidoc -- the six new theme
  constants (3 tabs, 3 pull-to-refresh) added to the tuning-constants
  table.
- docs/developer-guide/appendix_goal_generate_openapi_client.adoc --
  new Maven goal documentation page.
- docs/developer-guide/Maven-Appendix-Goals.adoc -- includes the new
  goal in the appendix.

Skill (scripts/initializr/.../skill/):
- references/java-api-subset.md -- fetchAsMapped section,
  URLImage.RequestDecorator section, JSONWriter section, OidcClient
  section, WebSocket-in-core mention, refreshed subset gotchas.
- references/ui-components.md -- MorphTransition.snapshotMode,
  Tabs animated indicator, modern pull-to-refresh, package-split
  table for components / spinner / ui.
- references/build-and-run.md -- Hot Reload modes,
  cn1:generate-openapi-client invocation.
- references/native-interfaces.md -- corrected allowed-types list
  to include String[] and primitive arrays; iOS NSData* marshal
  caveat.

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

* Address review feedback: WebSocketImpl factory + CLDC11 stub bodies

Two corrections to the previous round, both PR feedback:

1. WebSocket: replace WebSocketNativeImpl (NativeInterface pattern) with
   WebSocketImpl + createWebSocketImpl factory on
   CodenameOneImplementation. The Java API still lives under
   com.codename1.io.websocket but each per-platform port now subclasses
   WebSocketImpl directly and returns it from createWebSocketImpl --
   same pattern Media / Storage / LocationManager use. The
   WebSocketImpl talks directly to the native WebSocket API (no
   NativeInterface marshaling, no static-callback string-id
   round-trip). Util gets a narrow Util.createWebSocketImpl(parent)
   delegate so WebSocket (in a sub-package) can reach the impl
   accessor; pattern matches Util.secureRandomBytes.

2. CLDC11 contains stubs, not implementations. AtomicReference /
   AtomicInteger / AtomicLong / AtomicBoolean now have empty stub
   bodies (return 0 / null / false). The Java 8 default methods on
   Map / Collection / List / Iterable also drop their real
   implementations -- stubbed bodies returning null / false / no-op.
   Actual runtime behaviour comes from the platform (Android JDK on
   Android, vm/JavaAPI on ParparVM, the host JDK in the JavaSE
   simulator); CLDC11 java-runtime.jar is only used by the
   compile-time compliance check.

Also fixes the validate-java25-markdown-docs.sh CI step which rejected
the classic /** Javadoc markers introduced in the new atomic classes
-- all four are now /// markdown comments.

Verified: full mvn install green, errorprone clean,
validate-java25-markdown-docs passes, the immich-cn1-port external
fixture still passes cn1:compliance-check.

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

* Fix CI gates: SpotBugs nulls, Vale US-English, asciidoc parse errors

- SpotBugs: drop redundant null checks where getComponentStyle() /
  getThemeConstant() return non-null by contract (Tabs, DefaultLookAndFeel).
  Guard the snapshot-mode draw path against an uninitialised
  fromToComponents array even though the field is populated by
  initTransition() before paint() is ever called -- SpotBugs is
  flow-insensitive and complains otherwise.
- Vale: US-English spellings (behaviour -> behavior, colour -> color,
  serialisation -> serialization, etc.) across the new doc sections.
  Remove a "cannot" -> "can't" and two "e.g." -> "for example".
  Rephrase one parenthetical to drop a stray first-person "I" that
  vale was flagging on a downstream line via cumulative column
  tracking. Drop a couple of unnecessary hyphens
  ("currently-selected" -> "currently selected" etc).
- Asciidoctor: replace HTML entity &deg; with the literal degree
  symbol (asciidoctor doesn't recognise &deg; and errors out parsing
  the formatted-text run).
- LanguageTool: add "unobfuscated" to the accept list (existing prose
  on master uses the word but no LT run had previously been triggered
  there since the term was introduced).

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

* Fix CI: PMD (drop FQN) + LanguageTool accept-list additions

- URLImage:895 -- drop the redundant
  com.codename1.util.FailureCallback fully-qualified reference; the
  short form FailureCallback is already imported at the top of the
  file. PMD's UnnecessaryFullyQualifiedName rule.
- LanguageTool accept-list -- add "Petstore", "Swagger", "Api", and
  the "tween" family ("tween", "tweens", "tweened", "tweening").
  All four are technical terms LanguageTool's English dictionary
  doesn't recognise but that the new docs use as prose ("the indicator
  tweens its x / width", "the Swagger Petstore reference spec",
  "Each generated Api method").

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

* Fix PMD FQN violations, doc gate typo, and add Android screenshot baselines

PMD: shorten redundant FQNs in RequestBuilder/URLImage/MorphTransition.
Docs: replace ambiguous hyphenated "class-loading" wording flagged by
LanguageTool with "classloader" in two Annotation-* chapters.
Android baselines: capture MorphTransition{,Scrolled,Snapshot}Test PNGs
extracted from CI logs; refresh TabsTheme_dark/light for the animated
indicator visual flag.

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

* PMD OneDeclarationPerLine + LanguageTool tweens fix

Split combined int declarations in Tabs.java and reword "tweens its"
in the Tabs animated-indicator chapter to "animates its" so it no
longer trips MORFOLOGIK_RULE_EN_US.

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

* PMD ControlStatementBraces fix in JSONWriter.unwrap

Brace the two type-test branches that returned without curly braces.

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

* PMD ControlStatementBraces: brace inline bodies in JSONWriter

Wrap the single-statement bodies of two if checks and one for loop
that PMD flagged in writeJson and writeString.

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

* PMD ControlStatementBraces: brace MorphTransition.paintMorph skip check

Wrap the early-continue body where MorphTransition skips zero-area
morph entries; satisfies PMD ControlStatementBraces.

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

* PMD ControlStatementBraces: brace inline returns in DefaultLookAndFeel

Wrap the inline `if (f > 0) return f;` bodies in
modernIndicatorDiameterMm and modernIndicatorStrokeMm so they pass
PMD ControlStatementBraces.

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

* PMD UnusedFormalParameter: drop unused host arg from modernSpinnerRepaintAnimation

The Form host parameter was never read inside the Animation closure --
the repaint runs against `cmp` directly. Drop the parameter and the
call site arg.

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

* Checkstyle LeftCurly: expand JSONWriter one-line method bodies

Break the inline `{ return ... }` bodies in ObjectBuilder/ArrayBuilder
across multiple lines so they satisfy the LeftCurlyCheck rule.

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

* Add MorphTransition iOS goldens (regular + metal)

Baselines captured from the green Test iOS UI build scripts run on
this branch (artifacts ios-ui-tests / ios-ui-tests-metal). The
screenshots show the expected source → destination interpolation
across six animation frames.

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

* Reuse build-port cache key in input-validation (avoid hash divergence)

The Input validation gesture suite was failing on cache-miss every
run because its src_hash differs from build-port's even on the same
SHA. Mirror the workaround already in scripts-ios.yml by reusing
needs.build-port.outputs.cn1_built_cache_key instead of recomputing
the hash here.

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

* Revert WebSocket-in-core: no port has a native impl yet

The com.codename1.io.websocket package landed without iOS, Android,
JavaSE, or JavaScript port implementations -- isSupported() returned
false everywhere, leaving callers stranded. Drop the public API
(WebSocket, WebSocketImpl, WebSocketState), the framework factory
hooks (CodenameOneImplementation.createWebSocketImpl, Util's
delegate), and the developer-guide + initializr skill sections that
documented the half-wired contract. The existing cn1-websockets
cn1lib remains the supported transport until a fully-ported in-core
replacement is ready.

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

* Drop Flutter refs; AnimationTime for spinner; new Tabs/Pull-to-refresh tests; trim skill

- Strip the "guess from Flutter" alias rationale on Component.setPullToRefresh
  and the Flutter "hot reload" comparison in the build-and-run skill ref.
- DefaultLookAndFeel.modernSpinStartTime now reads AnimationTime.now() so
  screenshot tests can drive the rotation deterministically.
- Add TabsAnimatedIndicatorScreenshotTest and PullToRefreshSpinnerScreenshotTest
  under scripts/hellocodenameone -- both extend AbstractAnimationScreenshotTest
  so the 2x3 grid harness produces a baseline image of each animation phase.
- Compress the Tabs animated-indicator and modern pull-to-refresh sections
  in ui-components.md to a single short paragraph; the LLM doesn't need the
  per-constant table when both features are on by default in the modern
  themes. Same trim for MorphTransition.snapshotMode.

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

* OpenAPI redesign phase 1: drop old codegen, add new annotation surface

- Remove OpenApiCodegen.java, GenerateOpenApiClientMojo.java,
  OpenApiCodegenTest.java and the matching developer-guide appendix.
- Add com.codename1.annotations.rest.{RestClient, GET, POST, PUT,
  DELETE, PATCH, Path, Query, Header, Body} -- the contract the new
  cn1:generate-openapi mojo emits onto each interface method.
- Add com.codename1.io.rest.RestClients -- the runtime registry the
  generated cn1app.RestClientBootstrap fills in; each interface's
  static of(baseUrl) factory routes through it so the generated impl
  class never appears in project source.
- New appendix_goal_generate_openapi.adoc describes the redesign;
  skill references point at cn1:generate-openapi instead of the
  removed generate-openapi-client goal.

Build-time processor + mojo + records-support follow in subsequent
phases on this branch.

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

* OpenAPI redesign phase 4: Java records support for @mapped

AnnotatedClass.isRecord() reads the ACC_RECORD bit from the class
file's access flags (inlined constant for ASM-version safety).

MappingAnnotationProcessor:
  - For records, skip the concrete-class / public-no-arg-ctor checks
    and walk non-public component fields (record components are
    private final but carry component names).
  - Reject Property / ListProperty components on records -- they imply
    mutation and records are immutable.
  - emit* helpers thread `isRecord` so reads go through `o.name()`
    accessors and writes target local variables `_name` instead of
    `o.name` field assignment.
  - For records, `fromMap` and `readXml` declare per-component locals
    with kind-appropriate defaults, populate them, and return via the
    canonical constructor `new T(_a, _b, ...)` in bytecode declaration
    order (= canonical-ctor parameter order).

RecordMappingTest exercises a Pet record end-to-end on JDK 17;
skipped on JDK 8. POJO regression tests remain green.

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

* OpenAPI redesign phases 2 + 3: REST-client processor + generate-openapi mojo

RestClientAnnotationProcessor (sibling to MappingAnnotationProcessor):
  - Scans `@RestClient`-annotated interfaces.
  - Per method, reads the one HTTP-verb annotation (@GET/@POST/@PUT/
    @DELETE/@patch) plus the parameter-level @Path/@Query/@Header/@Body
    annotations now exposed by ClassScanner.
  - Resolves the response payload type from the trailing
    `OnComplete<Response<T>>` generic signature -- `List<X>` routes
    through `fetchAsMappedList`, `String` through `fetchAsString`,
    everything else through `fetchAsMapped(T.class, ...)`.
  - Emits `<Tag>ApiImpl` (final class implementing the user interface)
    plus a sibling `cn1app.RestClientBootstrap` that registers each
    impl with `RestClients` -- the build-server splice pattern
    `MapperBootstrap` already uses applies here unchanged.

GenerateOpenApiMojo (`cn1:generate-openapi`):
  - Inputs `-Dcn1.openapi.spec=<path|url>` and
    `-Dcn1.openapi.basePackage=<pkg>`. Optional outputDirectory /
    overwrite knobs.
  - Detects `maven.compiler.release` / `target`. >= 17 emits Java
    records, otherwise classes. The `@Mapped` processor already
    handles both shapes.
  - Schema unification: identical property shapes collapse to a
    single record/class.
  - Per OpenAPI tag emits one `@RestClient`-annotated interface
    under <basePackage> with a static `of(String baseUrl)` factory
    that delegates to `RestClients.create(...)`.

Supporting changes:
  - ClassScanner / MethodInfo now capture per-parameter annotations
    and the method's generic signature.
  - META-INF service registration adds the new processor.
  - skill ref + appendix updated to the actual mojo flag form.

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

* Docs gate: fix appendix table syntax + Vale/LanguageTool nits

Asciidoctor parsed `<path|url>` as an in-table cell delimiter and
dropped the surrounding row. Switch to angle-bracket-free PATH /
PKG / DIR placeholders matching other appendices.

Vale: "is not" -> "isn't", "fully-qualified" -> "fully qualified".
LanguageTool: replace "bookkeep imports" with "track imports".

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

* SpotBugs: collapse duplicate switch arms in MappingAnnotationProcessor.fieldType

REFERENCE / PROPERTY / LIST_PROPERTY all fall back to the binaryName
shape; SpotBugs DB_DUPLICATE_SWITCH_CLAUSES wants them folded into a
single case label.

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

* Wire TabsAnimatedIndicator + PullToRefreshSpinner into the device runner

The runner registers each screenshot test explicitly (no auto-scan);
the new animation tests need both a `new XScreenshotTest()` line in
the suite array and an entry in `isJsSkippedAnimationTest` (matching
how Morph* are already gated -- JS chunk-emission is unreliable for
animation grids today).

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

* SpotBugs: drop dead ternary + unused sign tracker in splitTopLevelArgs

DB_DUPLICATE_BRANCHES on the substring offset: both arms of
`sign > 0 ? 0 : 0` evaluated to the same value. `start` already
points at the sign character so the substring offset is just
`start..i`. The `sign` local was load-bearing for nothing else, so
drop it too.

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

* @cookie binding: emit + process cookie parameters end-to-end

- Add com.codename1.annotations.rest.Cookie parameter annotation.
- RestClientAnnotationProcessor recognises @cookie, collapses any
  @cookie params on a method into a single `Cookie: a=1; b=2` header
  with URL-encoded values.
- GenerateOpenApiMojo emits @cookie for OpenAPI `in: cookie`
  parameters instead of dropping them.
- Appendix loses the apologetic "Scope (MVP)" framing and the
  "tracked as follow-ups" trailer; the supported-feature list is
  now declarative.

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

* JavaBeans accessor support for @mapped POJOs

Private fields with a matching public `getX()` (or `isX()` for
booleans) plus `setX(FieldType)` are now first-class @mapped
targets. Property / ListProperty fields only need the getter --
the field itself isn't replaced, only its inner value mutated.

MappingAnnotationProcessor:
  - Per-field strategy stored as `useAccessor` + `getterName` +
    `setterName` on MappedField; `findGetter` / `findSetter` probe
    the declared methods on the class.
  - The `readExpr` / `writeStmt` helpers picks between record-
    accessor / bean-getter / public-field at each emit site so the
    record, public-field, and accessor-POJO paths converge.
  - `URL` -> `getURL` matches `java.beans.Introspector` (literal
    first, lowercased variant `getUrl` as a fallback).

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

* PullToRefreshSpinnerScreenshotTest: install modern theme flag

DefaultLookAndFeel only takes the arc-spinner path when
`pullToRefreshModernBool` is on. The bare framework theme defaults
to off, so the previous render fell through to the legacy rotating
arrow and the new Android baseline showed nothing modern. Overlay
the constant via addThemeProps -- same mechanism the iOS Modern /
Android Material native themes use -- and restore the theme on
finishCapture so subsequent suite tests aren't affected.

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

* CI: switch JDK distribution from Zulu to Temurin

actions/setup-java@v4 has been hitting Cloudflare 520 on Zulu's
mirror repeatedly today (Build Android JDK 21 + javascript-screenshots
keep failing at the Setup JDK step). Adoptium Temurin is hosted on
GitHub's own infrastructure and matches the OpenJDK reference
distribution, so it's both more reliable and more conventional.

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

* Add TabsAnimatedIndicator + PullToRefreshSpinner goldens

Six baselines captured from the green Android (JDK 8/17/21) and
iOS (regular + metal) screenshot runs:

  - Tabs frames show the indicator tweening from tab 0 to tab 2
    via Motion.createEaseInOutMotion driven by AnimationTime.
  - PullToRefresh frames show the modern arc spinner rotating;
    the test overlays pullToRefreshModernBool=true via addThemeProps
    so DefaultLookAndFeel takes the arc path even when the
    framework default theme is loaded.

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

* Modern pull-to-refresh: Material-spec white disc + drop shadow

DefaultLookAndFeel.drawModernPullToRefresh now paints a white
circular backdrop sized to the arc radius + stroke padding, with a
soft three-layer translucent shadow offset down-right, before the
arc itself. The arc was invisible against same-colour backdrops
before; the disc lifts it off any surface the way the Material 3
RefreshIndicator does.

Test fix: PullToRefreshSpinnerScreenshotTest was double-painting the
indicator -- once via the host form's normal paint chain (which
positions it after the title bar offset) and again with an explicit
drawPullToRefresh call (which used the un-translated origin). Drop
the second call; the host paint already renders the modern arc
because the container carries `$pullToRelease=updating`.

The three PNG baselines (Android + iOS regular + iOS metal) are
removed so CI re-captures them with the new rendering.

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

* PullToRefresh goldens: re-capture with disc backdrop + single paint

Android baseline now clearly shows the Material 3 white disc with
soft shadow under the rotating arc. The iOS variants render the
arc more lightly (no visible status bar in the test form), but each
cell has the expected single indicator -- no more double-paint over
the title and content list.

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.
shai-almog added a commit that referenced this pull request May 30, 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 30, 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