Skip to content

Swing, SwingX and JavaFX compatibility layers: desktop Java apps as Codename One apps - #5980

Open
shai-almog wants to merge 279 commits into
masterfrom
desktop-compat-layer
Open

shai-almog wants to merge 279 commits into
masterfrom
desktop-compat-layer

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

What this adds

An existing Swing or JavaFX project builds as a Codename One application for every target, the way an Android module does with the Android compatibility layer. The sources are imported with cn1:import-desktop-project into common/src/main/desktop and built unmodified.

  • maven/swing-compat: java.awt, Java2D, javax.swing, java.beans, javax.imageio, a SwingX subset and the FlatLaf entry points, written over Codename One components. Authored already relocated, because the JDK owns those package names; applications compile against the JDK's Swing.
  • maven/javafx-compat: the javafx.* subset (scene graph, controls, charts, layout panes, properties and bindings, animation, effects, Canvas), authored under the real names.
  • maven/fxml-compiler: FXML and JavaFX CSS compiled at build time; nothing on a device parses XML or reflects.
  • maven/compat-jdk: the JDK classes a device lacks (streams, java.util.function, java.nio.file, regex, CompletableFuture, java.time gaps and more), shared with the Android layer.
  • maven/build-engine: one composed relocation pass over every layer, generated entry points and registries, records and pattern switches desugared, and the compliance check extended so that unsupported API is a build error reported under its original name and source line.
  • Maven and Gradle plugin wiring, the archetype profile, samples (scripts/desktop-compat-samples), screenshot tests in hellocodenameone, an integration test, a real-port interaction harness (maven/integration-tests/desktop-compat-realport) and a benchmark harness (scripts/desktop-compat-benchmarks).
  • Developer guide chapter docs/developer-guide/Desktop-Interop.asciidoc, with measured size, memory and start-up tables.

Verified locally

  • Unit tests green: compat-jdk 276, swing-compat 731, javafx-compat 496, fxml-compiler 45, build-engine 2561 (3 skipped), Maven plugin 439, Gradle plugin 24.
  • SpotBugs: zero findings in the seven modules that produce a report.
  • Control characters, author and since tags, copyright headers, package-info, cast semantics, build-hint catalog, workflow validation and the markdown doc validator pass.

Not verified / known gaps

  • The new workflows (desktop-compat.yml additions, desktop-compat-benchmarks.yml) have never run on GitHub. Windows is unmeasured; macOS runtime numbers need run-macos-bench.sh.
  • Screenshot goldens for the new DesktopCompat* tests have to be seeded from the first CI run.
  • Native Linux port issues found while measuring, not fixed here yet: window size is fixed at 800x600; clips under a transform cull children of a scaled node (one JavaFX game draws only its grid); images are never freed (an image-per-frame canvas application grows without bound); typed text is not visible in text fields.
  • JavaSE port: Ctrl/Alt/Meta key combinations do not reach the application, so accelerators and mnemonics are inert.
  • The cloud builder needs a companion change before cloud builds pick the layers up.

🤖 Generated with Claude Code

shai-almog and others added 30 commits October 8, 2026 19:42
The compliance check reads the registry's members from the shared JDK classes' jar, so the generated class's references to itself are checked against the placeholder. Give it the same constructor and methods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d Locale a device lacks

A class the device has cannot be moved whole by a JDK_SHIMS row, so CompatRewrites redirects single calls, static field reads, constructors and method reference handles to Resources, JdkObjects and JdkLocale. It runs ahead of the relocation, only when a desktop layer is active, and leaves the shared JDK classes and the Android runtime alone. Anything else of ClassLoader has no rule and stays a build error.

Adds the JDK_SHIMS rows for Properties, UUID and the executors, and the dependency on the shared JDK classes that the resource flattening shares its naming rule through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e reads them through

CompatResources writes every nested file of the desktop resource directories to the root of the classes directory under ResourceNames.flatName, removes the nested copy, and fails the build when that name belongs to another file. It generates com.codename1.compat.jdk.CompatRegistry (and its part classes) with the shipped paths, the properties bundles and the bundle classes, created with new.

CompatRemapper takes the directories through withResourceDirectories, defaulting to the resources directory beside the desktop entry record; the Maven goal adds the generated desktop resources.

The end-to-end test compiles an application with javac, remaps it, passes the compliance check against the real device library and runs it against a bundle holding flat names only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e size hints

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nent, list and split pane keys, click counts by place

Also: the lifecycle exits through the exit hook once the last window it
showed is disposed of, synthesized wheel pointers no longer become mouse
events, and the click tests no longer sleep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nitor, blocking as on the desktop

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code fixes where the shape allowed it; scoped exclusions, in the slice's
own block, where the JDK's API forces the pattern. The table and tree
tests no longer sleep out the click count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The decorator package (highlighters and predicates), the renderer
package (string/icon values, providers, the table, list and tree
renderers), the sort controllers, the hideable columns with their
column model and factory, and JXTable on top of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…us bar, hyperlink

JXTitledPanel, JXHeader, JXCollapsiblePane, JXTaskPane and its container,
VerticalLayout and HorizontalLayout, JXStatusBar with its constraints, and
JXHyperlink with the hyperlink actions, which open an address through
Codename One.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ext fields

JXMonthView draws one month and selects through the SwingX date selection
models; JXDatePicker is a Codename One date Picker that keeps a month view
in step. PromptSupport and the JX text fields map the prompt to the hint
of the text field behind the component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbstractPainter and the layout and area bases, the matte, compound,
rectangle, text, image, gloss, pinstripe, checkerboard, busy, alpha and
shape painters, and JXLabel, JXButton, JXBusyLabel and JXTitledSeparator
over them. Tests draw into a recording graphics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ighlighters and rollover

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ides columns

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lifecycle and input events

Node, Parent, Group, Region and Pane with one lazily created Codename One
peer per node, the JavaFX layout pass pushing bounds into the peers in
device pixels, capture and bubble dispatch of pointer and key input,
focus, hover and pressed state, the stage hosts (form, window, dialog),
Application, Platform and FxLifecycle, the styling hooks, and the first
controls (Control, Labeled, ButtonBase, Button, Label).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ene graph to zero SpotBugs findings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HBox, VBox, BorderPane, StackPane, GridPane (with ColumnConstraints,
RowConstraints and ConstraintsBase), FlowPane, TilePane and AnchorPane,
with the per child constraints, grow and shrink distribution, margins,
spans, percentages, wrapping and anchors, and the style hooks the
styling contract promises for them. LayoutPanesTest holds each pane to
child bounds worked out by hand from the documented layout rules.

Region gains a package-private way to position a child inside a margin
without resizing it, which BorderPane needs for its edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ab panes

TextInputControl, TextField, PasswordField, TextArea, CheckBox, ToggleButton, RadioButton, Toggle, ToggleGroup, Hyperlink, Separator, Slider, ProgressIndicator, ProgressBar, ScrollPane, SplitPane, TabPane, Tab, SelectionModel, SingleSelectionModel and IndexRange, each over the matching Codename One component where one exists. The scroll, split and tab panes are built from scene-graph regions so their content stays a real descendant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
javafx.animation: Animation, Timeline, KeyFrame, KeyValue, Interpolator,
AnimationTimer, Transition and the pause, fade, translate, scale, rotate,
sequential and parallel transitions, all driven by one frame clock
(runtime.FrameClock) that registers with the form on screen only while
something runs and has a manual mode for tests.

javafx.concurrent: Worker, WorkerStateEvent, Task and Service. Codename
One has no java.util.concurrent beyond the atomic classes, so a Task is a
Runnable rather than a FutureTask, has no blocking get(), and a Service
has no executor property. The hand-over between threads uses atomics and
Platform.runLater only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds javafx.scene.shape (Shape and its subclasses, Path and its
elements, SVGPath with a complete path data parser), javafx.scene.text.Text,
javafx.scene.transform (2D), javafx.scene.canvas and javafx.scene.image.
Everything draws through the one Renderer over FxPath, which gain an
elliptical arc, an elliptical round rectangle and a clip.

Node.getTransforms() is folded into the paint matrix and so into
localToParent, bounds and picking. A canvas keeps a display list that is
replayed on paint and is bounded by clearRect and a cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…logs

ListView and TableView show their items in recycled cells over a shared
row flow, with single and multiple row selection. ComboBox, ChoiceBox
and DatePicker sit on the Codename One picker. ContextMenu, Menu and
MenuBar open as popups in a layer over the owner's form, which every
host reaches through SceneInput. Dialog, Alert, TextInputDialog and
ChoiceDialog block through a modal stage. Control gains a context menu
and a tooltip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(T) of an absent item as JavaFX has it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…llapsible pane's toggle action, action accelerators

The click tests no longer wait the click count out; the test base resets it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…odelSupport

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… classes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yout

A text area given its text inside a scroll pane overflowed the stack on the
pane's next layout: JViewport.doLayout showed the caret, showing a rectangle
validated the viewport, and the viewport -- still invalid, because it was in
the middle of that very layout -- laid itself out again.

Container.validateTree and JViewport.doLayout no longer start a pass that is
already under way, which covers the same shape in JList.ensureIndexIsVisible,
ScrollDelegate.reveal (JTable, JTree) and JComponent.scrollRectToVisible. The
caret is shown after the widget received the text that moved it, not before,
so an appended log is followed to its last line. JXErrorPane's workaround is
gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maven/swing-compat and maven/javafx-compat are relocated into an application
and translated by ParparVM with it, where a failed cast is not an exception.
Both are roots of check-cast-semantics.sh now, and the PR build compiles them
so that --require-all finds them.

The six findings were all in javafx.beans.binding.Bindings: the create*Binding
factories read the result of an erased Callable inside a try that catches
Exception. The result is read as an Object and tested with instanceof.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shai-almog and others added 2 commits October 11, 2026 18:02
…d iOS Metal

Also reseeds the five Linux goldens that changed when the port began to
honour bold and italic native fonts and scaled images, and the JavaScript
ButtonTheme pair whose flat button gradient the GeneralPath clip fix
corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 183 screenshots: 183 matched.

Native Android coverage

  • 📊 Line coverage: 18.09% (44224/244446 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 16.81% (209052/1243859), branch 11.61% (15555/133996), complexity 9.46% (12173/128732), method 14.38% (8737/60772), class 25.00% (1709/6835)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • com.codename1.androidcompat.rt.com.codename1.androidcompat.rt.TextXmlPullParser – 0.00% (0/623 lines covered)
      • com.codename1.androidcompat.androidx.constraintlayout.core.widgets.com.codename1.androidcompat.androidx.constraintlayout.core.widgets.Flow – 0.00% (0/576 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 18.09% (44224/244446 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 16.81% (209052/1243859), branch 11.61% (15555/133996), complexity 9.46% (12173/128732), method 14.38% (8737/60772), class 25.00% (1709/6835)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • com.codename1.androidcompat.rt.com.codename1.androidcompat.rt.TextXmlPullParser – 0.00% (0/623 lines covered)
      • com.codename1.androidcompat.androidx.constraintlayout.core.widgets.com.codename1.androidcompat.androidx.constraintlayout.core.widgets.Flow – 0.00% (0/576 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 225ms / native 138ms = 1.6x speedup
SIMD float-mul (64K x300) java 200ms / native 108ms = 1.8x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 87.000 ms
Base64 CN1 decode 83.000 ms
Base64 native encode 466.000 ms
Base64 encode ratio (CN1/native) 0.187x (81.3% faster)
Base64 native decode 276.000 ms
Base64 decode ratio (CN1/native) 0.301x (69.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 198 screenshots: 198 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

ParparVM vs HotSpot (JDK 25): Linux x64

Runner CPU: AMD EPYC 9V74 80-Core Processor (baseline linux-x64@amd-epyc-9v74-80-core-processor)

1 performance regression:

  • translator (self) on all 4 CPUs: RAM 0.50x against a 0.43x baseline (+16.7%, tolerance 15%)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in vm/selfhost/perf-baseline/ fails: above it is a regression, below it an improvement that has to be rebaselined (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (LinuxHelloMain) 4 0.99x (base 0.90x, +9.9%) 1.07x (base 0.96x, +11.2%) ok
translator (self) 4 0.59x (base 0.63x, -6.8%) 0.50x (base 0.43x, +16.7%) REGRESSION (RAM +16.7%)
intArithmetic 4 1.10x (base 1.10x, -0.0%) 0.07x (base 0.06x, +33.4%) ok
longArithmetic 4 1.08x (base 1.08x, +0.0%) 0.05x (base 0.06x, -1.2%) ok
mathTranscendental 4 1.09x (base 1.09x, +0.0%) 0.08x (base 0.07x, +14.0%) ok
arraySequential 4 2.62x (base 2.81x, -6.6%) 0.36x (base 0.35x, +0.3%) ok
arrayRandom 4 1.06x (base 1.02x, +3.9%) 0.22x (base 0.21x, +6.9%) ok
objectAllocation 4 1.22x (base 1.21x, +0.8%) 0.31x (base 0.39x, -19.2%) ok
valueEscape 4 0.10x (base 0.10x, +0.0%) 0.06x (base 0.06x, -1.0%) ok
hashMapChurn 4 1.10x (base 1.10x, +0.5%) 0.10x (base 0.10x, -0.5%) ok
stringBuilding 4 1.51x (base 1.49x, +1.5%) 0.26x (base 0.26x, +0.4%) ok
recursion 4 1.25x (base 1.24x, +0.4%) 0.06x (base 0.06x, -1.0%) ok
quicksort 4 1.08x (base 1.07x, +1.2%) 0.13x (base 0.11x, +16.6%) ok

A change that moves performance ON PURPOSE records the new baseline in vm/selfhost/perf-baseline/pr/5980.json, with the reason, from this job's perf-results.json. The rebaseline is then part of this pull request's diff:

python3 vm/selfhost/calibrate-perf-baseline.py --pr 5980 --reason "<why these benchmarks moved>" perf-results.json

Result: performance regression

@shai-almog

shai-almog commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 198 screenshots: 198 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

ParparVM vs HotSpot (JDK 25): Linux arm64

Runner CPU: Neoverse-N2 (baseline linux-arm64@neoverse-n2)

1 improvement past the baseline, which fails until rebaselined:

  • hello (LinuxHelloMain) on all 4 CPUs: RAM 0.79x against a 1.10x baseline (-28.7%, tolerance 15%)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in vm/selfhost/perf-baseline/ fails: above it is a regression, below it an improvement that has to be rebaselined (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (LinuxHelloMain) 4 1.02x (base 0.93x, +9.6%) 0.79x (base 1.10x, -28.7%) IMPROVED: rebaseline (RAM -28.7%)
translator (self) 4 0.76x (base 0.70x, +8.5%) 0.49x (base 0.45x, +10.2%) ok
intArithmetic 4 1.05x (base 1.04x, +0.0%) 0.03x (base 0.03x, -0.8%) ok
longArithmetic 4 0.79x (base 0.79x, -0.1%) 0.03x (base 0.03x, -0.5%) ok
mathTranscendental 4 1.10x (base 1.10x, +0.0%) 0.03x (base 0.03x, +0.7%) ok
arraySequential 4 0.36x (base 0.36x, -0.9%) 0.37x (base 0.37x, +0.3%) ok
arrayRandom 4 0.94x (base 0.94x, +0.3%) 0.20x (base 0.20x, +0.1%) ok
objectAllocation 4 1.93x (base 1.77x, +9.4%) 0.37x (base 0.38x, -1.4%) ok
valueEscape 4 0.51x (base 0.51x, -0.1%) 0.03x (base 0.03x, -2.6%) ok
hashMapChurn 4 0.84x (base 0.84x, -0.0%) 0.11x (base 0.12x, -5.1%) ok
stringBuilding 4 1.49x (base 1.36x, +9.9%) 0.26x (base 0.26x, -0.2%) ok
recursion 4 1.41x (base 1.43x, -1.5%) 0.03x (base 0.04x, -1.8%) ok
quicksort 4 1.00x (base 0.99x, +0.8%) 0.09x (base 0.09x, +0.0%) ok

A change that moves performance ON PURPOSE records the new baseline in vm/selfhost/perf-baseline/pr/5980.json, with the reason, from this job's perf-results.json. The rebaseline is then part of this pull request's diff:

python3 vm/selfhost/calibrate-perf-baseline.py --pr 5980 --reason "<why these benchmarks moved>" perf-results.json

Result: improved past the baseline: rebaseline required

shai-almog and others added 3 commits October 11, 2026 20:47
BytecodeCompliance: the backend check master added runs inside this branch's
scanner. In that mode it reads what master's read and leaves bootstraps and
dynamic constants to checkBackendBootstraps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pt, iOS 27, tvOS and Mac Catalyst

Windows also reseeds AndroidCompatMain, SurfacesRasterizer and
graphics-draw-string, which now draw the bold and italic faces a derived
native font asked for, and DesktopMode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 198 screenshots: 198 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 44ms / native 2ms = 22.0x speedup
SIMD float-mul (64K x300) java 43ms / native 3ms = 14.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 61.000 ms
Base64 CN1 decode 72.000 ms
Base64 SIMD encode 91.000 ms
Base64 encode ratio (SIMD/CN1) 1.492x (49.2% slower)
Base64 SIMD decode 81.000 ms
Base64 decode ratio (SIMD/CN1) 1.125x (12.5% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.667x (33.3% faster)
Image applyMask (SIMD off) 14.000 ms
Image applyMask (SIMD on) 20.000 ms
Image applyMask ratio (SIMD on/off) 1.429x (42.9% slower)
Image modifyAlpha (SIMD off) 24.000 ms
Image modifyAlpha (SIMD on) 18.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.750x (25.0% faster)
Image modifyAlpha removeColor (SIMD off) 26.000 ms
Image modifyAlpha removeColor (SIMD on) 19.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.731x (26.9% faster)

@shai-almog

shai-almog commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Apple Watch (watchOS / Core Graphics)

Compared 249 screenshots: 227 matched, 9 updated, 13 missing references.

  • AndroidCompatWidgets — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    AndroidCompatWidgets
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as AndroidCompatWidgets.png in workflow artifacts.

  • ButtonTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ButtonTheme_dark.png in workflow artifacts.

  • ButtonTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ButtonTheme_light.png in workflow artifacts.

  • ChatInput_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ChatInput_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatInput_dark.png in workflow artifacts.

  • ChatInput_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ChatInput_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatInput_light.png in workflow artifacts.

  • DesktopCompatFxAlert — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatFxAlert.png.

    DesktopCompatFxAlert
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatFxAlert.png in workflow artifacts.

  • DesktopCompatFxControls — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatFxControls.png.

    DesktopCompatFxControls
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatFxControls.png in workflow artifacts.

  • DesktopCompatFxData — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatFxData.png.

    DesktopCompatFxData
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatFxData.png in workflow artifacts.

  • DesktopCompatFxFxml — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatFxFxml.png.

    DesktopCompatFxFxml
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatFxFxml.png in workflow artifacts.

  • DesktopCompatFxPanes — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatFxPanes.png.

    DesktopCompatFxPanes
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatFxPanes.png in workflow artifacts.

  • DesktopCompatFxShapes — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatFxShapes.png.

    DesktopCompatFxShapes
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatFxShapes.png in workflow artifacts.

  • DesktopCompatSwingContainers — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatSwingContainers.png.

    DesktopCompatSwingContainers
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatSwingContainers.png in workflow artifacts.

  • DesktopCompatSwingControls — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatSwingControls.png.

    DesktopCompatSwingControls
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatSwingControls.png in workflow artifacts.

  • DesktopCompatSwingOptionPane — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatSwingOptionPane.png.

    DesktopCompatSwingOptionPane
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatSwingOptionPane.png in workflow artifacts.

  • DesktopCompatSwingPainting — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatSwingPainting.png.

    DesktopCompatSwingPainting
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatSwingPainting.png in workflow artifacts.

  • DesktopCompatSwingTables — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatSwingTables.png.

    DesktopCompatSwingTables
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatSwingTables.png in workflow artifacts.

  • DesktopCompatSwingTrees — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatSwingTrees.png.

    DesktopCompatSwingTrees
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatSwingTrees.png in workflow artifacts.

  • DesktopCompatSwingX — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-watch/DesktopCompatSwingX.png.

    DesktopCompatSwingX
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DesktopCompatSwingX.png in workflow artifacts.

  • PaletteOverrideTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    PaletteOverrideTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PaletteOverrideTheme_dark.png in workflow artifacts.

  • PaletteOverrideTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    PaletteOverrideTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PaletteOverrideTheme_light.png in workflow artifacts.

  • ShowcaseTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ShowcaseTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ShowcaseTheme_dark.png in workflow artifacts.

  • ShowcaseTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ShowcaseTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ShowcaseTheme_light.png in workflow artifacts.

@shai-almog

shai-almog commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 180 screenshots: 180 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 67ms / native 2ms = 33.5x speedup
SIMD float-mul (64K x300) java 84ms / native 3ms = 28.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 70.000 ms
Base64 CN1 decode 80.000 ms
Base64 native encode 950.000 ms
Base64 encode ratio (CN1/native) 0.074x (92.6% faster)
Base64 native decode 324.000 ms
Base64 decode ratio (CN1/native) 0.247x (75.3% faster)
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.929x (7.1% faster)
Base64 SIMD decode 69.000 ms
Base64 decode ratio (SIMD/CN1) 0.863x (13.7% faster)
Base64 encode ratio (SIMD/native) 0.068x (93.2% faster)
Base64 decode ratio (SIMD/native) 0.213x (78.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 17.000 ms
Image createMask (SIMD on) 5.000 ms
Image createMask ratio (SIMD on/off) 0.294x (70.6% faster)
Image applyMask (SIMD off) 90.000 ms
Image applyMask (SIMD on) 63.000 ms
Image applyMask ratio (SIMD on/off) 0.700x (30.0% faster)
Image modifyAlpha (SIMD off) 75.000 ms
Image modifyAlpha (SIMD on) 58.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off) 73.000 ms
Image modifyAlpha removeColor (SIMD on) 72.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.986x (1.4% faster)

shai-almog and others added 2 commits October 12, 2026 20:19
… follows the runtime

The device runtime now has the streaming half of java.util.zip, so the
compliance advice and the porting table no longer send a zip stream to
com.codename1.io.gzip. Only ZipFile is still absent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

public boolean createNewFile() throws IOException {
if (exists()) {
return false;
}
File parent = getParentFile();
if (parent != null && !parent.exists()) {
throw new IOException("No such file or directory: " + getParent());
}
OutputStream out = fs().openOutputStream(storagePath());

P2 Badge Make createNewFile's existence check atomic

When two threads use createNewFile() for the same path, both can observe that it does not exist and both then open it for output, so both return true and the later open may truncate data written by the first caller. Desktop code commonly relies on this method's atomic create semantics for lock and sentinel files; serialize the check-and-create operation at least within the application process, or use an exclusive-create primitive where the port provides one.


if (append && fs.exists(p)) {
out = fs.openOutputStream(p, (int) fs.getLength(p));

P2 Badge Refuse append offsets that overflow the storage API

When appending to a file larger than Integer.MAX_VALUE, narrowing its length to int wraps the offset before it is passed to FileSystemStorage. Depending on the port this either fails or writes at an earlier position and corrupts the existing file instead of appending; since the underlying API cannot express larger offsets, detect this case and fail explicitly rather than truncating the position.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +536 to +537
if (JAVA_MAIN.matcher(code).find()) {
candidate(out, prefix + fileClass);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record the Java class that actually declares main

When public static void main belongs to a static nested class or another top-level class in the file, this records the source filename's class instead of the declaring class (for example, Outer rather than Outer$Launcher). The import therefore succeeds with an invalid entry record, and DesktopEntryPoints later rejects the outer class because it has no main method; determine the main method's enclosing declaration as is already done for nested JavaFX classes.

Useful? React with 👍 / 👎.

Comment on lines +163 to +165
outstanding.add(job);
int slot = next;
next = (next + 1) % workers.length;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serialize executor bookkeeping across concurrent submitters

When two threads call execute concurrently, both mutate the unsynchronized ArrayList and next fields here; concurrent ArrayList.add operations can overwrite one bookkeeping entry even though both jobs are queued on EasyThread. A subsequent shutdownNow() then cannot find and drop the lost entry, so that queued task may run after immediate shutdown; access to the acceptance check, outstanding list, worker selection, and shutdown paths needs one synchronization boundary.

Useful? React with 👍 / 👎.

Comment on lines +124 to +128
} else if (delayAfter > 0 && !outcome.isDone() && !shutdown.get()) {
again();
}
} finally {
running.set(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the running flag before scheduling the next fixed-delay run

For scheduleWithFixedDelay, again() installs a one-shot timer while running remains true until the finally block. With a short delay or a thread preemption at this point, the timer can fire first, fail the compare-and-set in run(), and return without scheduling another attempt; the periodic task then stops permanently while its future remains incomplete.

Useful? React with 👍 / 👎.

if (parent != null && !parent.isDirectory() && !parent.mkdirs()) {
throw new IOException("Cannot create " + parent);
}
Files.copy(src.toPath(), dest.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve unrecorded destination files during imports

When the destination already contains a developer-created file that was never listed in the previous import record, recorded is null and this unconditional REPLACE_EXISTING silently overwrites it. This occurs both on the first import into a nonempty desktop source tree and when an upstream project later adds a path the developer had independently added; treat an unrecorded existing destination as user-owned rather than replacing it.

Useful? React with 👍 / 👎.

Comment on lines +168 to +178
Entry entry = new Entry(jar.length() + "\t" + jar.lastModified());
entry.describe(lib);
Set<String> shipped = lib.shipped();
if (!shipped.isEmpty()) {
for (Map.Entry<String, List<String>> service : lib.reachedServices().entrySet()) {
for (String provider : service.getValue()) {
entry.services.add(service.getKey() + "\t" + provider);
}
}
}
boolean same = old != null && old.stamp.equals(entry.stamp);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fingerprint library contents before reusing unpacked classes

When a dependency jar is rebuilt in place with the same byte length and last-modified timestamp, this stamp marks it unchanged even if its entries differ. The call to unpack then treats all previously recorded files as current and skips reading their new bytes, leaving stale relocated classes and resources in the application; this can occur with reproducible artifacts or coarse-timestamp filesystems, so the reuse decision needs a content digest or entry-level comparison.

Useful? React with 👍 / 👎.

Comment on lines +402 to +407
public URI toUri() {
String storage = toFile().storagePath();
try {
return new URI(storage);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Escape path characters when constructing file URIs

When a path contains a space, #, %, or another character that must be escaped in a URI, passing the raw storage path to this constructor throws IllegalArgumentException; for example, Paths.get("a b").toUri() should return a URI containing a%20b. This also breaks File.toURI(), which delegates here, for ordinary filenames containing such characters; construct the URI from decoded path components so Java performs the required percent-encoding.

Useful? React with 👍 / 👎.

Comment on lines +77 to +78
if (has("<artifactId>" + artifactId + "</artifactId>")) {
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check module dependencies by full coordinates and scope

When the same artifactId appears anywhere else in the POM—such as only under dependencyManagement, as a plugin dependency, or under a different group—this global text check suppresses the actual module dependency and reports no manual action. The imported sources then compile without the library they require or against an unrelated artifact; deduplicate only matching groupId:artifactId entries in the module's own dependency list.

Useful? React with 👍 / 👎.

@shai-almog

shai-almog commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 192 screenshots: 192 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300) java 53ms / native 2ms = 26.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 53.000 ms
Base64 CN1 decode 63.000 ms
Image encode benchmark iterations 100
Image createMask (SIMD off) 18.000 ms
Image createMask (SIMD on) 17.000 ms
Image createMask ratio (SIMD on/off) 0.944x (5.6% faster)
Image applyMask (SIMD off) 113.000 ms
Image applyMask (SIMD on) 54.000 ms
Image applyMask ratio (SIMD on/off) 0.478x (52.2% faster)
Image modifyAlpha (SIMD off) 44.000 ms
Image modifyAlpha (SIMD on) 68.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.545x (54.5% slower)
Image modifyAlpha removeColor (SIMD off) 90.000 ms
Image modifyAlpha removeColor (SIMD on) 77.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.856x (14.4% faster)

ParparVM vs HotSpot (JDK 25): macOS arm64

Runner CPU: Apple M1 (Virtual) (baseline macos-arm64)

1 performance regression:

  • hello (HelloCodenameOne) on all 3 CPUs: RAM 1.93x against a 1.34x baseline (+44.4%, tolerance 20%)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in vm/selfhost/perf-baseline/ fails: above it is a regression, below it an improvement that has to be rebaselined (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (HelloCodenameOne) 3 0.93x (base 0.96x, -3.3%) 1.93x (base 1.34x, +44.4%) REGRESSION (RAM +44.4%)
translator (self) 3 0.57x (base 0.62x, -8.4%) 0.57x (base 0.57x, +1.5%) ok
intArithmetic 3 1.07x (base 1.03x, +3.6%) 0.12x (base 0.12x, +0.7%) ok
longArithmetic 3 1.05x (base 1.03x, +2.1%) 0.12x (base 0.07x, +66.1%) ok
mathTranscendental 3 0.97x (base 1.01x, -3.4%) 0.12x (base 0.12x, -0.7%) ok
arraySequential 3 0.43x (base 0.42x, +3.0%) 0.53x (base 0.53x, -0.0%) ok
arrayRandom 3 0.96x (base 0.99x, -2.9%) 0.32x (base 0.32x, -0.1%) ok
objectAllocation 3 0.97x (base 1.11x, -12.1%) 0.74x (base 0.81x, -8.6%) ok
valueEscape 3 0.51x (base 0.51x, +0.0%) 0.08x (base 0.09x, -7.2%) ok
hashMapChurn 3 1.05x (base 1.18x, -10.6%) 0.07x (base 0.07x, +9.7%) ok
stringBuilding 3 0.84x (base 0.84x, -0.6%) 0.41x (base 0.46x, -11.1%) ok
recursion 3 1.21x (base 1.24x, -2.3%) 0.12x (base 0.12x, +0.6%) ok
quicksort 3 1.01x (base 1.01x, -0.5%) 0.11x (base 0.11x, +0.5%) ok

A change that moves performance ON PURPOSE records the new baseline in vm/selfhost/perf-baseline/pr/5980.json, with the reason, from this job's perf-results.json. The rebaseline is then part of this pull request's diff:

python3 vm/selfhost/calibrate-perf-baseline.py --pr 5980 --reason "<why these benchmarks moved>" perf-results.json

Result: performance regression

@shai-almog

shai-almog commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 1000 ms
Simulator Boot (Run) 1000 ms
App Install 21000 ms
App Launch 1000 ms
Test Execution 453000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 81ms / native 3ms = 27.0x speedup
SIMD float-mul (64K x300) java 87ms / native 5ms = 17.4x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 122.000 ms
Base64 CN1 decode 59.000 ms
Base64 native encode 1706.000 ms
Base64 encode ratio (CN1/native) 0.072x (92.8% faster)
Base64 native decode 1464.000 ms
Base64 decode ratio (CN1/native) 0.040x (96.0% faster)
Base64 SIMD encode 62.000 ms
Base64 encode ratio (SIMD/CN1) 0.508x (49.2% faster)
Base64 SIMD decode 57.000 ms
Base64 decode ratio (SIMD/CN1) 0.966x (3.4% faster)
Base64 encode ratio (SIMD/native) 0.036x (96.4% faster)
Base64 decode ratio (SIMD/native) 0.039x (96.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.500x (50.0% faster)
Image applyMask (SIMD off) 98.000 ms
Image applyMask (SIMD on) 82.000 ms
Image applyMask ratio (SIMD on/off) 0.837x (16.3% faster)
Image modifyAlpha (SIMD off) 42.000 ms
Image modifyAlpha (SIMD on) 220.000 ms
Image modifyAlpha ratio (SIMD on/off) 5.238x (423.8% slower)
Image modifyAlpha removeColor (SIMD off) 234.000 ms
Image modifyAlpha removeColor (SIMD on) 230.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.983x (1.7% faster)

shai-almog and others added 2 commits October 13, 2026 07:52
- An import no longer writes over a file no import wrote.
- The entry record names the class a main is declared in, nested or a
  second top-level class included.
- The executor's bookkeeping is safe to submit to from several threads.
- A fixed-delay task is armed again only after its run has ended.
- A library jar's stamp includes a checksum of its entry listing.
- Path.toUri and File.toURI escape the path.
- An imported library is looked for in the module's own dependencies,
  by group and artifact.
- Appending to a file past the int offset range fails instead of wrapping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 534d0f01fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +210 to +211
int slot = (next.getAndIncrement() & 0x7fffffff) % workers.size();
worker(slot).run(job);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop workers created concurrently with shutdown

When a first submission passes the shutdown check and is paused before worker(slot), a concurrent shutdown() sees no worker to stop; the submitter can then create a new EasyThread and queue the accepted job after killWorkersWhenIdle() has finished. That worker is never asked to stop, so after the job completes isTerminated() remains false and awaitTermination() always times out. Recheck shutdown after worker creation or synchronize worker creation and shutdown.

Useful? React with 👍 / 👎.

if (ext.group(2).indexOf('.') < 0 && !imported) {
continue;
}
candidate(out, prefix + ext.group(1)).javafx = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record enclosing names for nested Kotlin JavaFX apps

For a valid nested Kotlin application such as class Host { class App : Application() { ... } }, the JVM class is Host$App, but this records only App. The import therefore writes a nonexistent mainClass, and DesktopEntryPoints later rejects it even though the compiled nested application is present; derive the enclosing declaration chain as the Kotlin @JvmStatic path already does.

Useful? React with 👍 / 👎.

Comment on lines +48 to +49
@Mojo(name = "remap-compat", defaultPhase = LifecyclePhase.PROCESS_CLASSES,
requiresDependencyResolution = ResolutionScope.COMPILE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve runtime-scoped libraries before compatibility remapping

A Maven desktop application can legitimately declare a plugin or service-provider jar with runtime scope, including a library whose bytecode uses Swing or JavaFX. This mojo requests only the compile dependency set, and the later artifact filter also accepts only SCOPE_COMPILE, so such a jar never reaches CompatLibraries and its toolkit references are not relocated before packaging. Request runtime dependency resolution and include runtime-scoped jars while continuing to exclude provided dependencies.

Useful? React with 👍 / 👎.

Comment on lines +654 to +655
copyTree(from, to);
deleteTree(from);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report failed source deletion during directory moves

When moving a directory between parents, File.renameTo falls back to this copy-and-delete path, but deleteTree ignores every failed deletion. If a child or the source directory cannot be removed, move still returns success with the source wholly or partially present alongside the copied target, so callers may proceed believing ownership was transferred. Propagate deletion failures as an IOException instead of unconditionally completing the move.

Useful? React with 👍 / 👎.

Comment on lines +177 to +179
@Override
public void run() {
EventQueue.invokeLater(tick);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Coalesce pending Swing timer events

When the event dispatch thread takes longer than the timer delay, every background firing reaches EventQueue.invokeLater even though coalesce defaults to true. A Swing Timer keeps only one pending event in this mode; here the event queue can grow without bound and then deliver a burst of stale actions after the event thread unblocks. Track whether a tick is already queued and suppress subsequent firings unless setCoalesce(false) was requested.

Useful? React with 👍 / 👎.

Comment on lines +137 to +140
if (repeats) {
timer.schedule(task, initialDelay, Math.max(1, delay));
} else {
timer.schedule(task, initialDelay);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor repeat changes made while the timer is running

When a timer is started with repeats disabled and setRepeats(true) is called before its first firing, Swing begins rescheduling it, but this chooses a one-shot task permanently during start(). The task fires once while tick() now sees repeats == true, so it neither repeats nor calls stop(); isRunning() remains true and subsequent start() calls are ignored. Schedule subsequent firings according to the current repeat flag rather than capturing it only at startup.

Useful? React with 👍 / 👎.

shai-almog and others added 2 commits October 13, 2026 12:31
…xes from review

- javax.swing.Timer coalesces the periods a busy event dispatch thread
  missed into one event, and asks whether it repeats at each firing.
- A fixed pool stops a worker that a submission started after shutdown
  had swept the workers, so it terminates; a stopping worker's refusal
  is a RejectedExecutionException.
- Files.move fails when the copied source cannot be deleted.
- remap-compat relocates runtime-scoped libraries, as the Gradle build
  does.
- The importer records a nested Kotlin JavaFX application as Outer$Nested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ends mid-drain

A period that elapses while the queue is drained is a new event, so the
coalesced count is bounded rather than exactly one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9919796706

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/// The `**/*.fxml` and `**/*.css` files under the desktop resources.
/// The task is skipped when there are none.
@InputFiles
@SkipWhenEmpty

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run cleanup when the last desktop resource is removed

When the final .fxml or .css file is deleted, @SkipWhenEmpty makes this task finish with NO-SOURCE without invoking DesktopResourceCompiler.run() (Gradle documentation). The compiler's cleanup for stale FxmlDocuments.java and .cn1css files therefore never runs, while both output directories remain attached to the main source set, so an incremental build can continue compiling and packaging resources that no longer exist. Allow the empty-input invocation to run so it can clear its previous outputs.

Useful? React with 👍 / 👎.

Comment on lines +213 to +216
if (millis < 0) {
signal.get();
} else {
signal.get((int) Math.max(1, Math.min(millis, Integer.MAX_VALUE)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate interrupts from blocking future waits

When a thread is blocked here on an incomplete future and another thread interrupts it, AsyncResource.get() ultimately waits through Util.wait, which catches InterruptedException and resumes waiting. Consequently CompletableFuture.get(), executor Future.get(), and CountDownLatch.await() cannot honor their declared interruption contract and may leave cancellation or shutdown code blocked until an unrelated completion occurs. Use an interrupt-aware wait path and propagate the interruption instead of relying on AsyncResource.get().

Useful? React with 👍 / 👎.

Comment on lines +73 to +74
declared = wireDesktopCompat(common, DesktopProjectImporter.moduleDir(source, module));
r = new DesktopProjectImporter(MavenLog.of(getLog())).importProject(source, module, common, mainClass,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the import before rewriting the destination POM

When entry-point selection later fails—for example, because the source project has multiple candidates and no main-class setting—wireDesktopCompat has already written compatibility and library dependencies into common/pom.xml. The command then reports that nothing was imported, but still leaves the destination project modified. Perform the non-mutating import validation before writing the POM, or restore the original POM when importProject fails.

Useful? React with 👍 / 👎.

/// Every match from here on, each as its own result.
public Stream<MatchResult> results() {
List<MatchResult> all = new ArrayList<MatchResult>();
reset();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve matcher position and region in results()

Calling reset() here makes results() restart at the beginning of the entire input and also discards any configured region. For example, after find() has consumed the first match, the JDK stream contains only the remaining matches, whereas this implementation includes the first match again; matcher.region(...).results() likewise searches outside the requested region. Build the results stream from the matcher's current state instead of resetting it.

Useful? React with 👍 / 👎.

if (formatter == DateTimeFormatter.ISO_LOCAL_TIME) {
return ISO_TIME_PATTERN;
}
return ISO_DATE_TIME_PATTERN;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the selected ISO formatter in withZone

For every unremembered formatter except ISO_LOCAL_DATE and ISO_LOCAL_TIME, this fallback assumes the local date-time pattern. Consequently common calls such as DateTimeFormatter.ISO_INSTANT.withZone(zone) and ISO_OFFSET_DATE_TIME.withZone(zone) lose their Z/offset and fractional-second formatting and instead emit a plain local timestamp. Distinguish all supported ISO constants rather than mapping them to ISO_DATE_TIME_PATTERN.

Useful? React with 👍 / 👎.

Comment on lines +54 to +56
if (BASIC.indexOf((char) b) >= 0) {
throw new IllegalArgumentException("Illegal base64 line separator character 0x"
+ Integer.toString(b, 16));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject padding characters in MIME line separators

A custom MIME separator containing = passes this validation because = is not in BASIC, even though it is Base64's padding character and the JDK rejects it. The encoder then inserts padding-looking bytes between complete groups; this implementation's own MIME decoder treats such an = at a group boundary as an invalid ending, so getMimeEncoder(4, new byte[]{'='}) can produce output that cannot be decoded. Include the padding character in the forbidden separator set.

Useful? React with 👍 / 👎.

Comment on lines +268 to +272
for (int i = KNOWN.size() - 1; i >= 0; i--) {
Known k = KNOWN.get(i);
DateTimeFormatter f = k.formatter.get();
if (f == null) {
KNOWN.remove(i);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Synchronize access to the formatter registry

When formatters are created or used from multiple threads, known() iterates and removes from the shared ArrayList while remember() can concurrently append to it. A removal can shrink the list after another thread has chosen its index, causing get(i) to fail, while racing appends can lose a formatter's metadata and make later withZone or format calls use the wrong fallback behavior. Protect all accesses to KNOWN with one synchronization boundary.

Useful? React with 👍 / 👎.

// Managed versions and plugin dependencies are not the module's.
String pom = text.replaceAll("(?s)<dependencyManagement>.*?</dependencyManagement>", " ")
.replaceAll("(?s)<build>.*?</build>", " ");
Matcher dep = POM_DEPENDENCY.matcher(pom);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore dependencies inside XML comments

When a POM keeps a dependency disabled inside <!-- ... -->, this regex still matches its <dependency> block because comments are never removed. librariesOf() can then wire that stale coordinate into the destination POM, so importing a project that Maven itself builds without the dependency may fail artifact resolution or bundle unwanted code. Strip XML comments or read the Maven model before scanning dependency elements.

Useful? React with 👍 / 👎.

- The importer reads POMs without their XML comments, and the import
  goal restores the POM when the import fails after wiring it.
- JdkTime keeps its formatter table copy-on-write, and an ISO_ constant
  given a zone keeps its own offset, Z and fraction.
- Base64 rejects '=' in a MIME line separator; Matcher.results() no
  longer resets the matcher.
- Rebaseline hello RAM on macOS arm64, Windows x64 and Linux arm64: the
  hello application ships the compatibility runtimes, so the benchmark
  translates about 9,600 classes instead of 7,350.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review


P2 Badge Honor deleteOnExit during orderly application exit

For every remapped desktop application, File.deleteOnExit() silently does nothing, so temporary files that callers expect to be removed during normal termination remain in the application sandbox across launches. This can steadily consume storage and retain sensitive temporary data even when the app terminates through the orderly JdkSystem.exit path, which already runs shutdown hooks. Record requested paths and delete them during that exit sequence instead of returning without registering any cleanup.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +140 to +144
if (used) {
throw new IllegalStateException(USED);
}
used = true;
return source;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject operations after any pipeline stage closes

When one stage closes the shared pipeline, only that stage's used flag is set. A downstream stage therefore still passes this check and consumes the source after its close handlers and active flat-mapped stream have already been released; for example, Stream.of(1).map(x -> x).close() followed by count() returns 1 instead of throwing IllegalStateException. Track the closed state in the shared StreamState and reject take() and further operations once any stage closes.

Useful? React with 👍 / 👎.

Comment on lines +400 to +403
kotlinRelocation(t -> new com.codename1.gradle.tasks.RemapCompatAction(
kotlinDir.get(), compileClasspath, onClickNames, true, null)
.withDesktopEntryRecord(desktopEntry).withApplicationMain(desktopMain)
.shippingWhenEmpty(javaSources).execute(t),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass runtime libraries through the Kotlin-only shipping path

For a Gradle desktop module with Kotlin sources but no Java sources, compileJava is NO-SOURCE, so this relocation action deliberately becomes the full runtime-shipping pass via shippingWhenEmpty. Unlike the Java action, however, it never calls withApplicationLibraries(main.getRuntimeClasspath()); consequently CompatRemapper.bundleLibraries() sees applicationLibraries == null and skips unpacking and relocating implementation/runtime dependency jars. A Kotlin-only app that depends on a Swing or JavaFX library can therefore ship that library with unresolved desktop references or omit its relocated classes entirely.

Useful? React with 👍 / 👎.

if (k == null || k.zone == null) {
return ZonedDateTime.parse(text, formatter);
}
return ZonedDateTime.of(localDateTimeFrom(formatter.parse(text)), k.zone);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve parsed offsets when applying the override zone

When the formatter has an override zone and the input also contains an offset, this discards the parsed offset and reinterprets the local wall time in the override zone. For example, parsing 2020-01-01T12:00:00+02:00 with ISO_OFFSET_DATE_TIME.withZone(UTC) should produce 10:00Z, but this path produces 12:00Z, shifting the represented instant by two hours. Preserve the parsed instant or offset before converting it to the override zone.

Useful? React with 👍 / 👎.

Comment on lines +93 to +95
int d = hexDigit(c);
if (d < 0 || digits >= 16) {
throw new IllegalArgumentException("Invalid UUID string: " + name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce each UUID group's maximum width

This applies a 16-digit limit independently to every UUID group even though their maximum widths are 8-4-4-4-12. As a result, malformed inputs such as 100000000-0-0-0-0 are accepted and then truncated by the masks below to the all-zero UUID, while UUID.fromString is required to reject them; this also lets multiple distinct untrusted strings collapse to the same identifier. Validate each group against its own width before accumulating it.

Useful? React with 👍 / 👎.

Comment on lines +633 to +634
public static ZonedDateTime plusDays(ZonedDateTime value, long days) {
return ZonedDateTime.of(value.toLocalDateTime().plusDays(days), value.getZone());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain the preferred offset during zoned date arithmetic

When date-based arithmetic lands in a daylight-saving overlap, reconstructing the result with ZonedDateTime.of always selects the earlier valid offset instead of retaining the original offset when possible. For example, subtracting one day from 2024-10-28T02:30+01:00[Europe/Berlin] should produce the later +01:00 occurrence on October 27, but this returns the earlier +02:00 occurrence and changes the represented instant by an hour. Resolve the new local date-time using value.getOffset() as the preferred offset; the month and year helpers have the same issue.

Useful? React with 👍 / 👎.

…ry fixes from review

- Adding days, months or years to a zoned value keeps the offset it had
  when the result falls in an hour the clocks go back over.
- Text with an offset, parsed by a formatter with an override zone, is a
  moment shown in that zone, for the ISO constants and for patterns.
- A stream stage made from a pipeline that was since closed is refused.
- A Kotlin-only desktop module on Gradle bundles its runtime libraries
  and compiles its FXML, as the javac pass does.
- UUID.fromString documents that an overlong group is truncated, which is
  what the JDK does, and a test holds it to that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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