Declarative router + deep links + bytecode annotation framework#5037
Open
shai-almog wants to merge 30 commits into
Open
Declarative router + deep links + bytecode annotation framework#5037shai-almog wants to merge 30 commits into
shai-almog wants to merge 30 commits into
Conversation
A new com.codename1.router package layers URL-based navigation, deep
links, route guards, and a per-tab nav shell on top of existing Form
infrastructure. The runtime is opt-in: existing Form.show() / showBack()
code continues to work unchanged.
Core runtime (CodenameOne/src/com/codename1/router/):
- DeepLink — tolerant URL parser (scheme/host/path/segments/query/fragment).
- Router — fluent route(), redirect(), guard(), notFound(), start();
push/pop/replace with a navigation stack, location listeners, and a
pluggable BrowserHistoryBridge for the JavaScript port.
- PopGuard / PopReason — Flutter-PopScope analogue; intercepts hardware
back, toolbar back, and Router.pop() before user code runs.
- RouteContext / RouteBuilder / RouteGuard — typed handoff between a
matched URL and the Form constructor.
- TabsForm — Form whose tabs each keep their own navigation stack.
- AasaBuilder / AssetLinksBuilder — JSON generators for iOS Universal
Links and Android App Links.
- web/JsRouterBootstrap + cn1-router-history.js — browser-history bridge
for the JS port.
Display gains setDeepLinkHandler(LinkHandler) / dispatchDeepLink(url).
Display.setProperty("AppArg", url) now routes URL-shaped values through
the deep-link handler, so iOS/Android ports need no native changes.
Form gains setPopGuard(PopGuard) and checkPopGuard(reason); guards are
honored from MenuBar.keyReleased (hardware back) and Button.fireActionEvent
(toolbar back). Sheet gains showForResult() returning AsyncResource<T>
with auto-cancel on dismiss.
Build-time annotation framework (maven/codenameone-maven-plugin/):
- A reusable AnnotationProcessor SPI under com.codename1.maven.annotations
(AnnotatedClass, MethodInfo, FieldInfo, AnnotationValues, ProcessorContext,
ClassScanner). Processors register via ServiceLoader.
- Two new Mojos:
cn1:generate-annotation-stubs (generate-sources) — writes compile-time
stubs each processor declares.
cn1:process-annotations (process-classes) — ASM-scans target/classes,
dispatches to every registered processor, fail-fast aborts with a
combined error list when validation fails.
- RouteAnnotationProcessor — the first concrete processor. Bytecode-based
(not source regex): validates @route classes (extends Form, non-empty
path starting with /, accessible constructor, no duplicate patterns),
emits the RoutesIndex + RoutesIndex$Builder bytecode directly via ASM.
Prefers a (RouteContext) constructor over a no-arg one when both exist.
Adding more annotations is now a matter of dropping a new processor in
META-INF/services — the orchestrator picks them up unchanged.
Tests:
- 46 new core unit tests for DeepLink, RouteMatch, Router, PopGuard,
AasaBuilder, AssetLinksBuilder (all 2608 core tests still pass).
- 11 new plugin tests: ClassScanner picks annotations off real .class
files; RouteAnnotationProcessor compiles fixtures with javac, runs
the processor, loads the generated bytecode in a child classloader,
and invokes RoutesIndex.register() against a stub Router that records
every call. Negative tests cover non-Form @route, empty pattern,
missing leading slash, duplicate pattern, and abstract class targets.
Maven plugin pom: bumped maven-surefire-plugin from 2.22.1 to 3.2.5 and
added junit-vintage-engine. The old surefire silently skipped every JUnit
test in this module under JDK 8; the bump matches the parent reactor's
existing pin.
Docs:
- Routing-And-Deep-Links.asciidoc — reference page covering every part of
the API plus iOS Universal Links / Android App Links setup.
- Tutorial-Routing-And-Deep-Links.asciidoc — end-to-end tutorial from an
empty project to a working deep-linkable app with @route, guards, and
a tab shell.
- Both pages included from developer-guide.asciidoc; asciidoctor lint
passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The Codename One JavaScript port's bundler scans the build output for any *.js file and importScripts() each one into the parparvm Web Worker. cn1-router-history.js was authored as a browser-main-thread shim and crashed the worker with `ReferenceError: document is not defined` on import, which broke the javascript-screenshots CI run. Guard the body of the shim with an explicit feature test for document / addEventListener / history so the same file safely round-trips through the worker import without doing anything, while still installing the browser-history bridge when loaded into the main page. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The Hugo-rendered website flattens each .asciidoc into its own page and doesn't translate the AsciiDoc link: macro into the resulting HTML, so both `link:Routing-And-Deep-Links.asciidoc[...]` and `link:Maven-Getting-Started.adoc[...]` in the tutorial pointed at non-existent files in the build output. Lychee correctly flagged them. Replace with inline references — the developer guide is a single include-stitched book, so cross-page links between included files were always cosmetic. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Contributor
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Vale reported 5 issues against the routing chapters: - Two Microsoft.HeadingColons violations on === headings whose post-colon word started lowercase. - Three Microsoft.Contractions hits for spelling out 'it is', 'does not', and 'they are' in the tutorial prose. LanguageTool flagged 'parparvm' and 'assetlinks' as misspellings. Both are technical identifiers; add them to the LanguageTool accept list under a new comment block. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Collaborator
Author
|
Compared 20 screenshots: 20 matched. |
Contributor
Cloudflare Preview
|
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
The legacy Ant build of CodenameOne core compiles against the CLDC11 bootclasspath, which doesn't expose java.util.regex.Pattern / Matcher — that's why the rest of CN1's core uses com.codename1.util.regex.RE. Under JDK 21 the Ant build (build-test (21)) broke with `package java.util.regex does not exist`. Switch RouteMatch to RE. Two engine differences vs java.util.regex that the rewrite has to account for: 1. RE.match(s) is find-style, not full-match. The pattern was already anchored with ^ and $, but we also assert getParenStart(0) == 0 and getParenEnd(0) == path.length() as belt-and-braces. 2. RE.getParenCount() counts groups the matcher actually visited. The catch-all wildcard `**` is implemented as an alternation `(?:|/(.*))` where the suffix capture lives on the right branch; when the left (empty) branch wins, the inner capture group isn't reported at all. Always populate the param map with empty string in that case so callers don't have to null-check. Refactor only — no public API change. All 46 router unit tests still pass, including the new bare-prefix catch-all assertion. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Two CI gates needed cleanup:
1. build-test (8): the PR-CI quality-report aggregates PMD findings and
fails the build when any forbidden rule fires in changed code. The
routing PR introduced 88 violations split across:
66 ControlStatementBraces (single-line `if (x) stmt;`)
10 MissingOverride (anonymous classes implementing interfaces)
5 ForLoopCanBeForeach (index-based for over a List)
4 LiteralsFirstInComparisons (`seg.equals("X")`)
1 SingularField (compiledRegex used only at construction)
1 UnnecessaryImport (java.util.HashMap leftover after refactor)
1 UnnecessaryFullyQualifiedName (com.codename1.io.Log.e)
Re-formatted every flagged line; PMD now reports zero forbidden
violations from this PR.
2. build-test (17): the legacy Android Ant build compiles with the
platform-default encoding (US-ASCII) and rejected the em-dashes,
en-dashes, and smart quotes that had crept into javadoc comments.
Replaced every non-ASCII character in the new and modified .java
files with its ASCII equivalent (-- for em-dash, ' for curly quote,
etc.). Verified there are zero codepoints > 0x7F in any touched
.java file.
Tests still green: 46 router core tests + 11 plugin tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
build-test (8) failed at the quality-report Checkstyle gate: the
project's checkstyle.xml uses the default LeftCurly option (`eol`),
which rejects any `{` that has code after it on the same line. Inline
forms like `if (x) { return; }`, `private RoutesIndex() { }`, and
`public String getRaw() { return raw; }` all violated.
Expand every flagged occurrence into the canonical three-line form:
if (x) {
return;
}
PMD's ControlStatementBraces rule is satisfied by the braces; Checkstyle
is now happy that the brace ends its line. The two gates pull in the
same direction once you commit to multi-line bodies.
Affects only the new routing package and the surface I touched on
Display/Form/Sheet/MenuBar/Button. No behavior change. All 46 router
core tests + 11 plugin tests still pass; local Checkstyle severity-Error
count drops from 99 to 0 across the touched files.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
…files The new files I introduced inherited the Oracle "All rights reserved" copyright header from the surrounding sources I was copying boilerplate from. That header is correct on Oracle-origin files (the J2ME fork ancestry) but wrong on newly authored code, which belongs to Codename One. Swap every header on the 44 new files for the canonical CN1 header. Touched files in CodenameOne/src/com/codename1/router, maven/codenameone-maven-plugin, the test stubs, and the new core unit tests. Also strip every `#### Since 8.0` block from doc comments. We're not at 8.0 yet and the version pinning was speculative; the public API surface is stable across the touched packages and the per-method version markers were noise. No code or test changes; all 46 router unit tests + 11 plugin tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Combine the two non-overlapping accept-list blocks (router & JS port identifiers from this PR, WebAuthn / passkey terms from master's #5039) into one section.
cn1-router-history.js was wrongly living under
CodenameOne/src/com/codename1/router/web/, which is the Java source
tree. That's exactly why it got swept into the parparvm worker bundle
by the JS bundler (which scans `*.js` in the build output and
importScripts()'s them) and crashed with `ReferenceError: document is
not defined` — a class of bug that only existed because the file sat
in the wrong place.
Move to Ports/JavaScriptPort/src/main/webapp/cn1-router-history.js
alongside port.js and sw.js. Those files are served as static webapp
assets and never imported into the worker, so:
- the bundler doesn't pick the shim up at all
- the worker-context guard I added earlier becomes unnecessary and is
removed (the shim now unconditionally installs the popstate /
history.pushState bridge it always wanted to)
Also flatten the now-empty com.codename1.router.web subpackage:
JsRouterBootstrap moves up to com.codename1.router. It was the only
class in the subpackage and is itself a single-method install() helper
that doesn't justify its own namespace.
Doc updates to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Contributor
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
…tcher
Major redesign of the deep-linking story per maintainer feedback. The
previous PR exposed a Router class, a TabsForm with forced UI hierarchy,
location/history listeners, a DeepLink value class, a LinkHandler
interface, a BrowserHistoryBridge, and tools/AasaBuilder + AssetLinksBuilder
in core. The route table was wired up by the application calling a
generated register() method.
That surface was too big and put URL semantics into the public runtime.
Replaced with a Spring-style annotation-driven design where the only
public types in the routing space are two annotations:
@route("/users/:id")
public class ProfileForm extends Form {
public ProfileForm(@RouteParam("id") String id) { ... }
}
@route("/home")
public static Form home() { ... }
Method-level @route on static factories is supported the same way.
Under the hood
--------------
The Codename One Maven plugin's process-annotations goal scans the
project's compiled bytecode, validates every @route fail-fast, and
generates com.codename1.router.generated.Routes -- a final class
implementing the package-private RouteDispatcher SPI. A static
Routes.bootstrap() installs itself into Display from its declaration; the
generated dispatcher does URL parsing, pattern matching, path variable
extraction, query-string fallback, and Form factory invocation. The
existing setProperty("AppArg", url) hook in every CN1 port routes URL-
shaped values through Display's internal dispatcher.
Removed
-------
* Router, RouteContext, RouteBuilder, RouteGuard, RouteMatch (folded into
the generated dispatcher).
* Location, LocationListener (gone -- no history-stream public API).
* DeepLink, LinkHandler (URL semantics are internal).
* TabsForm (forced UI hierarchy; the maintainer's preference is to leave
Tabs as a flexible Container and let route metadata live on the
Container if needed at all).
* BrowserHistoryBridge, JsRouterBootstrap, cn1-router-history.js
(history mirroring belongs in a port-side concern, not the public
runtime; can be reintroduced later in Ports/JavaScriptPort if needed).
* Display.setDeepLinkHandler / getDeepLinkHandler / dispatchDeepLink --
the public deep-link API is gone. Replaced by Display.installRouteDispatcher
which is doc-tagged "internal".
* The standalone Routing-And-Deep-Links and Tutorial-Routing-And-Deep-Links
docs (rewritten as one short Deep-Links-Routing chapter).
Moved
-----
* AasaBuilder / AssetLinksBuilder and their tests: from
com.codename1.router.tools (public runtime) to
com.codename1.maven.routing in the maven plugin (build-time tooling).
* JavaSourceCompiler: from plugin test-classes to main, so the route
processor can use it to compile its generated source on the fly.
Added
-----
* @com.codename1.annotations.RouteParam: Spring-style parameter binding.
* com.codename1.router.RouteDispatcher: internal SPI implemented by the
generated class.
* com.codename1.router.generated.Routes (stub): no-op shipped with the
framework, overwritten in target/classes by the maven plugin when the
project declares @route targets.
* Three package-info.java files for the touched packages.
Removed every Flutter reference and every "Since X.Y" marker from the
new code; PopGuard's docs no longer mention Flutter's PopScope. The
maintainer pointed out we don't have a 8.0 commitment yet.
Tests
-----
* 19 plugin tests pass: ClassScanner, Aasa/AssetLinks builders, and an
end-to-end RouteAnnotationProcessorTest that compiles @route fixtures,
runs the processor, loads the generated Routes class in a child
classloader, dispatches URLs, and verifies the path / method dispatch.
* Full core unit-test reactor still green (verify clean).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
CI Linux JDK 8 sees Display.getInstance().dispatcher null after bootstrap while local Mac JDK 8 has it set. Add prints so CI surfaces which classloader sees which Display, whether installRouteDispatcher was called, and identity hashes. To be reverted once the cause is understood.
Conflict was in CodenameOne/src/com/codename1/annotations/package-info.java where both sides added the file. PR #5040 on master shipped a broader description covering the whole annotations package (build pipeline + ParparVM bytecode translator) while this branch had a routing-only blurb. Took master's wording verbatim; it accurately covers the new @route / @RouteParam additions as build-pipeline annotations alongside the existing ParparVM-direction ones. Also aligned the two other new package-info.java files (com.codename1.router, com.codename1.router.generated) to master's convention: a `///` description with no copyright header, matching io/package-info.java, ui/package-info.java, and the package-info files PR #5040 added. Tests still pass: 19 plugin tests + the routing parts of core remain green locally.
The previous shape exposed only declarative @route annotations and relied on platform deep-links to drive navigation. That left no easy way to trigger routing from app code (other than `new MyForm().show()`, which bypasses the route table). Add a tiny imperative API alongside: Navigation.navigate("/users/42"); // push by URL through the route table Navigation.back(); // pop Navigation.getCurrent(); // top-of-stack entry Navigation.getStack(); // breadcrumb-ordered snapshot Navigation.popTo(entry); // pop back to a specific entry Five static methods plus a value type (NavigationEntry { path, form, title }). The Navigation stack only tracks URL-driven calls; raw `Form.show()` still works untouched, so application code picks declarative or imperative per call site. Under the hood -------------- * RouteDispatcher.dispatch(String) now returns Form (or null) instead of void. The caller -- Navigation -- pushes the stack and calls show(). * The build-time-generated Routes class is the only RouteDispatcher implementation; its bootstrap() now installs onto Navigation rather than Display. * Display loses installRouteDispatcher / dispatchUrlInternal. The AppArg URL path on Display.setProperty routes through Navigation.dispatchExternalUrl, which handles the EDT hop and delegates to Navigation.navigate. External deep links and in-app navigations share one stack. * The generated Routes.dispatch emits branches most-specific first (literals > params > catch-all) so the right route wins when several patterns overlap; first match returns directly. Tests ----- 20 plugin tests pass locally, including: * `classLevelRouteWithPathVariableIsDispatched` -- @route on a Form subclass with a @RouteParam constructor binding. * `methodLevelRouteFactoryIsDispatched` -- @route on a static factory method. * `navigationStackSupportsBackAndPopTo` -- navigate/back/popTo round- trip over a three-form stack. * The four existing fail-fast validation tests (non-Form target, empty pattern, missing leading slash, duplicate pattern across classes). Docs ---- Adds a "Navigate from app code" section to docs/developer-guide/Deep-Links-Routing.asciidoc covering the five-method API and a breadcrumb-render example.
…path The plugin's RouteAnnotationProcessorTest previously shipped local stubs for cn1-core types so the in-process javac call could resolve @route / @RouteParam / Form. On Linux JDK 8 the stubs' classloader and the real cn1-core jar (pulled in transitively elsewhere) ended up shadowing each other, producing "dispatcher null after bootstrap" failures. This swap: - Deletes the test stubs entirely. - Adds codenameone-core as a test-scope dependency on the plugin so the real annotations and Form are on the test classpath. - Teaches JavaSourceCompiler to consume `surefire.test.class.path` (and to fall back to the current URLClassLoader's URL list) when building the compiler classpath, because surefire 3.x only puts the booter jar on java.class.path. - Rewrites the test to validate the generated Routes.class structurally via ASM rather than invoking it -- avoids the classloader-sharing pitfalls that motivated the rewrite in the first place. All 84 plugin tests pass locally on JDK 8. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The catch (Exception ignored) swallowing block introduced for the URLClassLoader walk tripped SpotBugs' forbidden-rule gate on Linux JDK 8 PR-CI (build-test (8)). Extract the URL->File conversion to a small helper that catches only the documented exceptions (URISyntaxException, IllegalArgumentException) and returns null on failure, so the call site can simply skip the entry. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Three forbidden PMD violations on Linux JDK 8 PR-CI were blocking the build: * `CompareObjectsWithEquals` in `Navigation#popTo` -- `popTo` matches by reference identity (callers pass back the same `NavigationEntry` they pulled out of `getStack()`), and since `NavigationEntry` doesn't override `equals` the inherited `Object#equals` is reference equality, so the switch from `==` to `.equals(...)` is semantics-preserving. * `UnnecessaryFullyQualifiedName` on `java.util.Collections.sort(..., new java.util.Comparator<...>())` in `RouteAnnotationProcessor` -- import them and drop the qualifiers. * `UnusedLocalVariable` on `literalCount` in `emitRouteBranch` -- it was a leftover from an earlier specificity-ranking sketch that's no longer used (specificity is computed elsewhere). Also dropped the unused `Arrays` import. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Previously cn1-core shipped a no-op stub `com.codename1.router.generated.Routes` that the Codename One Maven plugin overwrote in the project's `target/classes` at `process-classes` time. That works on the JavaSE simulator (URLClassLoader honors the project's classes first), but it is hostile to platforms that translate bytecode -- parparvm and Android both have to deal with two definitions of the same class living in different jars and can produce ambiguous output. Shadowing a framework class with a per-project class is exactly what the stub mechanism in the builders is meant to avoid: the generated class belongs in the per-build stub area, not in the framework jar. Changes: * Delete `CodenameOne/src/com/codename1/router/generated/` so the cn1-core jar no longer carries a Routes class. * `Display.init()` resolves `Routes.bootstrap()` via reflection and silently no-ops on `ClassNotFoundException`. A project without any `@Route` produces no Routes class -- `Display` just doesn't install the dispatcher. * `RouteAnnotationProcessor.finish()` no longer references "the framework stub" when there are no project routes -- there is no stub to leave alone. * Developer guide updated to call out why the dispatcher is per-project. The Maven plugin's `process-annotations` Mojo continues to be the place that writes the generated dispatcher into the project's `target/classes` at an early build step; the server-side builders receive the project bundle with that class already in it (or no class at all when the app has no `@Route`). No builder changes are required. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The Ant CN1 core build (build-test 8/17/21) compiles against the CLDC11
profile in Ports/CLDC11. CLDC11's Class.java exposes forName and
newInstance but not getMethod, so the previous
`Class.forName(...).getMethod("bootstrap").invoke(null)` form did not
compile against that bootclasspath.
Switch to a self-registering constructor: the generated Routes class
now declares a public no-arg ctor that calls
Navigation.setDispatcher(this), and Display#init() does
Class.forName(...).newInstance() to trigger it. Both calls are in
CLDC11. Test updated to introspect <init> rather than the removed
static bootstrap method.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Two style issues flagged by Microsoft.Contractions and Microsoft.Adverbs on the paragraph describing the per-project Routes dispatcher: * "it is" -> "it's" * drop the "silently" qualifier Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Class.forName lookups for the route dispatcher would silently break in shipped builds (ParparVM and Android obfuscate user classes) even though they pass in the simulator. The correct injection point is the application stub each builder writes per build: it's compiled against the project's classpath, so a direct symbol reference is preserved by the same keep-rules that cover the rest of the stub. * Display.init no longer touches Routes. * RouteAnnotationProcessor.emitStubs now unconditionally writes a no-op Routes.java to target/generated-sources/cn1-annotations during the generate-sources phase. process-classes still overwrites it with the real dispatcher when @route declarations are present. Projects with zero @route still compile and run -- the no-op constructor simply doesn't install a dispatcher. * iOS builder (server-side BuildDaemon and the in-repo maven plugin's IPhoneBuilder) emit `new com.codename1.router.generated.Routes();` immediately before `Display.init(stub)` in the generated Stub.java. * Android builders (server-side BuildDaemon AndroidBuilder / AndroidGradleBuilder and the in-repo plugin's AndroidGradleBuilder) emit the same call in front of the first Display init in onResume. The reinit branches don't repeat the binding because the dispatcher is held statically by Navigation and survives a Display reinit. * Desktop production stub (cn1app-archetype template + the existing hellocodenameone example) binds Routes immediately before Display init too. * Developer guide explains the per-build injection model. Note: the simulator path (`mvn cn1:run`) currently does not auto-bind because the simulator's launcher is the cn1-core JavaSE port's Executor, which has no compile-time reference to a per-project class. `cn1:run-desktop` (which goes through the user's *Stub) does bind correctly. Will revisit simulator binding separately if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Three follow-ups to the per-build-stub binding switch: 1. The archetype-generated common/pom.xml now declares the generate-annotation-stubs and process-annotations executions, so every freshly archetyped project has a com.codename1.router.generated .Routes class on its classpath -- the per-platform stub's direct symbol reference resolves. Same update applied to the existing hellocodenameone sample. 2. The JavaSE simulator (Ports/JavaSE) now installs the dispatcher via Class.forName + newInstance in Executor#runApp just before Display.init. The simulator is the legitimate place for dynamic loading -- it runs unobfuscated, already spins its own ClassPathLoader, and Executor is already heavily reflective for loading the user's main class. For ParparVM iOS and Android the per-build application stub continues to bind by direct symbol reference; obfuscation rewrites both call site and target class together. 3. Vale flagged a 'do not' contraction in the routing doc; switched to 'don't'. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Collaborator
Author
|
Compared 11 screenshots: 11 matched. |
…OCATION LanguageTool flagged "Direct symbol references survive obfuscation" suggesting a missing preposition; rephrase as "are preserved under obfuscation" which sidesteps the rule and reads about the same. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The previous push always emitted `new com.codename1.router.generated .Routes()` into the per-platform application stub, but projects that predate the annotation Mojo (e.g. input-validation-app, the demos under docs/demos) don't have a Routes class on their classpath and the generated stub failed to compile. Check classesDir for `com/codename1/router/generated/Routes.class` before emitting the install line. Projects that wire up the annotation Mojo get the binding; legacy projects skip it (they had no routing support to begin with). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Previous push checked classesDir/com/codename1/router/generated/Routes .class to decide whether to emit the install line into the per-platform stub. That gives false positives: the builder unzips CN1 framework jars (iOSPort.jar, nativeios.jar, etc.) into classesDir before stub generation, and if any of those framework jars happens to carry a Routes.class the check fires for a project that never opted in. The generated stub then references com.codename1.router.generated.Routes but the javac classpath used to compile the stub doesn't necessarily include the framework jar, so compilation fails with "package com.codename1.router.generated does not exist". Inspect sourceZip (the project's jar-with-deps) directly. cn1-core itself ships no Routes class, so a hit in sourceZip is the project's own annotation-Mojo emission. Same change applied to both iOS and Android builders, locally and in BuildDaemon. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The CI cache key for the "Restore built CN1 + iOS port artifacts" step hashes CodenameOne/src + Ports/iOSPort + vm/* + Themes + native-themes but not maven/codenameone-maven-plugin/src. When a PR only touches the Maven plugin (e.g. the builder Stub generation logic), the cache key matches an older entry built before the change, so the iOS / scripts / input-validation workflows restore a stale codenameone-maven-plugin jar and the freshly-pushed builder fix never runs. Add maven/codenameone-maven-plugin/src/main to the SRC_HASH find list across all six iOS-related workflows so plugin changes invalidate the cache. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* CN1 copyright header restored on Navigation.java and NavigationEntry .java, and the floating class-level Javadoc moved out from above the package declaration to where Javadoc actually attaches (immediately before the type). Package-info.java files keep package-level docs above the package statement as they always have. * Stop hardcoding `new com.codename1.router.generated.Routes()` in application source. The archetype-generated `*Stub.java` template and the existing hellocodenameone sample stub no longer reference Routes; the JavaSE port instead installs the dispatcher in its `postInit()` override via `Class.forName(...).newInstance()`, which covers both the simulator-driven entry (Executor) and the desktop-production entry (per-app stub) without a static dependency in user code. Removed the now-redundant Class.forName from the JavaSE Executor. * Remove the orphan "Note: the per-project route dispatcher..." comment from Display.init -- it didn't annotate any code after the Class.forName itself was removed. * RouteAnnotationProcessor no longer emits a no-op Routes stub at generate-sources. Routes class now exists only when the project actually has `@Route` declarations, so the per-platform application stub's `zipHasRoutes(sourceZip)` check correctly skips the install line when there's nothing to install. * Wire the annotation Mojo into scripts/initializr's common/pom.xml the same way as the archetype, so projects bootstrapped from that sample also pick up routing out of the box. (The user shouldn't have to configure the Mojo themselves.) * Developer guide trimmed: dropped the "Wire the build" section (auto-configured by the archetype/initializr templates), replaced the "Enable Associated Domains in Xcode" prose with the existing `ios.associatedDomains` build hint (the iOS builder already writes the entitlement), and replaced the verbose `<intent-filter>` example with a reference to the existing `android.xintent_filter` build hint. Both build hints already appear in the build hints table. Added a brief "How it works" section explaining the build-time scan + per-platform-stub binding in broader terms. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
After deleting `RouteAnnotationProcessor.emitStubs` the `generate-annotation-stubs` Mojo became a no-op for routing (the only processor that exists). Dropping the execution out of the archetype and the hellocodenameone sample keeps the generated pom focused on goals that actually do work; the build hint table and the developer guide already point users at `process-annotations` as the single goal they need. Revert the matching `scripts/initializr/common/pom.xml` edit entirely: that project is pinned to the released `7.0.244` plugin to demonstrate "users on the released version" and the new process-annotations goal doesn't exist there yet (the Hugo website build runs `mvnw package` against it and was failing with MojoNotFoundException). The initializr resource templates (barebones-pom.xml, kotlin-pom.xml, ...) likewise stay on the released goal set; they can pick up `process-annotations` when the next CN1 release ships with the new Mojo. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…uter-and-deep-links
The iOS and Android local builders both reinvented the same ZipFile probe and the same install-line emitter for the build-time-generated @route dispatcher. Move both helpers onto the shared Executor: * `Executor#projectHasRouteDispatcher(sourceZip)` -- the ZipFile probe. * `Executor#routeDispatcherInstallSource(sourceZip, indent)` -- the stub-source fragment (empty when the project ships no Routes class, so legacy CN1 apps without the annotation Mojo still produce a clean stub). IPhoneBuilder and AndroidGradleBuilder now just splice in the helper output, which keeps the per-platform code short and lets the comment explaining the obfuscation contract live in one place. 84/84 plugin tests still green. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
com.codename1.router(URL-based navigation, route guards, redirects, per-tab navigation shell, location listeners) layered on top of the existingForminfrastructure —Form.show()/showBack()continues to work unchanged.Display.setDeepLinkHandler(LinkHandler)receiving a normalizedDeepLink(scheme/host/path/segments/query/fragment); auto-invoked for cold + warm launches. iOS / Android need no port changes —Display.setProperty("AppArg", url)now routes URL-shaped values through the handler.AnnotationProcessorSPI + two new Mojos (cn1:generate-annotation-stubs,cn1:process-annotations). ASM-scanstarget/classes, dispatches to every registered processor, fails-fast with a combined error list. Adding more annotations later is a matter of dropping a new processor intoMETA-INF/services— no orchestrator changes.RouteAnnotationProcessorvalidates@Route("/path")classes (extendsForm, non-empty path starting with/, accessible constructor, no duplicate patterns) and emits aRoutesIndex+RoutesIndex$Builderdirectly via ASM. No source-text regex; no runtime reflection.Form.setPopGuard(PopGuard)(Flutter-PopScopeanalogue) honored from hardware back, toolbar back, andRouter.pop().Sheet.showForResult()returning anAsyncResource<T>that auto-cancels withnullon dismiss.TabsForm— aFormwhose tabs each keep their own navigation stack.AasaBuilder,AssetLinksBuilder).window.historybridge:JsRouterBootstrap+ standalonecn1-router-history.jsshim.Docs
Two new pages under
docs/developer-guide/, both wired into the masterdeveloper-guide.asciidoc:Routing-And-Deep-Links.asciidoc— reference covering every part of the API plus iOS Universal Links / Android App Links setup and the extension recipe for adding new annotation processors.Tutorial-Routing-And-Deep-Links.asciidoc— end-to-end tutorial from an empty project to a working deep-linkable app with@Route, guards, and a tab shell.Asciidoctor lint passes locally with
--failure-level WARN.Test plan
DeepLink,RouteMatch,Router,PopGuard,AasaBuilder,AssetLinksBuilder. All 2608 core tests pass against the local-dev-javase profile.@Route-annotated fixtures withjavac, runsRouteAnnotationProcessor, loads the generatedRoutesIndexbytecode in a child classloader, and invokesregister()against a stubRouterthat records every call. Negative tests cover non-Form targets, empty pattern, missing leading slash, duplicate pattern, and abstract class targets.mvn verifyoncore-unittests(SpotBugs / PMD / Checkstyle) and oncodenameone-maven-plugin(SpotBugs): zero new SpotBugs findings on the touched modules.Notes on the build-time scanner
The earlier source-regex prototype was scrapped — the user pushed for a fail-fast bytecode-based scanner sharing infrastructure with the existing ASM passes (
BytecodeComplianceMojo's String.split rewrite is the prior art). The new framework runs inprocess-classes, so it sees the JVM's view of the project rather than a textual approximation, and any validation issue aborts the build with class + reason before any generated.classlands on disk.A small compile-time stub source (a no-op
RoutesIndexwithregister()) is emitted undertarget/generated-sources/cn1-annotations/duringgenerate-sourcesso application code that referencesRoutesIndex.register()resolves at compile time.process-classesoverwrites the stub's.classwith the real registrations.Bumped surefire from 2.22.1 to 3.2.5 in the plugin pom
The older surefire silently skipped every JUnit test in this module under JDK 8 (
Tests run: 0even thoughAppTestwas present). The bump matches the version already pinned by the parent reactor pom. Vintage engine is included so existing JUnit 4 tests in the module keep working.🤖 Generated with Claude Code