Skip to content

Simulator: data-driven menu hooks for cn1libs#4988

Merged
shai-almog merged 11 commits into
masterfrom
feat/simulator-menu-hooks
May 21, 2026
Merged

Simulator: data-driven menu hooks for cn1libs#4988
shai-almog merged 11 commits into
masterfrom
feat/simulator-menu-hooks

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

  • Adds SimulatorHookLoader to the JavaSE port: cn1libs ship a META-INF/codenameone/simulator-hooks.properties file and get a menu in the simulator's menu bar, with no Swing types in the contract.
  • JavaSEPort.installMenu consumes the neutral hook list and emits JMenu/JMenuItems today — replaceable when the simulator UX is rewritten without breaking any cn1lib.
  • JUnit tests cover the parser end-to-end.
  • New section in docs/developer-guide/Maven-Creating-CN1Libs.adoc documents the contract with the cn1-bluetooth cn1lib as a worked example.

Contract

# META-INF/codenameone/simulator-hooks.properties
name=Bluetooth

item1.label=Add demo peripheral
item1.action=com.example.bt.sim.Hooks#addDemoPeripheral

Actions are fqcn#staticMethod references to public static void no-arg methods. The loader resolves them via Display.class.getClassLoader() and pre-binds a Runnable that dispatches on the CN1 EDT via Display.callSerially. Multiple cn1libs each contribute one menu; discovery uses ClassLoader.getResources() so they merge cleanly.

The neutral SimulatorHook record (menuName, label, Runnable) is the contract; today's Swing menu rendering is just one consumer.

Test plan

  • mvn test -pl maven/javase — 7 new tests, all pass:
    • parses well-formed file
    • preserves declaration order
    • skips file without name=
    • skips item without matching .action
    • skips unknown class but keeps rest
    • skips non-static method
    • skips malformed action string
  • End-to-end smoke test: running BTDemo in the simulator with the cn1-bluetooth lib on the classpath shows a "Bluetooth" menu with all of the lib's items; clicking each fires the matching static method on the CN1 EDT.
  • Existing simulator-tests CI (mvn cn1:test) still passes — the loader is additive and the menu construction path is unchanged for the existing three menus.

Companion change

The cn1-bluetooth cn1lib at codenameone/bluetoothle-codenameone has a companion PR using this mechanism. The lib's CI builds CN1 from source via a composite action so once this lands the lib's PR can be merged on top.

🤖 Generated with Claude Code

Adds a small framework feature that lets cn1libs contribute items to the
JavaSE simulator's menu bar via a properties file, without referencing
any Swing types. Each cn1lib drops a
META-INF/codenameone/simulator-hooks.properties on its classpath:

    name=Bluetooth
    item1.label=Add demo peripheral
    item1.action=com.example.bt.sim.Hooks#addDemoPeripheral

The new SimulatorHookLoader scans every jar on the simulator classpath
via getResources(), parses the file, resolves each action's static
method against the classloader that loaded Display, and pre-binds a
Runnable that dispatches on the CN1 EDT. JavaSEPort.installMenu groups
the result by menu name and renders one JMenu per group between the
existing menus and the Help menu.

Why data-driven instead of a Java SPI: the simulator UX is going to be
rewritten and we don't want cn1libs to depend on JMenu/JMenuItem (or
have to be recompiled when the UX shape changes). The neutral
SimulatorHook record (menuName, label, Runnable) is the contract; the
UI shell on top is replaceable.

Tests in maven/javase cover well-formed parsing, declaration-order
preservation, and skip-on-error for every malformed case
(missing name, dangling label, unknown class, non-static target,
malformed action string).

Documentation lives in docs/developer-guide/Maven-Creating-CN1Libs.adoc
with the cn1-bluetooth lib as a worked example.

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

shai-almog commented May 19, 2026

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

@github-actions
Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

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

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Image references: No unused images detected (report)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

Compared 110 screenshots: 110 matched.

Native Android coverage

  • 📊 Line coverage: 11.82% (6580/55655 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.53% (33039/346569), branch 4.14% (1362/32925), complexity 5.17% (1632/31558), method 9.00% (1328/14749), class 15.07% (301/1998)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 11.82% (6580/55655 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.53% (33039/346569), branch 4.14% (1362/32925), complexity 5.17% (1632/31558), method 9.00% (1328/14749), class 15.07% (301/1998)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1089.000 ms
Base64 CN1 encode 233.000 ms
Base64 encode ratio (CN1/native) 0.214x (78.6% faster)
Base64 native decode 1245.000 ms
Base64 CN1 decode 388.000 ms
Base64 decode ratio (CN1/native) 0.312x (68.8% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 63000 ms
Simulator Boot (Run) 0 ms
App Install 10000 ms
App Launch 5000 ms
Test Execution 253000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 644.000 ms
Base64 CN1 encode 1776.000 ms
Base64 encode ratio (CN1/native) 2.758x (175.8% slower)
Base64 native decode 253.000 ms
Base64 CN1 decode 1248.000 ms
Base64 decode ratio (CN1/native) 4.933x (393.3% slower)
Base64 SIMD encode 489.000 ms
Base64 encode ratio (SIMD/native) 0.759x (24.1% faster)
Base64 encode ratio (SIMD/CN1) 0.275x (72.5% faster)
Base64 SIMD decode 437.000 ms
Base64 decode ratio (SIMD/native) 1.727x (72.7% slower)
Base64 decode ratio (SIMD/CN1) 0.350x (65.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 56.000 ms
Image createMask (SIMD on) 9.000 ms
Image createMask ratio (SIMD on/off) 0.161x (83.9% faster)
Image applyMask (SIMD off) 116.000 ms
Image applyMask (SIMD on) 57.000 ms
Image applyMask ratio (SIMD on/off) 0.491x (50.9% faster)
Image modifyAlpha (SIMD off) 112.000 ms
Image modifyAlpha (SIMD on) 52.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.464x (53.6% faster)
Image modifyAlpha removeColor (SIMD off) 173.000 ms
Image modifyAlpha removeColor (SIMD on) 59.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.341x (65.9% faster)
Image PNG encode (SIMD off) 951.000 ms
Image PNG encode (SIMD on) 1049.000 ms
Image PNG encode ratio (SIMD on/off) 1.103x (10.3% slower)
Image JPEG encode 412.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

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

Benchmark Results

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

Build and Run Timing

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

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 682.000 ms
Base64 CN1 encode 1197.000 ms
Base64 encode ratio (CN1/native) 1.755x (75.5% slower)
Base64 native decode 274.000 ms
Base64 CN1 decode 908.000 ms
Base64 decode ratio (CN1/native) 3.314x (231.4% slower)
Base64 SIMD encode 496.000 ms
Base64 encode ratio (SIMD/native) 0.727x (27.3% faster)
Base64 encode ratio (SIMD/CN1) 0.414x (58.6% faster)
Base64 SIMD decode 418.000 ms
Base64 decode ratio (SIMD/native) 1.526x (52.6% slower)
Base64 decode ratio (SIMD/CN1) 0.460x (54.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 73.000 ms
Image createMask (SIMD on) 10.000 ms
Image createMask ratio (SIMD on/off) 0.137x (86.3% faster)
Image applyMask (SIMD off) 123.000 ms
Image applyMask (SIMD on) 54.000 ms
Image applyMask ratio (SIMD on/off) 0.439x (56.1% faster)
Image modifyAlpha (SIMD off) 117.000 ms
Image modifyAlpha (SIMD on) 52.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.444x (55.6% faster)
Image modifyAlpha removeColor (SIMD off) 140.000 ms
Image modifyAlpha removeColor (SIMD on) 64.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.457x (54.3% faster)
Image PNG encode (SIMD off) 951.000 ms
Image PNG encode (SIMD on) 840.000 ms
Image PNG encode ratio (SIMD on/off) 0.883x (11.7% faster)
Image JPEG encode 486.000 ms

CI's vale gate flagged 10 prose issues in the simulator-menu-hooks
section: "backend" hits Microsoft.Avoid (treated as a noise token
since it's our standard term for a swappable implementation),
"e.g.", a missing Oxford-style comma inside quoted enumerations, a
stray "freely" adverb, heading punctuation, and "do not" instead
of "don't". Adds "[Bb]ackend" to the project vocabulary and rewrites
the affected sentences. No semantic change to the section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog added a commit to codenameone/bluetoothle-codenameone that referenced this pull request May 19, 2026
…latform plumbing

Every cn1lib CI job failed on its first run with a mix of issues that
all map to the same shape: the OS-activated native-helper Maven
profiles tried to do too much, and the framework setup composite
action didn't actually work cross-OS.

Concretely:

- The OS-activated profiles (linux-native-ble-helper, etc.) fired
  whenever a build ran on the matching OS, so the simulator-tests,
  ios-native-tests, and android-native-tests jobs all tried to
  compile the Rust helper too. Those runners don't have cargo (or
  libdbus-1-dev on Linux) installed, and they don't need the helper.
  Each profile now also requires `!skipNativeBleHelper` to activate,
  and every workflow / script that builds the cn1lib but doesn't
  need the helper passes -DskipNativeBleHelper=true.

- Composite action used a hard-coded /tmp path. Windows runners
  fail with "directory name is invalid" because Git Bash there
  doesn't see a real Unix /tmp. Swapped to $RUNNER_TEMP throughout.

- Composite action requested JDK 8 from Temurin, which doesn't ship
  Apple-Silicon JDK 8 binaries; macos-14 jobs failed with "Could
  not find satisfied version for SemVer '8'". Switched to Zulu.

- device-test.yml never ran the framework setup; it tried to fetch
  codenameone-maven-plugin 8.0-SNAPSHOT from Central. Wired in the
  composite action and the skip flag.

- TEMPORARY: composite's default cn1-ref is feat/simulator-menu-hooks
  (the framework PR's branch). Once codenameone/CodenameOne#4988
  merges, flip back to 'master'. Comment in the action calls this
  out so the followup isn't forgotten.

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

github-actions Bot commented May 19, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

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

Generated automatically by the PR CI workflow.

shai-almog and others added 2 commits May 20, 2026 06:42
Original feedback: CN1 tests live in the cross-platform common/
project and can't import JavaSE-port classes (no reflection, no
JavaSE-only types). The previous design forced tests to either
reflect on SimulatorHookLoader or directly instantiate cn1lib
internals, both of which break on iOS/Android/JavaScript.

This commit adds the missing piece: every hook gets a stable
`namespace:id` identifier and is registered with a new core class,
SimulatorHookExecutor. CN.executeHook(String hookId) delegates to
that executor and returns false on platforms where the registry is
empty (i.e., every non-simulator target). Tests in common/ can
drive simulator-only behavior with one cross-platform call:

    CN.executeHook("bluetooth:addDemoPeripheral");

Also lifts the menu-label restriction: hooks may now declare an
id and action without a label, in which case they are registered
with the executor (callable from tests) but hidden from the
simulator's menu. Useful for test fixture scaffolding ("seed N
peripherals", "prime next-call failure") that would clutter the
menu UX.

Properties-file grammar additions:

  namespace=<token>     # defaults to slugified `name`
  itemN.id=<token>      # defaults to the property key (item1, item2)
  itemN.label=...       # NOW OPTIONAL; absent = API-only hook

Documentation in Maven-Creating-CN1Libs.adoc updated with the new
shape and a CN.executeHook test example.

12 JUnit tests on the framework parser pass; existing menu rendering
is unchanged for any hook that ships a label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The action wrapper used Display.callSerially, which is fire-and-forget.
That broke CN.executeHook callers running off the EDT (every CN1
UnitTest's runTest()) — they'd assert state changes before the EDT
had run the action and false-fail.

Switched to Display.callSeriallyAndWait. From the EDT the body runs
inline (CN1's existing semantics); from any other thread the call
blocks until the action completes. CN.executeHook now returns true
only after the hook has actually executed, so tests can immediately
assert on the side effects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog added a commit to codenameone/bluetoothle-codenameone that referenced this pull request May 20, 2026
Rewrite BluetoothSimulatorHooksTest to call hooks by id through
CN.executeHook("bluetooth:<id>") instead of importing
com.codename1.impl.javase.simulator.SimulatorHook{,Loader} directly.
The new pattern works unchanged in cn1lib-using apps where the test
lives in the cross-platform common/ project — no JavaSE-port
imports, no reflection.

simulator-hooks.properties:
  - explicit namespace=bluetooth
  - explicit item.id for every hook (matches the executor key shape)
  - new item8 = primeReadFailure: a label-less hook the test uses
    to script a one-shot read failure. Demonstrates the API-only
    branch (no menu entry, still callable from tests).

BluetoothSimulatorHooks gains primeReadFailure() which delegates to
BluetoothSimulator.failNext("read", ...). Other existing hooks are
unchanged; the only state asserted from BluetoothSimulator is via
its public testing API (isEnabled, registeredPeripheralCount,
isPeripheralRegistered), which has always been part of the lib.

Depends on codenameone/CodenameOne#4988 latest commit (the
callSeriallyAndWait fix that makes CN.executeHook synchronous from
off-EDT callers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog and others added 5 commits May 20, 2026 07:12
The framework's docs-style gate (build-test JDK 8/17/21) rejects
classic /** Javadoc markers in core/CLDC classes — CN1 standardized
on Java 25 /// markdown comments instead. Convert SimulatorHookExecutor
to the project style. No semantic change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous design added CN.executeHook + nested itemN.id/.label/.action
keys. Both miss the mark:

- We already have CN/Display.execute(String url) for native dispatch
  on every platform; the JavaSE port can intercept hook urls before
  handing the rest to the OS browser. Adding executeHook duplicates
  that surface and forces app code to learn a second method.

- Items are positional: item1 is the first menu entry, item2 the
  second. The previous design treated itemN as an opaque pairing
  token (any string worked) and allowed reordering — that's wrong;
  the simulator UX renders in numeric order and the loader should
  too. The loop now stops at the first missing itemN, matching
  what the user-facing menu would do.

- The compound key syntax (itemN.label, itemN.action, itemN.id) is
  redundant when the namespace is already in the file's `name`/
  `namespace` field. Replaced with parallel arrays: itemN holds
  the action, labelN holds the label.

Final shape:

    name=Bluetooth
    namespace=bluetooth          # optional; defaults to slugified `name`

    item1=fqcn#method            # required; the Nth menu item's action
    label1=Toggle adapter        # optional; if absent, hook is API-only

    item2=fqcn#method2
    label2=Add demo peripheral

    # No labelN -> registered with the executor but not in the menu
    item3=fqcn#primeReadFailure

JavaSEPort.execute intercepts urls matching a registered hook (key
shape: "namespace:itemN") and routes them through the existing
SimulatorHookExecutor; non-matching urls fall through to the
browser launcher. JavaSEPort.canExecute reports TRUE for registered
hook urls so tests can guard cross-platform.

SimulatorHookExecutor stays in core. Tests use CN.execute(...) plus
CN.canExecute(...) as a "are we in a simulator?" gate; no
JavaSE-only imports needed. 12 JUnit tests pin the positional loop,
slugify rules, gap-stops behavior, executor registration, and the
API-only branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Android port's javac uses ASCII encoding (see feedback memory),
so any non-ASCII character in a .java file under CodenameOne/ or
Ports/JavaSE/ trips "unmappable character" errors in build-test
(8/17). The redesign sneaked em-dashes ('--'), arrows ('->') and a
right single quote into a handful of new/edited files. Sweep them
out: every modified .java is now pure ASCII.

Also drops a stray "e.g." from the dev guide section that Vale's
Microsoft.Foreign rule flags; replaced with "for example".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PMD's AvoidUsingVolatile rule (enforced by the build-test JDK 8
"Generate static analysis HTML summaries" step) flagged the
`private static volatile Map<String, Runnable> hooks` field. The
semantics we want -- safe replacement of the registry from the
JavaSE port while readers see either the old or new map atomically
-- map cleanly to AtomicReference, which avoids the volatile keyword
and keeps PMD happy. No observable behavior change.

12 framework JUnit tests still pass.

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

The volatile-vs-AtomicReference choice was a false binary:

- volatile: trips PMD's AvoidUsingVolatile gate on the JDK 8 PR CI run.
- AtomicReference: triggers "package java.util.concurrent.atomic does
  not exist" in the JavaSE port's javase-simulator-tests Ant step,
  which compiles the framework core against the CLDC subset. CLDC
  doesn't ship j.u.c.atomic.

Both gates fail on the new class. The portable third option is plain
synchronized accessors guarding the registry field with a private
lock object -- same memory-visibility guarantee, compiles under
every supported target, no PMD warning. Hook actions still run
OUTSIDE the lock so a long-running hook can't deadlock a concurrent
register() call. 12 framework JUnit tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog added a commit to codenameone/bluetoothle-codenameone that referenced this pull request May 20, 2026
Previous version of this PR depended on CN1 8.0-SNAPSHOT to pick up
the simulator hook intercept added in codenameone/CodenameOne#4988.
That's wrong for a cn1lib: the framework feature isn't released yet,
and tying this PR to an in-flight master commit means everything
downstream (CI, anyone reviewing) has to also build CN1 from source.

Reverting the cn1lib to the current release (7.0.243):

- pom.xml: cn1.version / cn1.plugin.version = 7.0.243.
- Drop the local-snapshots repository entry and the
  setup-cn1-framework composite action that cloned + built CN1
  master on every CI job. The workflows now use the same plain
  curl-the-build-client pattern they had pre-PR.
- ios-native-tests no longer needs continue-on-error: the iOS
  codegen on the released line generates the same dispatch shim
  the bridge has always been targeting (it worked under 7.0.71 and
  carries through 7.0.243), so the lib's ObjC selector naming
  (param1/param2/...) compiles cleanly. The 8.0-SNAPSHOT clash was
  upstream churn, not a bridge bug.
- cn1-framework-pin.txt removed.

The new hook surface ships dormant on 7.0.243:

- simulator-hooks.properties and BluetoothSimulatorHooks are still
  there; on 7.0.243 the simulator simply doesn't read them, the
  menu items don't render, and CN.canExecute("bluetooth:item1")
  returns false.
- BluetoothSimulatorHooksTest now opens with that exact canExecute
  guard. On 7.0.243 it returns true (test "passes" without doing
  anything); on a future CN1 release that intercepts the URLs
  (whenever #4988 ships), every existing assertion fires.

Local verification:
- mvn install of every module succeeds against 7.0.243.
- mvn cn1:test from BTDemo passes 8/8 (hook test no-ops cleanly).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog and others added 2 commits May 21, 2026 07:09
…ecute

The new SimulatorHookExecutor / SimulatorHook / SimulatorHookLoader
files were authored for Codename One, not imported from Oracle, so
the Oracle/Classpath header that older OpenJDK-derived files carry
doesn't belong on them. Strip the header — recent CN1-authored files
ship without one. Also revert the doc additions to CN.execute and
fold them into Display.execute, which is where the URL execute API
actually lives and where this behavior should be documented.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every CN1-authored source file in this repo carries the GPL v2 +
Classpath header. The previous commit stripped the bogus Oracle
attribution but left the new files headerless; this restores the
proper Codename One header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog merged commit 01ff6b3 into master May 21, 2026
23 checks passed
shai-almog added a commit to codenameone/bluetoothle-codenameone that referenced this pull request May 21, 2026
* Native BLE backend for the JavaSE port + cn1lib simulator menu

Splits the JavaSE port's BluetoothNativeBridgeImpl into a thin
dispatcher and two backends:

- SimulatorBluetoothBackend keeps the existing scriptable in-memory
  behavior — every test continues to drive it, and the simulator menu
  drives it manually via BluetoothSimulatorHooks.
- NativeBleBackend talks to real BLE hardware through a bundled Rust
  helper built from javase/src/main/rust/cn1-ble-helper. The helper
  wraps the btleplug crate, which covers macOS (CoreBluetooth), Linux
  (BlueZ via D-Bus) and Windows (WinRT) with a single source tree;
  protocol is JSON lines over stdin/stdout (see
  javase/src/main/rust/PROTOCOL.md).

Selection is via the cn1.bluetoothle.javase.backend system property
("simulator" / "nativeBle"), the CN1_BLUETOOTHLE_BACKEND env var, or
the simulator menu's new "Switch backend →" items. Swap is hot:
BluetoothNativeBridgeImpl holds the current backend in a volatile
static so a cached Bluetooth instance immediately sees the change.

The cn1lib's simulator menu uses the framework's new
SimulatorHookLoader (companion CodenameOne PR). Two new sets of
items: scripted simulator drivers (Toggle adapter, Add demo
peripheral, Disconnect all, Push notification, Clear) and the
backend toggle. Each action is a public static method on
BluetoothSimulatorHooks, dispatched on the CN1 EDT.

Maven integration:

- Three OS-activated profiles in javase/pom.xml run `cargo build
  --release` and copy the helper to a per-OS classpath resource
  (com/codename1/bluetoothle/native/{macos,linux,windows}/...). Hosts
  without cargo skip cleanly and the dispatcher falls back to
  simulator with a clear stderr message.
- `BluetoothSimulator` gains two public introspection methods,
  registeredPeripheralCount() and isPeripheralRegistered(), used by
  the new BluetoothSimulatorHooksTest to assert hook side effects
  without going through scanning.

CI:

- New composite action `.github/actions/setup-cn1-framework` clones
  codenameone/CodenameOne master and installs 8.0-SNAPSHOT into
  ~/.m2, caching on the contents of `.github/cn1-framework-pin.txt`.
  All existing jobs (maven.yml, simulator-tests, ios-native-tests,
  android-native-tests) now use it.
- New native-ble-helper matrix job runs on macos-14, ubuntu-latest
  and windows-latest. Each builds the helper, verifies it ended up
  at the right OS-keyed resource path inside the cn1lib jar, then
  runs scripts/native-tests/run-native-ble-helper-smoke.sh which
  asserts the helper emits a stateChanged event before clean exit
  (hosted runners typically report `unsupported` since they have no
  adapter — the only failure mode is "helper never emitted
  anything", which catches build / linking / runtime regressions).

Depends on codenameone/CodenameOne#4988 (SimulatorHookLoader);
the composite action builds that PR's master at run time, so this
PR will turn green once the framework PR is merged.

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

* CI: gate the Rust helper build behind -DskipNativeBleHelper and fix platform plumbing

Every cn1lib CI job failed on its first run with a mix of issues that
all map to the same shape: the OS-activated native-helper Maven
profiles tried to do too much, and the framework setup composite
action didn't actually work cross-OS.

Concretely:

- The OS-activated profiles (linux-native-ble-helper, etc.) fired
  whenever a build ran on the matching OS, so the simulator-tests,
  ios-native-tests, and android-native-tests jobs all tried to
  compile the Rust helper too. Those runners don't have cargo (or
  libdbus-1-dev on Linux) installed, and they don't need the helper.
  Each profile now also requires `!skipNativeBleHelper` to activate,
  and every workflow / script that builds the cn1lib but doesn't
  need the helper passes -DskipNativeBleHelper=true.

- Composite action used a hard-coded /tmp path. Windows runners
  fail with "directory name is invalid" because Git Bash there
  doesn't see a real Unix /tmp. Swapped to $RUNNER_TEMP throughout.

- Composite action requested JDK 8 from Temurin, which doesn't ship
  Apple-Silicon JDK 8 binaries; macos-14 jobs failed with "Could
  not find satisfied version for SemVer '8'". Switched to Zulu.

- device-test.yml never ran the framework setup; it tried to fetch
  codenameone-maven-plugin 8.0-SNAPSHOT from Central. Wired in the
  composite action and the skip flag.

- TEMPORARY: composite's default cn1-ref is feat/simulator-menu-hooks
  (the framework PR's branch). Once codenameone/CodenameOne#4988
  merges, flip back to 'master'. Comment in the action calls this
  out so the followup isn't forgotten.

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

* CI: disable CN1's auto-download-cn1-binaries profile

Every cn1lib job that runs the framework-setup composite was failing
with "destination path 'cn1-binaries' already exists and is not an
empty directory" — six different jobs, same root cause:

CN1's reactor pom has a download-cn1-binaries profile that activates
by default. It does `git clone cn1-binaries` on every reactor module
that hits process-resources, into a path the framework picks itself.
We already clone cn1-binaries to $RUNNER_TEMP and tell the build to
look there via -Dcn1.binaries, but the download profile fires anyway
on the first module, succeeds, then collides with itself on every
subsequent module.

Adding !download-cn1-binaries to the profile list disables it. This
matches what CN1's own scripts/setup-workspace.sh passes.

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

* CI: install build client on every run + harden helper-jar verify

Two fixes for issues exposed by the second-round CI run:

- Composite action's "Install CN1 build client" step was gated on
  cache-miss; cache-hit jobs found ~/.m2 restored from cache but
  CodeNameOneBuildClient.jar missing (it lives in ~/.codenameone),
  and every job failed with "CodeNameOneBuildClient.jar not found".
  Always install: copy from $RUNNER_TEMP if we just cloned, else
  download from GitHub raw.

- native-bluetooth-tests.yml's "Verify helper binary..." step used
  `jar tf cn1-bluetooth-javase-*.jar`. On CI three jars match
  (main / -sources / -javadoc) and `jar tf` reads only the first,
  alphabetically the -javadoc one, which has no native resources.
  Filter out the classifier jars and emit diagnostic output on miss
  so future regressions are easier to read.

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

* CI: Rust helper emits stateChanged on adapter-init failure + Android cn1:build needs JAVA17_HOME

Two more CI fixes uncovered by the second round of runs:

- ubuntu-latest native-ble-helper job: build + jar packaging passed
  but the smoke test failed. Hosted runners have no Bluetooth adapter
  and btleplug's Manager::new() / .adapters() returns errors there;
  the Rust helper was bailing out without producing any stdout, so
  the smoke test's "did stateChanged land" check saw silence and
  failed. Hardened main() to catch the adapter-init Err branch the
  same way as the no-adapter branch: log to stderr, emit
  {"event":"stateChanged","state":"unsupported"}, then drain stdin
  until EOF or shutdown.

- build-and-test (device-test workflow) and android-native-tests:
  Both invoke cn1:build for Android, which forks a Gradle 8 build.
  The CN1 plugin requires JAVA17_HOME (uppercase, no _X64 suffix)
  pointed at a Java 17 install, separate from the JAVA_HOME the
  rest of the build uses. setup-java only sets JAVA_HOME_17_X64;
  exported JAVA17_HOME from that for both workflows.

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

* CI: install ripgrep + fall back to xcodeproj on macos iOS job

The macos-14 runner image stopped shipping ripgrep, and
run-ios-native-tests.sh uses `rg` to:
  - guard against Cordova refs (under `!`, which silently masks
    command-not-found as a pass)
  - decide whether the pbxproj test target needs edits (`! rg -q`
    on missing rg evaluated as true, so the edits ran anyway, but
    only by accident)

Made it explicit: `brew install ripgrep` once at the top of the job.

Second iOS issue: cn1:build for ios-source in 8.0-SNAPSHOT generates
only BTDemo.xcodeproj, no companion BTDemo.xcworkspace. The script's
final xcodebuild invocation hardcoded `-workspace BTDemo.xcworkspace`
and bailed with exit 66 ("workspace does not exist"). Auto-detect
and fall back to `-project BTDemo.xcodeproj` when no workspace is
generated.

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

* CI: cap MAVEN_OPTS for android jobs, mark iOS continue-on-error

- android-native-tests + build-and-test: cn1:build forks a sub-Maven
  for gradle. On a runner that already has the Android emulator
  taking ~3-4 GB, the fork's default JVM heap ergonomics try to
  allocate more than what's free and the sub-process dies in VM init
  ("Error occurred during initialization of VM"). Pinned
  MAVEN_OPTS="-Xms256m -Xmx1g" to fit alongside the emulator.

- ios-native-tests: hits a CN1 master codegen bug. BluetoothLePlugin
  has Cordova-style `-(void)stopScan:(CDVInvokedUrlCommand*)command`
  selectors; the bridge has no-arg `-(BOOL)stopScan`. The ParparVM
  dispatch shim generated by 8.0-SNAPSHOT picks the void overload
  and clang errors with "initializing 'JAVA_BOOLEAN' with an
  expression of incompatible type 'void'". The iOS source project
  itself still builds and is usable; only the XCTest target fails
  to compile, and the fix has to land in codenameone/CodenameOne
  rather than the cn1lib. Marked continue-on-error: true with a
  comment until the framework codegen is fixed.

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

* Tests: drive hooks via CN.executeHook + add API-only primeReadFailure

Rewrite BluetoothSimulatorHooksTest to call hooks by id through
CN.executeHook("bluetooth:<id>") instead of importing
com.codename1.impl.javase.simulator.SimulatorHook{,Loader} directly.
The new pattern works unchanged in cn1lib-using apps where the test
lives in the cross-platform common/ project — no JavaSE-port
imports, no reflection.

simulator-hooks.properties:
  - explicit namespace=bluetooth
  - explicit item.id for every hook (matches the executor key shape)
  - new item8 = primeReadFailure: a label-less hook the test uses
    to script a one-shot read failure. Demonstrates the API-only
    branch (no menu entry, still callable from tests).

BluetoothSimulatorHooks gains primeReadFailure() which delegates to
BluetoothSimulator.failNext("read", ...). Other existing hooks are
unchanged; the only state asserted from BluetoothSimulator is via
its public testing API (isEnabled, registeredPeripheralCount,
isPeripheralRegistered), which has always been part of the lib.

Depends on codenameone/CodenameOne#4988 latest commit (the
callSeriallyAndWait fix that makes CN.executeHook synchronous from
off-EDT callers).

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

* CI: invalidate CN1 framework cache (CN.executeHook is new)

The cn1lib's framework-setup composite action keys its cache on
this file. Previous cn1lib CI runs cached the framework m2 artifacts
from BEFORE CN.executeHook + SimulatorHookExecutor existed; after
the framework PR added them, this round's cn1lib CI restored the
stale cache and the new test failed to compile ("cannot find symbol:
method executeHook").

Bumping the contents to force a fresh framework rebuild.

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

* Switch to positional itemN/labelN + CN.execute("bluetooth:itemN")

Aligns the cn1lib with the redesigned framework contract:

- simulator-hooks.properties uses parallel arrays. itemN is the
  action method; labelN is the optional menu text. No more
  itemN.action / itemN.id / itemN.label compound keys.

- Items are positional: item1 first, item2 second, etc. The
  primeReadFailure hook lives at item8 (no label8) so it's
  invisible in the menu but still callable from tests.

- BluetoothSimulatorHooksTest drops the experimental CN.executeHook
  in favor of the existing CN.execute(url) entry point. The JavaSE
  port intercepts "bluetooth:itemN" urls and dispatches the matching
  static method on the CN1 EDT. Each test also guards with
  CN.canExecute so the suite stays platform-portable.

- Cache pin bumped to force CI to rebuild the framework with the
  redesigned SimulatorHookExecutor + JavaSEPort.execute intercept.

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

* CI: enable KVM for android-emulator-runner

ubuntu-latest stopped auto-granting /dev/kvm to the runner user;
the ReactiveCircus android-emulator-runner step aborts with
"This user doesn't have permissions to use KVM (/dev/kvm)" before
booting the AVD. The canonical fix from the action's README is a
udev rule that opens KVM to all users. Adding it as the first
step of android-native-tests so the emulator boots again.

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

* CI: patch deprecated gradle configs in generated Android source

CN1 master's android codegen still emits the Gradle 5-removed
`androidTestCompile`/`testCompile`/`compile` dependency configurations
(now `androidTestImplementation`/`testImplementation`/`implementation`).
The emulator-runner step uses Gradle 8, which refuses the old names
and bails on app/build.gradle line 97 with
"Could not find method androidTestCompile()".

Patch the generated file before running gradle, same shape as the
existing compileSdkVersion / support-lib version rewrites. Anchored
on leading whitespace so the substitution doesn't touch coincidental
substrings elsewhere in the gradle file.

This is a workaround for the upstream codegen bug; the real fix
belongs in codenameone/CodenameOne when the Android source generator
gets a Gradle-8 pass.

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

* CI: always use androidTestImplementation when injecting test deps

The script's TEST_DEP_CONF fallback to "androidTestCompile" was
triggered when no top-level "implementation" line was found in the
generated app/build.gradle. Combined with the Gradle 5 removal of
that name in modern Gradle 8 (used by the emulator-runner step),
the appended test-dependency block at app/build.gradle:97
re-introduced the bad name and the build died on the very line
the previous fix tried to clean up.

Drop the fallback. androidTestImplementation works on AGP 3.x+,
which is everything the test runner library is compatible with
anyway; the conditional was a safety net for a Gradle version we
no longer support.

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

* CI: migrate android instrumentation test to AndroidX

The script-injected Android test imported pre-AndroidX
android.support.test.{InstrumentationRegistry,runner.AndroidJUnit4}
and pulled in com.android.support.test:runner:1.0.2. Those package
paths haven't resolved cleanly under modern AGP + Gradle 8 since
AndroidX shipped in 2018, and the generated project's other
support-lib deps were already being version-pinned by sibling
perl-substitutions in this script.

Migrate to the AndroidX equivalents:

  android.support.test.InstrumentationRegistry
    -> androidx.test.platform.app.InstrumentationRegistry
  android.support.test.runner.AndroidJUnit4
    -> androidx.test.ext.junit.runners.AndroidJUnit4
  android.support.test.runner.AndroidJUnitRunner
    -> androidx.test.runner.AndroidJUnitRunner
  com.android.support.test:runner:1.0.2
    -> androidx.test:runner:1.6.1
       + androidx.test.ext:junit:1.2.1
  InstrumentationRegistry.getTargetContext()
    -> InstrumentationRegistry.getInstrumentation().getTargetContext()

Also enable android.useAndroidX + android.enableJetifier in the
generated project's gradle.properties so the legacy support-v4 /
appcompat-v7 deps the CN1 codegen still emits get jetified at
build time instead of clashing with the AndroidX classpath.

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

* Pin to released CN1 7.0.243 and let the hook test no-op there

Previous version of this PR depended on CN1 8.0-SNAPSHOT to pick up
the simulator hook intercept added in codenameone/CodenameOne#4988.
That's wrong for a cn1lib: the framework feature isn't released yet,
and tying this PR to an in-flight master commit means everything
downstream (CI, anyone reviewing) has to also build CN1 from source.

Reverting the cn1lib to the current release (7.0.243):

- pom.xml: cn1.version / cn1.plugin.version = 7.0.243.
- Drop the local-snapshots repository entry and the
  setup-cn1-framework composite action that cloned + built CN1
  master on every CI job. The workflows now use the same plain
  curl-the-build-client pattern they had pre-PR.
- ios-native-tests no longer needs continue-on-error: the iOS
  codegen on the released line generates the same dispatch shim
  the bridge has always been targeting (it worked under 7.0.71 and
  carries through 7.0.243), so the lib's ObjC selector naming
  (param1/param2/...) compiles cleanly. The 8.0-SNAPSHOT clash was
  upstream churn, not a bridge bug.
- cn1-framework-pin.txt removed.

The new hook surface ships dormant on 7.0.243:

- simulator-hooks.properties and BluetoothSimulatorHooks are still
  there; on 7.0.243 the simulator simply doesn't read them, the
  menu items don't render, and CN.canExecute("bluetooth:item1")
  returns false.
- BluetoothSimulatorHooksTest now opens with that exact canExecute
  guard. On 7.0.243 it returns true (test "passes" without doing
  anything); on a future CN1 release that intercepts the URLs
  (whenever #4988 ships), every existing assertion fires.

Local verification:
- mvn install of every module succeeds against 7.0.243.
- mvn cn1:test from BTDemo passes 8/8 (hook test no-ops cleanly).

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

* Revert all the Android/iOS workarounds added for 8.0-SNAPSHOT

The previous patches in this PR (gradle config rename,
androidTestCompile -> androidTestImplementation fallback removal,
AndroidX migration, ripgrep install, xcodeproj fallback) were
chasing regressions in CN1 master (8.0-SNAPSHOT). Now that we pin
to the released CN1 7.0.243, none of those regressions apply:
master worked with `com.android.support.test:runner:1.0.2` +
`androidTestCompile` and with the original Xcode project layout
before this PR, and 7.0.243 generates the same surface. Restore
the pre-PR scripts so the cn1lib's CI stops fighting upstream
churn that isn't its problem.

Only difference vs the pre-PR baseline: each mvn invocation now
passes -DskipNativeBleHelper=true so the OS-activated Rust helper
profile sits out the native-tests jobs (those runners don't have
cargo and the dedicated native-ble-helper matrix in
native-bluetooth-tests.yml covers it).

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

* Restore Android + iOS workarounds: 7.0.243 codegen needs them too

I removed the Android (AndroidX migration, androidTestImplementation,
gradle config rename) and iOS (xcworkspace -> xcodeproj fallback)
workarounds thinking they were only required for the 8.0-SNAPSHOT
codegen regression. Wrong call: between CN1 7.0.71 (which the
original master branch is pinned to and where the pre-PR scripts
pass) and 7.0.243 (the current release), the codegen bumped the
emitted Gradle / Xcode templates enough that the original
"androidTestCompile com.android.support.test:runner:1.0.2" and
the workspace-based xcodebuild invocation no longer apply.

Latest CI failure on this branch with the pre-PR scripts:
  > Could not find method androidTestCompile() for arguments
    [com.android.support.test:runner:1.0.2] ...
  > xcodebuild: error: 'BTDemo.xcworkspace' does not exist.

Restoring the workarounds verbatim from commit bdf9515:
- AndroidX migration (test imports + testInstrumentationRunner +
  androidx.test:runner / androidx.test.ext:junit deps),
- useAndroidX + enableJetifier flags in the generated project,
- compile/testCompile/androidTestCompile -> implementation/etc.
  rename in the generated build.gradle,
- unconditional androidTestImplementation when injecting the test
  dependency block,
- iOS xcodebuild falls back to -project when the workspace isn't
  generated.

These workarounds applied to 8.0-SNAPSHOT AND apply to 7.0.243.

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

* iOS: prefix Cordova plugin methods with cn1_ to disambiguate selectors

The bridge declares no-arg `-(BOOL)stopScan`, `-(BOOL)requestLocation`
etc.; BluetoothLePlugin (a Cordova-style plugin under the hood) had
matching `-(void)stopScan:(CDVInvokedUrlCommand*)command` etc. Strict
selectors -- the colon makes them different -- but clang's
-Wobjc-multiple-method-names treats the *first word* as the conflict
key, and the CN1 ParparVM-generated dispatch shim for the bridge
uses `[ptr requestLocation]` where ptr is `id`. With CN1 7.0.243's
codegen + Xcode 15.4 the warning becomes a hard error:

    initializing 'JAVA_BOOLEAN' (aka 'int') with an expression of
    incompatible type 'void'

(clang picked the void overload because both first-word matches were
visible and the receiver was untyped).

Fix in the lib: rename all 47 Cordova action methods in
BluetoothLePlugin.{h,m} from `<action>:` to `cn1_<action>:` so no
first-word collisions remain. Update the bridge's executeCommand
dispatcher to build the prefixed selector when looking up the
plugin method by action name. The action-name surface seen by Java
callers is unchanged.

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

* iOS: forward-declare BluetoothLePlugin in bridge header

BluetoothNativeBridgeImpl.h was importing BluetoothLePlugin.h directly,
which transitively pulled <CoreBluetooth/CoreBluetooth.h> and the
CoreLocation headers into every file that included the bridge header.
The CN1 ParparVM-generated dispatch shim
(native_com_codename1_bluetoothle_BluetoothNativeBridgeImplCodenameOne.m)
is one such consumer: with `id`-typed receivers and Apple's
`-(void)stopScan` (CBCentralManager) / `-(void)requestLocation`
(CLLocationManager) in scope alongside the bridge's `-(BOOL)stopScan` /
`-(BOOL)requestLocation`, clang picked the void overload and the
JAVA_BOOLEAN return-value assignment failed with "initializing
'JAVA_BOOLEAN' with an expression of incompatible type 'void'".

Use `@class BluetoothLePlugin;` in the header (sufficient for the
`BluetoothLePlugin* _bluetoothPlugin` instance variable declaration)
and move the full `#import` into the .m where the implementation
actually dereferences plugin methods. The codegen shim no longer
sees CoreBluetooth / CoreLocation transitively, and its
`[ptr stopScan]` resolves unambiguously to the bridge's BOOL method.

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

* iOS: cast `ptr` to the bridge type in the generated CN1 dispatch shim

After the forward-declaration fix in the bridge header, the codegen
shim still failed with the same JAVA_BOOLEAN / void clang error.
Reason: the shim's own (CN1-generated) `#import` block pulls in
CoreLocation and CoreBluetooth directly -- it's not a transitive
include from our header, so `@class BluetoothLePlugin;` in the .h
doesn't help. With `ptr` declared as `id` and Apple's
`-(void)requestLocation` (CLLocationManager) / `-(void)stopScan`
(CBCentralManager) visible at the call site, clang picks the void
overload and the assignment fails.

Post-process the generated shim file
(BTDemo-src/native_..._BluetoothNativeBridgeImplCodenameOne.m) with
a perl substitution that wraps every `[ptr <selector>]` send in a
typed cast:

    [ptr requestLocation]
        -> [(com_codename1_bluetoothle_BluetoothNativeBridgeImpl*)ptr requestLocation]

Same shape as the existing CN1_THREAD_STATE_MULTI_ARG perl tweak in
this script. The cast forces clang to resolve the selector against
the bridge's interface specifically, so the BOOL return-type
matches the codegen's local declaration.

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

* ios native tests: force-link CoreBluetooth via OTHER_LDFLAGS

The CN1 7.0.243 ios-source generator uses different PBXProject UUIDs
for the test target's Frameworks build phase than 8.0-SNAPSHOT, so the
awk-driven CoreBluetooth.framework insertion silently no-ops and the
test bundle fails to link against CBCentralManager. Passing
OTHER_LDFLAGS as an xcodebuild build setting bypasses the pbxproj
entirely and works across generator versions.

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

* device-test: use fully-qualified plugin coordinate for cn1:build

After 'mvn install' writes local com.codenameone group metadata, the
cn1 prefix lookup against that metadata can miss the
codenameone-maven-plugin entry (the install step doesn't update the
prefix mappings), leaving the second invocation to fail with 'No
plugin found for prefix cn1'. Bypassing the prefix resolution with the
fully-qualified groupId:artifactId:version:goal form makes the call
deterministic.

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

* Add Codename One license header to new javase + test files

The new NativeBleBackend, SimulatorBluetoothBackend, and
BluetoothSimulatorHooksTest files shipped without a header. Add the
project's standard GPL v2 + Classpath header so every CN1-authored
file in the lib carries a license declaration.

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

* CI: use fully-qualified cn1 plugin coordinate in every script

The android-native-tests job intermittently failed with 'No plugin
found for prefix cn1' after a successful retry — same root cause that
the device-test workflow hit yesterday. The 'mvn install' step writes
local m2 group metadata for com.codenameone that doesn't always list
the codenameone-maven-plugin prefix mapping, leaving the next mvn
invocation unable to resolve cn1:.

Bypassing the prefix lookup with the
groupId:artifactId:version:goal form removes the dependency on the
local metadata being complete. Applied to all three call sites:
run-android-native-tests.sh, run-ios-native-tests.sh, and the
simulator-tests workflow step.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <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