MO&qKpkL9iY<%3e
zFXe?@O{9gEVcK_h#>bl*rmK|=V=X%#@t%O@k
z$H&AFskL^AFU4qZsKYee!kNbuo1(4tPyr
zbPBxgI^xig^_ri>)1Hy47iLOHQiDhkbyi<+=Sk!2`}*9UR_%`XyE-j@?1p`c_m^*N
ze0$VuX9E3Ta3e`we7MSfAYZasY_
zyjvh1xaX4CZ8Q284w(xyCtR-4>6Qh^Z-n1vmr
z5W(8X2@83%hzpA_93(SR<>hs3*Qzf=f3cma1js}srdF9?2yOu~7cj(T{#?Vih7@rE
z!dccA;p~iU#Mw|n4*!Av$+KNaEDRZt(g;D>QB9Tmj_A|AYDewE8AX{Hi{>^d}CI|91QQXzuKR!Ki=)`y>ZvbVAts8cj2}!YW=C8iJp3xGeV&$1g}jTb?vaVxz9iKR@|0yUrwD6Kc#?_8s@tjXrk6keg
z&=Zcj&5&EhSk10+QDfI9r}r#u_dRs=hv$#Gg_$2^Dx=aJE-pBooh}TBKBB4jTAX_|
z(ZfD>|1pW1wBW2XWXo?Q?gb?p#2y`0%!cty8oaMIRl9{`3BeE3ie4_O6hR9!BJF~0
zqFhsh_>n(H<^70{!1ASQxZ4vFNfDOG0!RUp$)XIu&gkQjpJ({v97f7)<-$E&3ppZ-
zIGu<9=&KZqc|~T?=6?7@XTa9GRW0f_1Eq`aS0((RHz71~DGX!A9#NpVe6u#b@=>|n
zV%#{KHLbOzDnTmK%(TJfADxy9i{oRRuQ^(yoPj!}`I^ERX2Dt{aj^R3mrOXPYI(B|
z+rb9_21vT0KmYaZ)d(F^c7;cgQBkl*Fl4|8v$7oosx{H_Bw(b^wavi>z=5&gnS2Sl
z$as6WCk?){X_*8MJ^+nCRh@a1rc~Nrkvd!EbP5mzcny_>n9i>}W#JYzA)%@=SHJ9a
z?t!9hGiL_cgQu_!br)rp6*8&+A#98Phe+ju$Bd}ke&{ZS(5MqTB!s;uh3
zC^Niao2O10z?Oq&3nkI3-QA1Q`03X~fnHg1VW4}Vr`hluU#YqoBt!b-h)&3vKu!QH
zN-#)9nVLYRK|;vXiS?6*5FrUQGV0JPya3{UWhF1h>@!Rw^9jIXcmuqR+9@Xp#gyzt
zld*W7nFcNmUP><}A7e5nrg3$6TRz2<_QFOuV^=cACbQg{)saGyR<7&Kd+1wr3RJVo
zqBO%urT%Graxq&qHU?xyh+-2Ysxm8CClV2FNL1Gl$N&@OgxN4rGB{Z*G1-#!Xo#t@
z6biOvVD;pPcPKOq+>D7NMMT65TVvZ=)&}I&)iSYSSs|3F^pf=>r8B*0IubLJQBC!~
zLEO*-!{HdhhoW15{M;=RL1y_;jKo&9TXm+~GDV}^zCR*=%~8Z(U>8_UGf9FI+dKi`
z!D14}Nt%kmgj`XR4=y%oI>Eywl_*mj0-ct`rs&9nWXjGgOkVhMl408O$uTv~8Atzx
z@onRPN5iH}#R)ej1jt`NCX_8GuLTn=U(;ohQ)3h&WBMvbamxgVHwj
zoLMmT!p4FxQD+Fsb9;&tr?^ISw#z21D#moNH8zCH7-ULaz?_j}8K#>T3(BnpqtlDq
F{{hGnCrAJQ
literal 0
HcmV?d00001
From 9028daa27b092744fe487d54482664a6e633af86 Mon Sep 17 00:00:00 2001
From: Shai Almog <67850168+shai-almog@users.noreply.github.com>
Date: Fri, 29 May 2026 18:54:00 +0300
Subject: [PATCH 4/8] Build-time-codegen post: quote YAML title to dodge colon
parse error
The post failed CI with:
error building site: assemble: failed to create page from pageMetaSource
/blog/build-time-codegen: ...build-time-codegen.md:2:8":
[1:8] mapping value is not allowed in this context
The title contains an unquoted colon ("Build-Time Codegen: Router,
ORM, Mappers, Binder, SVG / Lottie"), which YAML reads as a key /
value separator at column 8 and trips the parser. Wrapping the
value in double quotes makes it a plain string. Verified locally
with `hugo --buildFuture`: the post renders and the rendered
shows the colon intact.
---
docs/website/content/blog/build-time-codegen.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md
index 6ebc30e9a9..3185a73469 100644
--- a/docs/website/content/blog/build-time-codegen.md
+++ b/docs/website/content/blog/build-time-codegen.md
@@ -1,5 +1,5 @@
---
-title: Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie
+title: "Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie"
slug: build-time-codegen
url: /blog/build-time-codegen/
date: '2026-06-03'
From dd6e13a16be30f1fb6781aec664f9cdf63d0d623 Mon Sep 17 00:00:00 2001
From: Shai Almog <67850168+shai-almog@users.noreply.github.com>
Date: Fri, 29 May 2026 21:24:36 +0300
Subject: [PATCH 5/8] Build-time-codegen post: major restructure per review
- Retitled "Routing, ORM, OpenAPI, And Build-Time SVG / Lottie".
The headline pieces (router, ORM, OpenAPI, SVG, Lottie) lead;
the codegen plumbing is the "How it works" section at the end.
- Front-matter description rewritten around the headline pieces and
the JPA / JAXB familiarity callouts.
- Routing section now leads with the deep-link motivation: what
problem deep links create (URLs arriving from many sources), why
a single Display.setDeepLinkHandler scales badly, and how
@Route(...) collapses the if/else handler into one annotation
per form. Spring developers get an explicit "@Route ~
@RequestMapping, :id ~ {id}, RouteMatch.param ~ @PathVariable"
callout. React/Vue/Angular Router familiarity also called out.
- ORM section gets a JPA / Hibernate familiarity callout (@Entity,
@Id, @Column, EntityManager, Dao#findById/findAll/find), plus
the explicit "renamed @Transient to @DbTransient to avoid
java.beans.Transient" note.
- JSON / XML section gets a JAXB familiarity callout (@XmlRoot,
@XmlElement, @XmlAttribute, @XmlTransient direct port; the
Jackson convention for the JSON side).
- New OpenAPI section as a headline feature. Walks through the
cn1:generate-openapi-client Mojo configuration in pom.xml, the
Petstore reference spec output (6 models + 3 Api classes), and
a concrete PetApi usage example (getPetById, findPetsByStatus,
addPet). Frames the practical effect for teams whose backends
already publish OpenAPI specs.
- Component-binding section preserved with its validation
annotations, BindAttr, GroupConstraint composition.
- SVG section gets its own heading with a real static-fixture
screenshot (cropped from scripts/ios/screenshots-metal/
SVGStatic.png).
- Lottie section gets its own heading with an animated GIF
composed from the 6-frame LottieAnimatedScreenshotTest grid
(cropped, labels masked, frames stitched at 200ms each, looped).
- "How it works" section at the end explains the bytecode
AnnotationProcessor SPI, the generate-sources / process-classes
Mojo split, the stub-then-overwrite pattern, and the three
non-negotiable rules (no Class.forName, no service loader, no
field reflection).
- REMOVED: "The porting-exercise baseline" section and any other
mention of how decisions were arrived at.
- REMOVED: "The Metal / Android rendering fixes" subsection. The
three Metal / Android bug fixes are not user-facing features of
this release and do not belong in this post.
- REMOVED: "What ties this together" section.
- Front-matter title now quoted to handle the colon (carried over
from the previous CI fix).
Hero image and the lottie-pulse-spinner.gif / svg-static.png
fixtures committed alongside.
---
.../content/blog/build-time-codegen.md | 287 ++++++++++++------
.../lottie-pulse-spinner.gif | Bin 0 -> 15739 bytes
.../blog/build-time-codegen/svg-static.png | Bin 0 -> 51992 bytes
3 files changed, 199 insertions(+), 88 deletions(-)
create mode 100644 docs/website/static/blog/build-time-codegen/lottie-pulse-spinner.gif
create mode 100644 docs/website/static/blog/build-time-codegen/svg-static.png
diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md
index 3185a73469..6d5a6d5db7 100644
--- a/docs/website/content/blog/build-time-codegen.md
+++ b/docs/website/content/blog/build-time-codegen.md
@@ -1,36 +1,38 @@
---
-title: "Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie"
+title: "Routing, ORM, OpenAPI, And Build-Time SVG / Lottie"
slug: build-time-codegen
url: /blog/build-time-codegen/
date: '2026-06-03'
author: Shai Almog
-description: A reusable bytecode AnnotationProcessor SPI in the Maven plugin, the declarative router that is its first consumer, a SQLite ORM, JSON / XML mappers, a component binder with validation, the baseline additions surfaced by porting a substantial mobile client app onto Codename One, and a build-time SVG / Lottie transcoder that emits Codename One Image subclasses for every asset.
-feed_html: '
A reusable bytecode AnnotationProcessor SPI, the declarative router that is its first consumer, ORM + mappers + binder + validation, the porting-exercise baseline additions, and a build-time SVG / Lottie transcoder.'
+description: A declarative router and a unified deep-link API; a JPA-shaped SQLite ORM; JAXB-shaped JSON / XML mappers; an OpenAPI 3.x client generator that turns a spec into typed Codename One code; SVGs and Lottie animations transcoded to Java Image subclasses at build time. All four pieces sit on the same build-time codegen pipeline; the details of that pipeline are at the end.
+feed_html: '
A declarative router with deep links, a JPA-shaped SQLite ORM, JAXB-shaped JSON / XML mappers, an OpenAPI client generator, and build-time SVG / Lottie transcoders.'
---
-
+
-This is the architectural post for the week. The Saturday post was about how you iterate; Monday's post was about new platform APIs; today's post is about a shape of code that several PRs in this release share, that explains why a lot of the new APIs work the way they do, and that should shape how Codename One projects look over the next few years.
+This is the third follow-up to [Friday's release post](/blog/metal-default-new-build-cloud-and-a-new-format/). Saturday's was about how you iterate; Monday's was about new platform APIs in the core; today's is about four pieces that change how you write the structural parts of an app.
-The shape is **build-time codegen**. A reusable bytecode `AnnotationProcessor` SPI in the Maven plugin, the declarative router that is its first concrete consumer, then a SQLite ORM, JSON / XML mappers, and a component binder (all built on the same SPI), plus the build-time SVG / Lottie transcoders that ship in the same release for related reasons. The grab-bag PR from a recent porting exercise (a substantial mobile client app ported onto Codename One) lives here too because the ORM and mapping work share the porting exercise that drove it.
+The four are routing, persistence, network bindings, and graphics. All four use **build-time codegen** under the hood: a Maven-plugin pass that reads annotations or declarative source files at build time and emits typed Java that compiles into your binary. No reflection, no service loader, no `Class.forName`. The "How it works" section at the end of this post is the place to read about the codegen plumbing once you have seen what it powers. The earlier sections focus on the features themselves.
-Six PRs make up this post: [#5037](https://github.com/codenameone/CodenameOne/pull/5037) (router + annotation SPI), [#5047](https://github.com/codenameone/CodenameOne/pull/5047) (ORM + mappers + binder), [#5062](https://github.com/codenameone/CodenameOne/pull/5062) (validation), [#5055](https://github.com/codenameone/CodenameOne/pull/5055) (porting-exercise baseline additions), [#5042](https://github.com/codenameone/CodenameOne/pull/5042) and [#5066](https://github.com/codenameone/CodenameOne/pull/5066) (SVG / Lottie), plus [#5049](https://github.com/codenameone/CodenameOne/pull/5049) (Metal / Android rendering fixes that fell out of the SVG screenshot tests).
+## Deep links and routing
-## Bytecode codegen, not source-text codegen
+The piece that motivates everything else in this section is deep links. Modern mobile apps need to handle URLs from a wide variety of sources: a notification that wants to land on a specific screen, a marketing email with a link into the app, a "share" sheet that hands the user a URL to a particular item, an associated-domains rule that opens the app when a friend taps `https://yourapp.com/users/42` in Safari. iOS treats these through Universal Links; Android treats them through App Links; the framework collapses both into a single in-app concept.
-The Maven plugin now has an `AnnotationProcessor` SPI and two new Mojos: `cn1:generate-annotation-stubs` (in `generate-sources`) and `cn1:process-annotations` (in `process-classes`). The orchestrator ASM-scans `target/classes`, dispatches to every registered processor, validates the annotated classes, and emits a typed runtime artifact next to each one plus a tiny `Index` class that registers everything with a public runtime registry. Adding a new processor later is a matter of dropping it into `META-INF/services` with no orchestrator changes.
+`Display.setDeepLinkHandler(LinkHandler)` registers a handler that receives a normalised `DeepLink` (scheme, host, path, segments, query map, fragment). The same handler fires for cold launches (the app was not running; the OS started it because of a link) and warm launches (the app was already running and got the URL via app-resume). iOS and Android need no port changes for this to work; the existing platform plumbing already writes URL-shaped values into `Display.setProperty("AppArg", url)` and the new handler intercepts those.
-The reason this runs against bytecode rather than against source text is that the source-regex prototype was scrapped early. The bytecode pass sees the JVM's view of the project (`extends Form` is a thing the JVM actually knows, not a pattern we have to hope the user wrote a specific way), rule violations come back with class names and reasons, and the build fails fast before any generated `.class` lands on disk. The infrastructure shares the ASM passes that the `BytecodeComplianceMojo`'s existing String rewrites already use.
-
-A small stub source is emitted under `target/generated-sources/cn1-annotations/` during `generate-sources` so application code that references the generated registry resolves at compile time. The real `.class` overwrites the stub later in `process-classes`. Standard "compile against a stub, link against the real thing" pattern; it just works inside a single Maven build instead of needing a multi-module split.
-
-Three non-negotiable rules across every processor in this batch: **no `Class.forName`, no service loader, 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 class they target. Anything that worked in the simulator and broke in production because R8 renamed a class or a field; that whole shape of bug is structurally absent.
-
-cn1-core ships a no-op stub of each generated index (`RoutesIndex`, `MappersIndex`, `BindersIndex`, `DaosIndex`) so application code compiles even when the project has no annotated classes. The build-time processor shadows each stub with the real implementation before packaging.
+```java
+Display.getInstance().setDeepLinkHandler(link -> {
+ if ("/users".equals(link.path()) && link.segments().size() == 2) {
+ showUserDetailForm(link.segments().get(1));
+ return true;
+ }
+ return false;
+});
+```
-## The router
+That works, but as the surface grows the if/else chain in the handler becomes a real maintenance burden. Five URL patterns, ten patterns, twenty patterns; the handler grows arms and legs, the routing decisions creep into the form constructors that need to read query parameters, and the "what screens does this app have, and at what paths" question is answered by reading through hundreds of lines of switch statements scattered across files.
-The first concrete consumer is the declarative router in `com.codename1.router`. The API is opt-in; the existing `Form.show()` / `Form.showBack()` flow keeps working unchanged.
+The declarative router in `com.codename1.router` is what we built to keep that question tractable. Each form declares its own path with a `@Route` annotation:
```java
@Route("/")
@@ -39,52 +41,67 @@ public class HomeForm extends Form { /* ... */ }
@Route("/users/:id")
public class UserDetailForm extends Form {
public UserDetailForm(RouteMatch match) {
- String id = match.param("id");
- // build UI for user `id`
+ String userId = match.param("id");
+ // build UI for user `userId`
}
}
+
+@Route("/about")
+public class AboutForm extends Form { /* ... */ }
```
-`Router.navigate("/users/42")` resolves the path, instantiates `UserDetailForm`, and shows it. The build-time processor validates that the annotated class extends `Form`, that the path starts with `/`, that the constructor is accessible, that there are no duplicate patterns. Anything off the rails fails the build with a class name and a reason, not at runtime with a stack trace.
+`Router.navigate("/users/42")` resolves the path, instantiates `UserDetailForm`, and shows it. The deep-link handler then collapses to a single line: `Display.getInstance().setDeepLinkHandler(link -> Router.navigate(link.path()))`. Each form owns its own routing rule; adding or moving a screen is a one-class change.
-The rest of the router surface is the kind of thing that has become table stakes in modern client routing: route guards (run before navigation completes; can cancel or redirect), redirects, per-tab navigation stacks (`TabsForm`, where each tab keeps its own back stack), location listeners (anything in the app can subscribe to "the route changed"), and a `Form.setPopGuard(PopGuard)` hook to intercept hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?". `Sheet.showForResult()` returns an `AsyncResource` that auto-cancels with `null` if the user dismisses the sheet.
+**For Spring developers,** the shape is familiar by design. `@Route` plays the same role as Spring MVC's `@RequestMapping`: a class-level declaration that announces "this controller handles URLs of this shape". The `:id` parameter syntax mirrors Spring's `{id}` path-variable syntax; `RouteMatch.param("id")` is the same kind of accessor as Spring's `@PathVariable`. The mental model carries over from server-side Java with almost no friction. The same recognition is available to anyone with React Router, Vue Router, or Angular Router experience; the `:param` convention is the cross-framework default.
-### Deep links
+The build-time processor validates that each annotated class extends `Form`, that the path starts with `/`, that the constructor is accessible, and that there are no duplicate patterns. Any rule violation fails the build with a class name and a reason, not at runtime with a stack trace.
-`Display.setDeepLinkHandler(LinkHandler)` registers a handler that receives a normalised `DeepLink` (scheme, host, path, segments, query, fragment). The same handler is invoked for cold launches (the app was not running; the OS started it because of a link) and warm launches (the app was already running and got the URL via app-resume). iOS and Android need no port changes for this to work; the existing platform plumbing already writes URL-shaped values into `Display.setProperty("AppArg", url)` and the new handler intercepts those.
+The rest of the router surface covers the kind of thing that has become table stakes in modern client routing:
-For the link-publishing side, an `AasaBuilder` emits the iOS `apple-app-site-association` JSON, and an `AssetLinksBuilder` emits the Android `assetlinks.json`. The full setup walk-through (entitlements, `intent-filter`, the `.well-known/` upload) is at [Routing-And-Deep-Links.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Routing-And-Deep-Links.asciidoc), with an end-to-end tutorial at [Tutorial-Routing-And-Deep-Links.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Tutorial-Routing-And-Deep-Links.asciidoc).
+- **Route guards** run before navigation completes and can cancel or redirect.
+- **Per-tab navigation stacks** via `TabsForm`, where each tab keeps its own back stack.
+- **Location listeners** so anything in the app can subscribe to "the route changed".
+- **`Form.setPopGuard(PopGuard)`** intercepts hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?".
+- **`Sheet.showForResult()`** returns an `AsyncResource` that auto-cancels with `null` if the user dismisses the sheet.
-The JavaScript port bridges the router into `window.history` so navigating the in-app router pushes a real entry into the browser history. Back and forward buttons in the browser drive the router; reloading the page lands at the deep-link URL. The Initializr, the Playground, the Skin Designer, and the new Build Cloud console all benefit.
+The API is opt-in. Apps that prefer the existing `Form.show()` / `Form.showBack()` flow keep using that; nothing changes.
-## ORM, mappers, and binder (with validation)
+For the link-publishing side, an `AasaBuilder` emits the iOS `apple-app-site-association` JSON and an `AssetLinksBuilder` emits the Android `assetlinks.json`. The full setup walk-through (entitlements, the Android `intent-filter`, the `.well-known/` upload on your origin server) is at [Routing and Deep Links](https://www.codenameone.com/developer-guide/#_routing_and_deep_links) in the developer guide.
-[PR #5047](https://github.com/codenameone/CodenameOne/pull/5047) lands three more processors on the same SPI. [PR #5062](https://github.com/codenameone/CodenameOne/pull/5062) layers validation on the binder.
+The JavaScript port bridges the router into `window.history` so navigating the in-app router pushes a real entry into the browser's session history. Back and forward in the browser drive the router; reloading the page lands at the deep-link URL; sharing the URL out of the address bar takes a colleague to the same in-app location. The Initializr, the Playground, the Skin Designer, and the new Build Cloud console are all working examples.
-### SQLite ORM
+## SQLite ORM
-`@Entity`, `@Id`, `@Column`, `@DbTransient` for the schema; `EntityManager` and `Dao` for the runtime:
+The second piece is a SQLite ORM, also driven by annotations. `@Entity` marks the class; `@Id` and `@Column` shape the schema; `@DbTransient` opts a field out:
```java
@Entity
public class TodoItem {
- @Id @Column long id;
- @Column String title;
- @Column(name = "completed_at") Date completedAt;
- @DbTransient Object cachedView;
+ @Id @Column long id;
+ @Column String title;
+ @Column(name = "completed_at")
+ Date completedAt;
+ @DbTransient Object cachedView;
}
Dao dao = EntityManager.open("todos.db").dao(TodoItem.class);
dao.createTable();
dao.insert(new TodoItem(0, "Read the post", null));
+
List open = dao.find("completed_at IS NULL", new Object[] {});
+TodoItem byId = dao.findById(42);
+dao.delete(byId);
```
-The generated DAO does the typed work underneath. No reflection in `insert`; the generated code calls `setString(1, e.title)` and `setLong(2, e.id)` directly. Validation at build time catches missing `@Id`, fields that look like relationships but are not yet supported, abstract entity classes.
+The generated DAO does the typed work underneath. No reflection in `insert`; the generated code calls `setString(1, e.title)` and `setLong(2, e.id)` directly against the SQLite `PreparedStatement`. Validation at build time catches missing `@Id`, fields that look like relationships but are not yet supported, and abstract entity classes; the build fails with a class name and a reason.
+
+**For JPA / Hibernate developers,** the API is intentionally familiar. `@Entity`, `@Id`, `@Column`, and `@Transient` (here renamed `@DbTransient` to avoid colliding with `java.beans.Transient`) carry the same meaning they do under `javax.persistence` / `jakarta.persistence`. The `EntityManager` name is the same. `Dao#findById`, `Dao#findAll`, `Dao#find(where, params)`, `Dao#insert`, `Dao#update`, `Dao#delete` line up with the basic JPA repository contract. The query language is plain SQL (there is no JPQL or Criteria DSL) but the annotation surface, the lifecycle, and the runtime methods will feel like a long-lost friend to anyone with server-side Java persistence experience.
-### JSON / XML mapping
+Three rules the design enforces: no `Class.forName`, no service loader, no field reflection. The "this code only breaks in production because R8 renamed a field" shape of JPA-on-Android bug is structurally absent.
-`@Mapped` is the entry point; `@JsonProperty` and `@XmlElement` (plus `@XmlRoot`, `@XmlAttribute`, `@JsonIgnore`, `@XmlTransient`) shape the wire format:
+## JSON / XML mapping
+
+`@Mapped` marks a class as a transferable POJO. `@JsonProperty` and `@XmlElement` (plus `@XmlRoot`, `@XmlAttribute`, `@JsonIgnore`, `@XmlTransient`) shape the wire format. The runtime entry points are `Mappers.toJson(...)`, `Mappers.fromJson(...)`, `Mappers.toXml(...)`, `Mappers.fromXml(...)`:
```java
@Mapped
@@ -100,107 +117,201 @@ String json = Mappers.toJson(user);
User back = Mappers.fromJson(json, User.class);
```
-The same `@Mapped` POJO is the type the `Rest` helpers from PR #5055 will accept (`Rest.get(url).fetchAsMapped(User.class)`, `fetchAsMappedList(User.class)`).
+The same `@Mapped` POJO is the type the typed `Rest` helpers accept:
+
+```java
+Rest.get("https://api.example.com/users/42")
+ .fetchAsMapped(User.class)
+ .onResult((user, err) -> { /* ... */ });
+
+Rest.get("https://api.example.com/users")
+ .fetchAsMappedList(User.class)
+ .onResult((users, err) -> { /* ... */ });
+```
+
+`Rest.fetchAsJsonList` (top-level JSON arrays, no `{"root":[...]}` envelope trick), `JSONWriter` (the complement of `JSONParser`, with fluent builders and streaming variants for `Writer` and `OutputStream`), and `URLImage.setDefaultBearerToken` (auth headers on image fetches) all ship alongside.
+
+**For JAXB developers,** the XML surface (`@XmlRoot`, `@XmlElement`, `@XmlAttribute`, `@XmlTransient`) is a direct port of the long-established `javax.xml.bind.annotation` surface. The same model class can be both `@XmlRoot`-decorated and `@JsonProperty`-decorated, which gives you a single source of truth for both wire formats. The JSON surface adopts the Jackson convention (`@JsonProperty`, `@JsonIgnore`) that nearly every modern JVM JSON binding (Jackson, Moshi, kotlinx-serialization) inherited.
+
+## OpenAPI client generation
+
+This is the headline of the codegen post and arguably the most useful single feature in this release for any team that talks to a backend.
+
+A new `cn1:generate-openapi-client` Mojo reads an OpenAPI 3.x JSON spec (a URL or a local file) and writes typed Codename One client code that compiles into your app:
+
+- One `@Mapped` POJO per `components.schemas` entry.
+- One `Api.java` class per OpenAPI tag, with one fluent method per operation.
+- Every method routes through `Rest.` + `Mappers.toJson` + `fetchAsMapped` / `fetchAsMappedList`, so the generated surface integrates with the rest of the framework instead of dragging in a separate HTTP stack.
+
+Wire it into the project's `pom.xml`:
-### Component binding with validation
+```xml
+
+ com.codenameone
+ codenameone-maven-plugin
+
+
+ petstore-client
+ generate-openapi-client
+
+ https://petstore3.swagger.io/api/v3/openapi.json
+ com.example.petstore
+
+
+
+
+```
+
+`mvn generate-sources` picks the spec up, downloads it, and writes one file per schema and one per tag under `target/generated-sources/`. The Petstore reference spec exercised end-to-end produces six model classes (`Pet`, `Order`, `Customer`, `Tag`, `Category`, `User`) and three Api classes (`PetApi`, `StoreApi`, `UserApi`), and the nine generated `.class` files compile cleanly against `codenameone-core`. Documented at [the OpenAPI codegen Maven goal](https://www.codenameone.com/developer-guide/#_appendix_goal_generate_openapi_client).
-`@Bindable` marks a model class; `@Bind(name = "userField")` ties a field to a component on the form by its name. The build-time binder reads the field types and the components, generates the bidirectional wiring at compile time:
+In application code you call the generated `Api` class the same way you would call any other Java method:
+
+```java
+PetApi pets = new PetApi();
+
+// Returns AsyncResource; resolves with the deserialised object.
+pets.getPetById(42).onResult((pet, err) -> {
+ if (err == null) Log.p("Got " + pet.getName());
+});
+
+// Returns AsyncResource>.
+pets.findPetsByStatus("available").onResult((list, err) -> {
+ if (err == null) {
+ for (Pet p : list) Log.p(p.getName());
+ }
+});
+
+// POST with a request body. addPet takes a Pet, returns a Pet.
+Pet candidate = new Pet();
+candidate.setName("Mittens");
+candidate.setStatus("available");
+pets.addPet(candidate).onResult((created, err) -> { /* ... */ });
+```
+
+There is no hand-rolled `ConnectionRequest` setup, no manual JSON parsing, no string-typed request bodies. The generated client takes a typed `Pet`, serialises it with `Mappers.toJson(...)`, fires the right HTTP verb, deserialises the response with `Mappers.fromJson(...)`, and surfaces the result through the framework's `AsyncResource` so your callback fires on the EDT.
+
+For teams who already publish an OpenAPI spec as part of their backend (most modern backend frameworks do this automatically; FastAPI, Spring's `springdoc-openapi`, NestJS, ASP.NET Core, Go's `gnostic`), the practical effect is that the mobile client's bindings stay in sync with the backend without anyone hand-writing a single network call. Update the spec, re-run `mvn generate-sources`, and the new and changed endpoints land in your app as typed Java the IDE picks up immediately.
+
+It is the kind of change that is most useful when you do not know you have it: pull a fresh spec, rebuild, and your IDE highlights every place in the codebase that called a renamed endpoint or passed the wrong type to a parameter.
+
+## Component binding with validation
+
+The fourth annotation processor on the same pipeline is the component binder. `@Bindable` marks a model class; `@Bind(name = "userField")` ties a field to a component on a form by the component's `name`. Field-level validation annotations compose with `@Bind` on the same field:
```java
@Bindable
public class SignupModel {
- @Bind(name = "userField") @Required @Length(min = 3) private String user;
- @Bind(name = "emailField") @Required @Email private String email;
- @Bind(name = "ageField") @Numeric(min = 13, max = 120) private String age;
- @Bind(name = "roleField") @ExistIn({ "admin", "editor",
- "viewer" }) private String role;
+ @Bind(name = "userField") @Required @Length(min = 3)
+ private String user;
+
+ @Bind(name = "emailField") @Required @Email
+ private String email;
+
+ @Bind(name = "ageField") @Numeric(min = 13, max = 120)
+ private String age;
+
+ @Bind(name = "roleField") @ExistIn({ "admin", "editor", "viewer" })
+ private String role;
}
+```
+The runtime side is two lines:
+
+```java
Binding b = Binders.bind(model, form);
b.getValidator().addSubmitButtons(submitBtn);
```
-`Binding` is the handle: `refresh()` re-reads the model into the components, `commit()` writes the components back, `disconnect()` tears the listeners down. Multiple validation annotations on a single field compose via the existing `Validator.addConstraint(Component, Constraint...)` varargs (`GroupConstraint`, first failure wins). `@Validate(MyClass.class)` is the escape hatch for hand-written `Constraint` implementations. The validation set: `@Required`, `@Length`, `@Regex`, `@Email`, `@Url`, `@Numeric`, `@ExistIn`, `@Validate`.
+`Binding` is the handle: `refresh()` re-reads the model into the components, `commit()` writes the components back, `disconnect()` tears the listeners down. Multiple validation annotations on a single field compose via `Validator.addConstraint(Component, Constraint...)` and `GroupConstraint` (first failure wins). `@Validate(MyClass.class)` is the escape hatch for hand-written `Constraint` implementations. The validation set: `@Required`, `@Length`, `@Regex`, `@Email`, `@Url`, `@Numeric`, `@ExistIn`, `@Validate`.
-The new `BindAttr` enum lets `@Bind` target a specific attribute of the component (`TEXT`, `UIID`, `SELECTED`, ...) when the default is not what you want. The annotation framework reads it at build time and generates the matching `Component#setUiid(...)` / `Component#setSelected(...)` call.
+The new `BindAttr` enum lets `@Bind` target a specific attribute of the component (`TEXT`, `UIID`, `SELECTED`, ...) when the default ("write a `String` field into the component's text") is not what you want.
-Three new dev-guide chapters: [Annotation-JSON-XML-Mapping.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc), [Annotation-Component-Binding.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-Component-Binding.asciidoc), and [Annotation-SQLite-ORM.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-SQLite-ORM.asciidoc).
+## SVG at build time
-## The porting-exercise baseline
+SVG and Lottie use the same codegen pipeline, but emit different output: instead of reading annotations from your Java, they read declarative graphics files from `src/main/svg/` and `src/main/lottie/` and emit Codename One `Image` subclasses that render through the `Graphics` shape API on every platform.
-[PR #5055](https://github.com/codenameone/CodenameOne/pull/5055) ships as "Improvements to baseline based on porting exercise". The porting exercise was real: a substantial third-party mobile client onto Codename One. The port is still compiling cleanly through every change in that PR; the additions worth pulling out:
+Drop an SVG into `src/main/svg/`:
-**Java subset additions.** Eleven Java 8 default methods on `Map` (`getOrDefault`, `putIfAbsent`, `remove(K, V)`, `replace(K, V)` / `replace(K, V, V)`, `forEach`, `replaceAll`, `computeIfAbsent`, `computeIfPresent`, `compute`, `merge`); `BiFunction`; `Iterable.forEach(Consumer)`, `Collection.removeIf(Predicate)`, `List.replaceAll(UnaryOperator)`, `List.sort(Comparator)`. All four primitive atomics: `AtomicReference`, `AtomicInteger`, `AtomicLong`, `AtomicBoolean`. Standard Java 8 surface that was simply missing.
-
-**`Rest` typed helpers.** `Rest.fetchAsJsonList`, `Rest.fetchAsMapped(Class)`, `Rest.fetchAsMappedList(Class)` (these are the surface the mapper post above mentioned). `Rest.fetchAsJsonList` closes a long-standing rough edge: top-level JSON arrays no longer need the historical `{"root":[...]}` envelope trick.
-
-**`URLImage.RequestDecorator`** plus `setDefaultRequestDecorator`, `setDefaultBearerToken`, and a per-call decorator overload on `createToStorage`. Authenticated image endpoints no longer require working around the "URLImage does not pass headers" gap; set a bearer token once and the image cache and the `URLImage` machinery use it everywhere.
-
-**`JSONWriter`** is the complement of `JSONParser`. `JSONWriter.toJson(Object)` for one-shot, fluent `JSONWriter.object().put(...).toJson()` and `JSONWriter.array()` builders, streaming variants for `Writer` and `OutputStream`.
+```
+src/main/svg/
+ star.svg
+ gradient_circle.svg
+ path_arrow.svg
+ rounded_button.svg
+ wave.svg
+ pro_badge.svg
+ clipped_badge.svg
+```
-**`Tabs.setAnimatedIndicator(boolean)`** enables a Material 3 / iOS 26 sliding-underline indicator (gated by `tabsAnimatedIndicatorBool` plus duration / thickness constants and a new `TabIndicator` UIID). Off in the framework defaults; **on by default in the modern native themes** that we landed two weeks ago.
+After the next build:
-**`DefaultLookAndFeel.drawModernPullToRefresh`** is a Material 3 arc-spinner painted via `Graphics.drawArc`; sweep grows 0° to 330° as the user pulls, then spins while the task runs. Gated by `pullToRefreshModernBool`; on by default in the modern themes.
+```java
+Image star = Resources.getGlobalResources().getImage("star.svg");
+Image star2 = Resources.getGlobalResources().getImage("star"); // either form
+form.add(star);
+```
-**`MorphTransition.snapshotMode(boolean)`** is the fix for an edge case that has been around forever: a morph from a source inside a scrolling container leaked off-viewport because the source was repainted live. The opt-in path captures source and destination as clipped `Image` snapshots at `initTransition()` and tweens those. Default live-paint path unchanged.
+A grid of the static SVGs from the hellocodenameone fixture, rendered through the new pipeline:
-**`com.codename1.io.websocket`** moves into the core. Per-platform native impls remain in the cn1lib for now.
+
-**`cn1:generate-openapi-client`** mojo reads an OpenAPI 3.x JSON spec and emits one `@Mapped` POJO per `components.schemas` entry plus one `Api.java` per tag. The Petstore reference spec runs end-to-end. Dev-guide page: [appendix_goal_generate_openapi_client.adoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/appendix_goal_generate_openapi_client.adoc).
+The transcoder is a `maven/svg-transcoder/` module that parses SVG with `javax.xml` StAX. No Batik, no Flamingo, no external dependencies. Coverage targets what real-world icon SVGs use: `rect` (rounded corners included), `circle`, `ellipse`, `line`, `polyline`, `polygon`, the full `path` grammar (`M` / `L` / `H` / `V` / `C` / `S` / `Q` / `T` / `A` / `Z` plus relative-coordinate and smooth-curve reflection), groups with affine transforms (`translate`, `scale`, `rotate`, `skew`, `matrix`), linear gradients via `LinearGradientPaint`, fill, stroke, stroke-width, linecap, linejoin, opacity.
-A natural question on this PR is "why is all of that in one PR rather than ten". The honest answer is that the porting exercise was the regression fixture for every single one of those additions, and breaking it up would have meant maintaining a long-lived branch where each split-out item had to be re-verified against the port independently.
+SMIL animations are supported in the same pipeline: ``, `` (`translate`, `scale`, `rotate`), and ``. Time values interpolate against wall-clock time on every paint, with `from` / `to` / `values` / `begin` / `dur` / `repeatCount` / `fill="freeze"` honoured. So an SVG with a rotating sub-element becomes a real animated `Image` you can drop into a `Form` and watch spin without writing any animation code yourself.
-## SVG and Lottie at build time
+**Important caveat:** this is **Metal-only on iOS**. The GL ES 2 path that was the iOS default until [last Friday's flip](/blog/metal-default-new-build-cloud-and-a-new-format/#metal-is-the-default-on-ios) does not have the shape API coverage the SVG / Lottie pipeline emits. Apps that opted in to Metal pick the transcoders up automatically; apps still on `ios.metal=false` will see placeholders. Now that Metal is the default this stops being a thing most apps notice on their next build.
-The last two PRs in this batch sit on the same "emit Java from declarative input at build time" pattern. [PR #5042](https://github.com/codenameone/CodenameOne/pull/5042) is the SVG transcoder; [PR #5066](https://github.com/codenameone/CodenameOne/pull/5066) is the Lottie / Bodymovin transcoder that reuses the SVG pipeline; [PR #5049](https://github.com/codenameone/CodenameOne/pull/5049) is the small set of iOS Metal and Android rendering fixes the SVG screenshot tests exposed. They share one chapter at [SVG-Transcoder.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/SVG-Transcoder.asciidoc).
+Coverage and the troubleshooting section are at [SVG Transcoder](https://www.codenameone.com/developer-guide/#_svg_transcoder) in the developer guide. Explicit non-coverage in v1: SVG `text`, masks / clip-paths, filters, radial-gradient paint (falls back to first stop colour), CSS keyframe animations.
-**Important caveat before the details:** this is **Metal-only on iOS**. The GL ES 2 path that was the iOS default until [last Friday's flip](/blog/metal-default-new-build-cloud-and-a-new-format/#metal-is-the-default-on-ios) does not have the shape API coverage the SVG / Lottie pipeline emits. Apps that opted in to Metal pick up the transcoders automatically; apps still on `ios.metal=false` will see placeholders. Now that Metal is the default this stops being a thing most apps notice on their next build.
+## Lottie at build time
-The shape:
+The same pipeline carries Lottie. Drop a Bodymovin export into `src/main/lottie/`:
```
-src/main/svg/
- home.svg
- settings.svg
- profile.svg
src/main/lottie/
- spinner.json
pulse.json
+ spinner.json
```
-After the next build:
+After the next build, both are real `Image` instances on every platform that exposes the shape API:
```java
-Image home = Resources.getGlobalResources().getImage("home");
+Image pulse = Resources.getGlobalResources().getImage("pulse");
Image spinner = Resources.getGlobalResources().getImage("spinner");
-form.add(home).add(spinner);
+form.add(pulse).add(spinner);
```
-The SVG transcoder is a `maven/svg-transcoder/` module that parses SVG with `javax.xml` StAX (no Batik, no Flamingo, no external deps) and emits a Codename One `Image` subclass rendering through the `Graphics` shape API. SVG coverage covers what real-world icon SVGs use: rect (rounded corners), circle, ellipse, line, polyline, polygon, the full `path` grammar (M / L / H / V / C / S / Q / T / A / Z plus relative-coordinate and smooth-curve reflection), groups with affine transforms, linear gradients, fill, stroke, opacity. SMIL animations are supported: ``, `` (translate / scale / rotate), ``. Time values interpolate against wall-clock time on every paint.
+The animation runs against wall-clock time on every paint, with no `Timer` and no allocation in the hot path. A capture of the hellocodenameone Lottie fixture (one pulsing circle and one rotating bar) at six points in the loop:
+
+
-The Lottie pipeline reuses everything: each Bodymovin file is parsed into the same `SVGDocument` model the SVG path uses, the same `JavaCodeGenerator` emits the same `GeneratedSVGImage` subclass, the same `SVGRegistry` registers it. No new `Image` base class, no per-port wiring. v1 covers shape layers (`rc` / `el` / `sh`) with solid fills and strokes, layer transforms (anchor / position / scale / rotation / opacity), animated rotation / position / scale collapsed to a 2-keyframe loop, solid-color layers as filled rects.
+The Lottie transcoder lives in `maven/lottie-transcoder/`. It parses Bodymovin JSON with no external dependencies (the framework's built-in JSON parser carries the load) and lowers each file into the same `SVGDocument` model the SVG path uses. The same `JavaCodeGenerator` emits the same `GeneratedSVGImage` subclass, and the same `SVGRegistry` registers it under the source filename. **No new `Image` base class, no new registry, no per-port wiring**, since the SVG path's JavaSE reflective load and iOS / Android Stub weaving already cover the new format.
-Why Java at build time rather than parse SVG at runtime: parsing requires `javax.xml`, which is JVM-only by design; the generated code is allocation-light and deterministic (a path's commands become inlined `g.fillShape(new GeneralPath()...)` calls; an animation becomes a `currentTransform.translate(...)` against a wall-clock variable); and R8 / ParparVM rename and dead-code eliminate the generated code as freely as any other class, so SVGs you do not actually `getImage(...)` get dropped from the final binary.
+Coverage in v1: shape layers (`rc` / `el` / `sh`) with solid fills and strokes; layer transforms (anchor, position, scale, rotation, opacity); animated rotation, position, and scale collapsed to a two-keyframe loop; solid-color layers as filled rects. Most icon-grade Bodymovin exports lower cleanly. Complex character animations from After Effects with image references, masks, and effects do not, and the transcoder logs which layers it dropped so the source of any blank output is obvious.
-### The Metal / Android rendering fixes
+## How it works: the build-time codegen pipeline
-The SVG screenshot tests exercised the shape API harder than anything we had thrown at it before, and three rendering bugs surfaced; the fixes in [PR #5049](https://github.com/codenameone/CodenameOne/pull/5049) affect any code path that uses `setClip(GeneralPath)`, gradient paint, or text under a transform, not just the SVG pipeline:
+Everything above sits on a single Maven-plugin pass.
-1. **iOS Metal `setClip(GeneralPath)` triangle.** Metal's stencil clip's triangle fan was treating every Bezier control point as a polygon vertex. Non-rect `ClipShape`s are now midpoint-flattened into a polyline before reaching native.
-2. **iOS Metal `drawString` skips the affine scale.** Text under a viewBox scale was rasterised at `font.pointSize` and stretched on the GPU. `CN1MetalDrawString` now reads the effective scale from `currentTransform`, picks an atlas font at `pointSize * scale`, and divides glyph metrics back into caller-side coords.
-3. **Android and iOS Metal `gradient_circle.svg` double-circle.** `LinearGradientPaint.paint` was baking `getTranslateX/Y()` into a translate that sat before the SVG scale, sending the cell offset through the scale twice. The "translate dance" is dropped.
+The plugin has an `AnnotationProcessor` SPI and two new Mojos: `cn1:generate-annotation-stubs` (in `generate-sources`) and `cn1:process-annotations` (in `process-classes`). The orchestrator ASM-scans `target/classes`, dispatches to every registered processor, validates the annotated classes, and emits a typed runtime artifact next to each one plus a tiny `Index` class that registers everything with a public runtime registry. Adding a new processor later is a matter of dropping it into `META-INF/services` with no orchestrator changes.
+
+The reason this runs against bytecode rather than against source text is that the source-regex prototype was scrapped early. The bytecode pass sees the JVM's view of the project (`extends Form` is a thing the JVM actually knows, not a pattern we have to hope the user wrote a specific way), rule violations come back with class names and reasons, and the build fails fast before any generated `.class` lands on disk. The infrastructure shares the ASM passes that the `BytecodeComplianceMojo`'s existing String rewrites already use.
-If your app uses `setClip(GeneralPath)` or paints text under a non-uniform transform anywhere, you pick these fixes up on next rebuild.
+A small stub source is emitted under `target/generated-sources/cn1-annotations/` during `generate-sources` so application code that references the generated registry resolves at compile time. The real `.class` overwrites the stub later in `process-classes`. Standard "compile against a stub, link against the real thing" pattern; it just works inside a single Maven build instead of needing a multi-module split.
+
+cn1-core ships a no-op stub of each generated index (`RoutesIndex`, `MappersIndex`, `BindersIndex`, `DaosIndex`) so application code compiles even when the project has no annotated classes. The build-time processor shadows each stub with the real implementation before packaging.
-## What ties this together
+Three non-negotiable rules across every processor:
-The thread across all six PRs is the same pattern: **emit Java at build time, validate at build time, fail fast with a class name and a reason, R8 / ParparVM rename the generated code together with the rest of the app**. The router uses it to register `@Route` classes. The ORM uses it to generate typed DAOs. The mappers use it to generate typed JSON / XML readers and writers. The binder uses it to wire fields to components. The SVG and Lottie transcoders use it to turn declarative graphics into Java classes that render through the shape API.
+- **No `Class.forName`** anywhere in generated code.
+- **No service loader.** Everything is wired through the typed registry the codegen emits.
+- **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 class they target.
-The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. That is the shape Codename One projects are going to look more and more like over the next year.
+The SVG and Lottie transcoders sit on a parallel pipeline (declarative graphics files in place of annotations), but follow the same rules and emit code that obeys the same constraints. The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. That is the shape Codename One projects are going to look more and more like over the next year.
## Wrapping up
-That closes out the post series for this release cycle. The next weekly index lands on Friday in the same short format.
+That closes the post series for this release. The next weekly index lands on Friday in the same short format.
Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/).
diff --git a/docs/website/static/blog/build-time-codegen/lottie-pulse-spinner.gif b/docs/website/static/blog/build-time-codegen/lottie-pulse-spinner.gif
new file mode 100644
index 0000000000000000000000000000000000000000..5f2d85a26e0626f21cd08544fde20b78f25fd448
GIT binary patch
literal 15739
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
2U|t8PYX4>z%Ce;MElC@n2MP$i
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
literal 0
HcmV?d00001
diff --git a/docs/website/static/blog/build-time-codegen/svg-static.png b/docs/website/static/blog/build-time-codegen/svg-static.png
new file mode 100644
index 0000000000000000000000000000000000000000..c9f5d8ab77d3c8d60e29af87e214136e5d556fb8
GIT binary patch
literal 51992
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
z