From 8d2cfcfde3c64a41cc0bd0c1c0b381768c940090 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 22 Aug 2026 15:55:01 +0300 Subject: [PATCH 001/115] Check build hints at compile time instead of shipping them inert A build hint is a `codename1.arg.=` line that reaches a builder as `request.getArg(name, default)`. Nothing checked the name, so a misspelling was accepted, copied into the build request, never read, and silently discarded: a green build with the setting simply not applied. Our own agent reference had been shipping `android.xPermissions`, `android.minSdkVersion` and `android.sdkVersion` for exactly that reason. The builders read `android.xpermissions`, `android.min_sdk_version`, and nothing at all. Most hints can now be written as annotations on the application's main class, where javac does the checking: a misspelled name is an unknown symbol, a wrong value type is a type error, and a value outside a hint's supported set is an unknown enum constant. @Ios(newStorageLocation = true, themeMode = IosThemeMode.MODERN) @Android(minSdkVersion = 24, useAndroidX = true) @Desktop(titleBar = DesktopTitleBar.NATIVE) public class MyApplication extends Lifecycle { } The builders are untouched: `BuildHintAnnotationProcessor` converts the annotations back into the same key/value pairs and `CN1BuildMojo` merges them before the command-line overlay, the CN1Lib merges and both preflights, so a library still appends onto an annotation-supplied value and `-D` still wins. `Simulator` publishes them as system properties at startup so `cn1:run` sees hints that no longer live in the properties file. The properties file is untouched too. It stays the way to set the long tail and the open-ended families such as `android.permission.` that an annotation cannot express, with no new warnings or errors. Declaring one hint both ways is a build error. One catalog, five generated views --------------------------------- The hint set had been described in five places that had drifted apart: a prose table in the developer guide, a runtime scraper of that table in the Settings tool that guessed each type by string-matching the description, a fifteen-entry schema in the simulator, a fourteen-entry separator map in the plugin, and a hand-written agent reference. Only 147 of ~520 names appeared in more than one. `maven/build-hint-catalog` is now the single source of truth (529 hints: 457 mined from the builders, 56 documented-but-unread, 16 dynamic families; 82 exposed as annotation attributes). The annotations, the binding table the processor reads back, the guide's table, the simulator's editor schema and the agent reference are all generated from it. The guide's table goes from 208 rows to 529 with no prose lost. Enums are emitted only where the accepted set is demonstrable from the code that reads the hint -- `HardeningPreflight` rejects an unknown `harden.level`, `IOSDependencyManager` throws on an unknown `ios.dependencyManager`, and `GenerateDesktopAppWrapperMojo` silently falls back to `native` on an unknown `desktop.titleBar`, which is the failure this removes. Generated projects ------------------ The archetype and all four initializr templates now carry the annotations, and `cn1:migrate-build-hints` moves an existing project over. Eleven in-repo projects are migrated. `java.version` deliberately stays in the properties file: it picks the toolchain that compiles the class the annotations live on. Gates ----- `scripts/check-build-hint-catalog.sh` fails when code reads a hint the catalog does not describe, and when our own docs or templates name one that no builder reads. Its baseline is empty, so it is a hard gate rather than a ratchet. `scripts/gen-build-hint-annotations.sh --check` fails on generated-file drift. Both run in the Java 8 leg of PR CI. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/pr.yml | 14 + CLAUDE.md | 38 + .../annotations/buildhints/Android.java | 160 + .../buildhints/AndroidThemeMode.java | 49 + .../annotations/buildhints/Build.java | 64 + .../annotations/buildhints/Desktop.java | 72 + .../buildhints/DesktopTitleBar.java | 48 + .../buildhints/HardenControlFlow.java | 47 + .../annotations/buildhints/HardenLevel.java | 49 + .../annotations/buildhints/HardenStrings.java | 48 + .../annotations/buildhints/Hardening.java | 69 + .../buildhints/InstallLocation.java | 48 + .../codename1/annotations/buildhints/Ios.java | 166 + .../buildhints/IosDependencyManager.java | 50 + .../annotations/buildhints/IosPrivacy.java | 112 + .../buildhints/IosProjectType.java | 48 + .../annotations/buildhints/IosThemeMode.java | 49 + .../buildhints/NativeThemeMode.java | 48 + .../annotations/buildhints/OnDeviceDebug.java | 77 + .../annotations/buildhints/package-info.java | 52 + .../impl/javase/BuildHintCatalogDefaults.java | 307 ++ .../impl/javase/BuildHintSchemaDefaults.java | 6 + .../com/codename1/impl/javase/Simulator.java | 66 + .../common/codenameone_settings.properties | 1 - .../codenameone/developerguide/DemoCode.java | 2 + .../Advanced-Topics-Under-The-Hood.asciidoc | 718 +--- .../_generated-build-hints.adoc | 3187 +++++++++++++++++ maven/build-hint-catalog/pom.xml | 31 + .../common/codenameone_settings.properties | 27 +- .../common/src/main/java/__mainName__.java | 13 + maven/codenameone-maven-plugin/pom.xml | 5 + .../com/codename1/maven/CN1BuildMojo.java | 107 + .../codename1/maven/LibraryHintMerger.java | 51 +- .../maven/MigrateBuildHintsMojo.java | 505 +++ .../com/codename1/maven/OpenSettingsMojo.java | 8 +- .../maven/ProcessAnnotationsMojo.java | 56 +- .../maven/annotations/ProcessorContext.java | 35 + .../BuildHintAnnotationProcessor.java | 428 +++ ...ame1.maven.annotations.AnnotationProcessor | 1 + .../BuildHintAnnotationProcessorTest.java | 343 ++ maven/integration-tests/all.sh | 1 + .../build-hint-annotations-test.sh | 93 + maven/pom.xml | 1 + scripts/build-hint-catalog-baseline.txt | 8 + scripts/build_hint_miner.py | 100 + .../common/codenameone_settings.properties | 6 - .../certificatewizard/CertificateWizard.java | 5 + scripts/check-build-hint-catalog.py | 163 + scripts/check-build-hint-catalog.sh | 24 + .../common/codenameone_settings.properties | 5 - .../codenameone/playground/CN1Playground.java | 5 + .../common/codenameone_settings.properties | 4 - .../com/codenameone/fidelity/FidelityApp.java | 3 + .../common/codenameone_settings.properties | 8 +- .../codename1/gamebuilder/GameBuilder.java | 5 + scripts/gen-build-hint-annotations.sh | 55 + .../common/codenameone_settings.properties | 5 - .../guibuilder/CodenameOneGUIBuilder.java | 3 + .../common/codenameone_settings.properties | 7 - .../hellocodenameone/HelloCodenameOne.kt | 4 + .../src/main/resources/barebones-src.zip | Bin 1327 -> 1435 bytes .../common/src/main/resources/common.zip | Bin 251573 -> 251603 bytes .../common/src/main/resources/grub-src.zip | Bin 279805 -> 275075 bytes .../common/src/main/resources/kotlin-src.zip | Bin 1507 -> 1563 bytes .../common/src/main/resources/skill/SKILL.md | 2 +- .../skill/references/android-to-cn1.md | 2 +- .../skill/references/build-and-run.md | 26 +- .../resources/skill/references/build-hints.md | 191 +- .../skill/references/mobile-adaptability.md | 2 +- .../skill/references/native-interfaces.md | 31 +- .../common/src/main/resources/tweet-src.zip | Bin 357703 -> 356080 bytes .../common/codenameone_settings.properties | 3 - .../inputvalidation/InputValidationApp.java | 3 + .../common/codenameone_settings.properties | 6 - .../purchasetest/PurchaseTestApp.java | 4 + .../common/codenameone_settings.properties | 7 - scripts/settings/common/pom.xml | 20 +- .../settings/CodenameOneSettings.java | 57 +- .../settings/hints/BuildHintCatalog.java | 207 +- .../settings/hints/BuildHintMetadata.java | 40 + .../settings/project/ProjectBinding.java | 6 - .../settings/BuildHintCatalogTest.java | 105 +- .../codename1/settings/SettingsThemeTest.java | 7 +- scripts/settings/pom.xml | 5 + .../common/codenameone_settings.properties | 3 - .../codename1/videobuilder/VideoBuilder.java | 2 + tools/build-hint-bootstrap/README.md | 23 + tools/build-hint-bootstrap/curation.py | 220 ++ tools/build-hint-bootstrap/gen_catalog.py | 267 ++ tools/build-hint-bootstrap/gen_external.py | 66 + 90 files changed, 7880 insertions(+), 1135 deletions(-) create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/Android.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/AndroidThemeMode.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/Build.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/Desktop.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/DesktopTitleBar.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/HardenControlFlow.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/HardenLevel.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/HardenStrings.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/Hardening.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/InstallLocation.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/Ios.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/IosDependencyManager.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/IosPrivacy.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/IosProjectType.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/IosThemeMode.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/NativeThemeMode.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java create mode 100644 CodenameOne/src/com/codename1/annotations/buildhints/package-info.java create mode 100644 Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java create mode 100644 docs/developer-guide/_generated-build-hints.adoc create mode 100644 maven/build-hint-catalog/pom.xml create mode 100644 maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java create mode 100644 maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java create mode 100644 maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java create mode 100755 maven/integration-tests/build-hint-annotations-test.sh create mode 100644 scripts/build-hint-catalog-baseline.txt create mode 100644 scripts/build_hint_miner.py create mode 100755 scripts/check-build-hint-catalog.py create mode 100755 scripts/check-build-hint-catalog.sh create mode 100755 scripts/gen-build-hint-annotations.sh create mode 100644 tools/build-hint-bootstrap/README.md create mode 100644 tools/build-hint-bootstrap/curation.py create mode 100644 tools/build-hint-bootstrap/gen_catalog.py create mode 100644 tools/build-hint-bootstrap/gen_external.py diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index dcc8f9990c1..e70275e9810 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -416,6 +416,20 @@ jobs: - name: Run SpotBugs for ByteCodeTranslator if: ${{ matrix.java-version == 8 }} run: mvn -B -DskipTests=true -f vm/ByteCodeTranslator/pom.xml verify + # A build hint is a string nothing checks: a misspelled name is accepted, + # never read, and silently does nothing. The catalog in + # maven/build-hint-catalog is what gives every hint a type, a default and a + # value domain, and it is what the @Ios/@Android annotations, the developer + # guide table and the Settings tool are generated from. These two steps keep + # the catalog complete and the generated files in step with it. + - name: Check build hint catalog + if: ${{ matrix.java-version == 8 }} + run: scripts/check-build-hint-catalog.sh + - name: Check generated build hint annotations + if: ${{ matrix.java-version == 8 }} + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + scripts/gen-build-hint-annotations.sh --check # ParparVM's CHECKCAST is unchecked, so a failed cast does not throw # ClassCastException on iOS -- code written to catch it silently uses the # wrong object instead (issue #5531). core/android/ios are already compiled diff --git a/CLAUDE.md b/CLAUDE.md index 3ec43bd914a..fef8739422a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -213,6 +213,44 @@ removing one can make a previously-used private method dead. Findings land in each module's `target/spotbugsXml.xml`. +### Build hints are a catalog, not free-form strings + +A build hint is a `codename1.arg.=` line that reaches a builder as +`request.getArg(name, default)`. Nothing used to check the name, so a misspelled +hint was accepted, never read, and silently did nothing -- a green build with the +setting simply not applied. Our own agent reference shipped +`android.xPermissions`, `android.minSdkVersion` and `android.sdkVersion` for +years; the builders read `android.xpermissions`, `android.min_sdk_version`, and +nothing at all. + +**`maven/build-hint-catalog` is the single source of truth.** Every hint's name, +type, default, value domain, merge separator and documentation lives there, and +everything else is generated from it: + +- the `com.codename1.annotations.buildhints` annotations in `CodenameOne/src` +- `BuildHintAnnotationBinding`, which the annotation processor reads back +- the developer guide's build hint table (`docs/developer-guide/_generated-build-hints.adoc`) +- the simulator's Build Hint editor schema (`BuildHintCatalogDefaults`) +- the agent reference's annotation table (`skill/references/build-hints.md`) + +Adding a hint to a builder means adding it to the catalog in the same change. +Regenerate with: + +```bash +source tools/env.sh +scripts/gen-build-hint-annotations.sh # rewrite the generated files +scripts/gen-build-hint-annotations.sh --check # what CI runs +scripts/check-build-hint-catalog.sh # every hint the code reads is catalogued +``` + +`scripts/build-hint-catalog-baseline.txt` is a ratchet, and it is **empty**: every +hint the code reads is described. A new entry means a hint went in without a +catalog row. The same gate refuses a `codename1.arg.*` key in our own docs and +project templates that no builder reads. + +Do not re-run `tools/build-hint-bootstrap/` -- it seeded the catalog once and +would overwrite hand edits. + ### Never rely on ClassCastException **ParparVM's `CHECKCAST` is unchecked.** `BC_CHECKCAST` expands to nothing and the diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/Android.java b/CodenameOne/src/com/codename1/annotations/buildhints/Android.java new file mode 100644 index 00000000000..a308905c7fe --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/Android.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// Android build hints, checked by the compiler. +/// +/// Place this on your application's main class -- the class named by +/// `codename1.mainName`. An attribute you do not set is not written at all, so +/// the builder's own default applies; the values shown here are that default, +/// for reference. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface Android { + + /// Allows explicitly setting the `android:launchMode` attribute of the main + /// activity in android. Default is "singleTop," but for some applications you + /// may need to change this behaviour. In particular, apps that are meant to + /// open a file type will need to set this to "singleTask." See + /// https://developer.android.com/guide/topics/manifest/activity-element.html[Android + /// docs for the activity element] for more information about the + /// `android:launchMode` attribute. + String activityLaunchMode() default "singleTop"; + + /// Produces an Android App Bundle (.aab) rather than an APK. Required for new + /// Play Store submissions. + boolean appBundle() default false; + + /// Android build-tools version. It also selects the compile SDK, so there is no + /// separate compile-SDK hint. + String buildToolsVersion() default ""; + + /// Indicates whether the `RECORD_AUDIO` permission should be requested. Can be + /// `enabled` or any other value to disable this option + String captureRecord() default "enabled"; + + /// true/false defaults to true - indicates whether to include the debug version + /// in the build. Defaults conditionally rather than to a fixed value: when + /// android.release is on it defaults to false, and when release is off it + /// defaults to true, so a build that selects neither still produces something + /// installable (AndroidGradleBuilder.java:447-451). + boolean debug() default false; + + /// Turns off R8, falling back to the older shrinker. Note that hardening + /// requires R8, so this conflicts with harden.level. + boolean disableR8() default false; + + /// Boolean true/false defaults to true. Allows disabling the proguard + /// obfuscation even on release builds, notice that this isn't recommended + boolean enableProguard() default true; + + /// Gradle dependency statements to add to the app module, such as + /// implementation 'com.example:lib:1.0'. + /// Values are joined with `;` when the hint is written. + String[] gradleDep() default {}; + + /// Hides the Android status bar. + boolean hideStatusBar() default false; + + /// Maps to android:installLocation manifest entry defaults to auto. Can also be + /// set to internalOnly or preferExternal. + InstallLocation installLocation() default InstallLocation.AUTO; + + /// The license key for the Android app, this is required if you use in-app + /// purchase on Android + String licenseKey() default ""; + + /// The least SDK required to run this app, the default value changes based on + /// functionality but can be as low as 7. This corresponds to the XML attribute + /// `android:minSdkVersion`. + int minSdkVersion() default 19; + + /// Boolean true/false defaults to false. Multidex allows Android binaries to + /// reference more than 65536 methods. This slows builds a bit so you have it + /// off by default but if you get a build error mentioning this limit you should + /// turn this on. + boolean multidex() default true; + + /// Uses the current Firebase Cloud Messaging integration. Requires AndroidX and + /// Gradle 8.13 or newer. + boolean newFirebaseMessaging() default true; + + /// Arguments for the keep option in proguard allowing you to keep a pattern of + /// files for example, `-keep class com.mypackage.ProblemClass { *; }` + /// Values are joined with `\n` when the hint is written. + String[] proguardKeep() default {}; + + /// true/false defaults to true - indicates whether to include the release + /// version in the build + boolean release() default true; + + /// Extra Gradle repositories to resolve dependencies from. + /// Values are joined with `\n` when the hint is written. + String[] repositories() default {}; + + /// Indicates the Android SDK used to compile the Android build defaults to 21. + /// Notice that not all targets will work since the source might have some + /// limitations and not all SDK targets are installed on the build servers. + int targetSDKVersion() default 0; + + /// `auto`, `modern` / `material`, `hololight` (default for existing apps), + /// `legacy`. `auto` and `modern` / `material` opt in to the CSS-generated + /// Android Material 3 theme from `native-themes/android-material/theme.css`. + /// `hololight` is Android Holo Light (what the framework shipped on API 14+ + /// before this refactor). `legacy` loads the pre-Holo Android theme. The legacy + /// alias `cn1.androidTheme` is still accepted, and `and.hololight=true` still + /// maps to `hololight`. The default stays on `hololight` for existing apps + /// until you flip in a future release. + AndroidThemeMode themeMode() default AndroidThemeMode.AUTO; + + /// Statements added to the top-level Gradle build file rather than the app + /// module. + /// Values are joined with `\n` when the hint is written. + String[] topDependency() default {}; + + /// Use Android X instead of support libraries. This will also run a + /// find/replace on all source files to replace support libraries and artifacts + /// with AndroidX equivalents. + boolean useAndroidX() default false; + + /// defaults to an empty string. Allows developers of native Android code to add + /// text within the application block to define things such as widgets, services + /// etc. + String xapplication() default ""; + + /// Arbitrary text spliced into the generated app-module Gradle file. + /// Values are joined with `\n` when the hint is written. + String[] xgradle() default {}; + + /// more permissions for the Android manifest + String xpermissions() default ""; +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/AndroidThemeMode.java b/CodenameOne/src/com/codename1/annotations/buildhints/AndroidThemeMode.java new file mode 100644 index 00000000000..0fb0d887471 --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/AndroidThemeMode.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `and.themeMode` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum AndroidThemeMode { + AUTO("auto"), + MODERN("modern"), + HOLOLIGHT("hololight"), + LEGACY("legacy"); + + private final String wire; + + AndroidThemeMode(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/Build.java b/CodenameOne/src/com/codename1/annotations/buildhints/Build.java new file mode 100644 index 00000000000..031fac834db --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/Build.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// Build hints that are not specific to one platform. +/// +/// Place this on your application's main class -- the class named by +/// `codename1.mainName`. An attribute you do not set is not written at all, so +/// the builder's own default applies; the values shown here are that default, +/// for reference. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface Build { + + /// The application ID for an app that requires native Facebook login + /// integration, this defaults to null which means native Facebook support + /// shouldn't be in the app + String facebookAppId() default "706695982682332"; + + /// The Android/chrome push identifier, see the push section for more details + String gcmSenderId() default ""; + + /// `modern`, `legacy`, `custom` (default unset). Cross-platform override that + /// sets both `ios.themeMode` and `and.themeMode` together when those aren't set + /// explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + + /// Holo Light, `custom` disables the framework native theme entirely. The + /// legacy alias `cn1.nativeTheme` is still accepted. + NativeThemeMode nativeTheme() default NativeThemeMode.MODERN; + + /// true/false (defaults to false). Blocks codename one from injecting its own + /// resources when set to true, the only effect this has is in slightly reducing + /// archive size. This might have adverse effects on some features of Codename + /// One so it isn't recommended. + boolean noExtraResources() default false; +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/Desktop.java b/CodenameOne/src/com/codename1/annotations/buildhints/Desktop.java new file mode 100644 index 00000000000..d8fcae1c401 --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/Desktop.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// Desktop build hints, checked by the compiler. +/// +/// Place this on your application's main class -- the class named by +/// `codename1.mainName`. An attribute you do not set is not written at all, so +/// the builder's own default applies; the values shown here are that default, +/// for reference. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface Desktop { + + /// Boolean true/false defaults to true. When set to true some values will ve + /// implicitly doubled to deal with retina displays and icons etc. Will use + /// higher DPI's + boolean adaptToRetina() default true; + + /// Starts the desktop build in full-screen mode. + boolean fullscreen() default false; + + /// Height in pixels for the form in desktop builds, will be doubled for retina + /// grade displays. Defaults to 600. + int height() default 600; + + /// Enables grab-able, click-to-page desktop scrollbars. + boolean interactiveScrollbars() default true; + + /// Boolean true/false defaults to true. Indicates whether the UI in the desktop + /// build is resizable + boolean resizable() default true; + + /// How the desktop window is framed: native for the OS title bar and menu bar, + /// custom for an undecorated window with a Codename One drawn title bar, or + /// toolbar for the legacy in-app Toolbar. An unrecognized value falls back to + /// native with a warning. + DesktopTitleBar titleBar() default DesktopTitleBar.NATIVE; + + /// Width in pixels for the form in desktop builds, will be doubled for retina + /// grade displays. Defaults to 800. + int width() default 800; +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/DesktopTitleBar.java b/CodenameOne/src/com/codename1/annotations/buildhints/DesktopTitleBar.java new file mode 100644 index 00000000000..b9da214763c --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/DesktopTitleBar.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `desktop.titleBar` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum DesktopTitleBar { + NATIVE("native"), + CUSTOM("custom"), + TOOLBAR("toolbar"); + + private final String wire; + + DesktopTitleBar(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/HardenControlFlow.java b/CodenameOne/src/com/codename1/annotations/buildhints/HardenControlFlow.java new file mode 100644 index 00000000000..3e10121724d --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/HardenControlFlow.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `harden.controlFlow` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum HardenControlFlow { + OFF("off"), + ON("on"); + + private final String wire; + + HardenControlFlow(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/HardenLevel.java b/CodenameOne/src/com/codename1/annotations/buildhints/HardenLevel.java new file mode 100644 index 00000000000..ae32518c1be --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/HardenLevel.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `harden.level` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum HardenLevel { + OFF("off"), + STANDARD("standard"), + AGGRESSIVE("aggressive"), + PARANOID("paranoid"); + + private final String wire; + + HardenLevel(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/HardenStrings.java b/CodenameOne/src/com/codename1/annotations/buildhints/HardenStrings.java new file mode 100644 index 00000000000..15f866cd08d --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/HardenStrings.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `harden.strings` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum HardenStrings { + OFF("off"), + CONSTANTS("constants"), + ALL("all"); + + private final String wire; + + HardenStrings(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/Hardening.java b/CodenameOne/src/com/codename1/annotations/buildhints/Hardening.java new file mode 100644 index 00000000000..c9190a591fd --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/Hardening.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// App hardening build hints, checked by the compiler. +/// +/// Place this on your application's main class -- the class named by +/// `codename1.mainName`. An attribute you do not set is not written at all, so +/// the builder's own default applies; the values shown here are that default, +/// for reference. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface Hardening { + + /// Permits a local or source build to run with hardening requested but not + /// applied. Without it such a build is refused, so a hardened app is never + /// shipped from a target that cannot actually harden it. + boolean allowUnhardenedLocalBuild() default false; + + /// Overrides control-flow obfuscation independently of harden.level. + HardenControlFlow controlFlow() default HardenControlFlow.OFF; + + /// Keep rules in ProGuard syntax, one per line, for classes that are resolved + /// by name at runtime and so cannot be found by the automatic analysis. Same + /// syntax as android.proguardKeep, so existing rules port directly. Rules are + /// separated by newlines only, because a semicolon is legal inside a rule body + /// such as { *; }. + String keep() default ""; + + /// Master switch for app hardening: off, standard, aggressive or paranoid. An + /// unrecognized value fails the build rather than being quietly treated as off. + HardenLevel level() default HardenLevel.OFF; + + /// Overrides symbol renaming independently of harden.level. + boolean rename() default false; + + /// Overrides string obfuscation independently of harden.level: off, constants + /// or all. + HardenStrings strings() default HardenStrings.OFF; +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/InstallLocation.java b/CodenameOne/src/com/codename1/annotations/buildhints/InstallLocation.java new file mode 100644 index 00000000000..a1c7655fa57 --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/InstallLocation.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `android.installLocation` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum InstallLocation { + AUTO("auto"), + INTERNAL_ONLY("internalOnly"), + PREFER_EXTERNAL("preferExternal"); + + private final String wire; + + InstallLocation(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/Ios.java b/CodenameOne/src/com/codename1/annotations/buildhints/Ios.java new file mode 100644 index 00000000000..081b7a871bf --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/Ios.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// iOS build hints, checked by the compiler. +/// +/// Place this on your application's main class -- the class named by +/// `codename1.mainName`. An attribute you do not set is not written at all, so +/// the builder's own default applies; the values shown here are that default, +/// for reference. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface Ios { + + /// A semicolon separated list of libraries that should be linked to the app to + /// build it + /// Values are joined with `;` when the hint is written. + String[] addLibs() default {}; + + /// Comma separated list of url schemes that `canExecute` will respect on iOS. + /// If the url scheme isn't mentioned here `canExecute` will return false + /// starting with iOS 9. Notice that this collides with `ios.plistInject` when + /// used with the `LSApplicationQueriesSchemes...` value so you + /// should use one or the other. For example, to enable `canExecute` for a url + /// like `myurl://xys` you can use: `myurl,myotherurl` + /// Values are joined with `,` when the hint is written. + String[] applicationQueriesSchemes() default {}; + + /// Objective-C code that can be injected into the iOS app delegate at the top + /// of the body of the didFinishLaunchingWithOptions callback method + String beforeFinishLaunching() default ""; + + /// Indicates the version number of the bundle, this is useful if you want to + /// create a minor version number change for the beta testing support + String bundleVersion() default ""; + + /// Which native dependency manager to use: auto picks one from whichever of + /// ios.pods and ios.spm.packages is set, and cocoapods, spm or both require the + /// matching hint to be set. An unrecognized value fails the build. + IosDependencyManager dependencyManager() default IosDependencyManager.AUTO; + + /// Minimum iOS version the build targets. Set it to the lowest iOS you actually + /// support; a higher value excludes older devices from the App Store listing. + String deploymentTarget() default ""; + + /// Objective-C code that can be injected into the iOS app delegate at the top + /// of the file. For example, if you need to include headers or make special + /// imports for other injected code + String glAppDelegateHeader() default ""; + + /// true/false (defaults to false). Whether to include the push capabilities in + /// the iOS build. Notice that the IDE plugin has an "Include Push" check box + /// you *should* use under the iOS section. + boolean includePush() default false; + + /// UIInterfaceOrientationPortrait by default. Indicates the orientation, one or + /// more of (separated by colon :): `UIInterfaceOrientationPortrait`, + /// `UIInterfaceOrientationPortraitUpsideDown`, + /// `UIInterfaceOrientationLandscapeLeft`, + /// `UIInterfaceOrientationLandscapeRight`. Notice that the IDE plugin has an + /// "Interface Orientation" combo box you *should* use under the iOS section. + String interfaceOrientation() default ""; + + /// The null and empty-string reads of this hint are presence checks; 6.0 is the + /// substantive default (IPhoneBuilder.java:4671). + String minDeploymentTarget() default "6.0"; + + /// true/false defaults to false but defined on new projects as true by default. + /// This changes the storage directory on iOS from using caches to using the + /// documents directory which is the recommended location but might break + /// compatibility. This is described in + /// https://github.com/codenameone/CodenameOne/issues/1480[this issue] + boolean newStorageLocation() default true; + + /// Added the `-ObjC` compile flag to the project files which some native + /// libraries require + boolean objC() default false; + + /// entries to inject into the iOS plist file during build. + String plistInject() default ""; + + /// A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be + /// linked to the app to build it. For example, `AFNetworking ~> 2.6, + /// ORStackView ~> 3.0, SwiftyJSON ~> 2.3` + /// Values are joined with `,` when the hint is written. + String[] pods() default {}; + + /// Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a + /// minimum platform level. For example, `ios.pods.platform=7.0`. + String podsPlatform() default ""; + + /// Extra CocoaPods spec repositories to search, in addition to the default + /// trunk. + /// Values are joined with `,` when the hint is written. + String[] podsSources() default {}; + + /// true/false defaults to false. The iOS build process adapts the submitted + /// icon for iOS conventions (adding an overlay) that might not be appropriate + /// on some icons. Setting this to true leaves the icon unchanged (only scaled). + boolean prerenderedIcon() default false; + + /// one of ios, ipad, iphone (defaults to ios). Indicates whether the resulting + /// binary is targeted to the iphone only or ipad only. Notice that the IDE + /// plugin has a "Project Type" combo box you *should* use under the iOS + /// section. + IosProjectType projectType() default IosProjectType.IOS; + + /// Swift Package Manager packages to link, one per entry, each written as + /// identity|url|requirement. + /// Values are joined with `;` when the hint is written. + String[] spmPackages() default {}; + + /// Specifies the team ID associated with the iOS provisioning profile and + /// certificate. Use `ios.debug.teamId` and `ios.release.teamId` to specify + /// different team IDs for debug and release builds respectively. + String teamId() default ""; + + /// `auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the + /// existing iOS 7 flat theme so pre-refactor screenshot goldens and apps see no + /// behavior change. `modern` / `liquid` opts in to the CSS-generated iOS Modern + /// (liquid-glass) theme shipped from `native-themes/ios-modern/theme.css`. + /// `ios7` / `flat` is the same as `auto` - pre-liquid iOS 7 flat theme; + /// `legacy` / `iphone` loads the pre-iOS 7 iPhone theme. The `auto` -> modern + /// flip is planned for a future release. + IosThemeMode themeMode() default IosThemeMode.AUTO; + + /// true/false (defaults to true). Enables iOS UIScene lifecycle support. + /// UIScene lets iOS manage one or more app UI sessions independently, improving + /// lifecycle handling in modern iOS versions. Apple has indicated UIScene will + /// be required starting with iOS 27, so this is now on by default; set the flag + /// to `false` only if you need to temporarily fall back to the legacy + /// `UIApplicationDelegate` lifecycle. + boolean uiscene() default true; + + /// Allows intercepting a URL call using the syntax `urlPrefix` + String urlScheme() default ""; +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/IosDependencyManager.java b/CodenameOne/src/com/codename1/annotations/buildhints/IosDependencyManager.java new file mode 100644 index 00000000000..d30fa80f9ba --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/IosDependencyManager.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `ios.dependencyManager` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum IosDependencyManager { + AUTO("auto"), + COCOAPODS("cocoapods"), + SPM("spm"), + BOTH("both"), + NONE("none"); + + private final String wire; + + IosDependencyManager(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/IosPrivacy.java b/CodenameOne/src/com/codename1/annotations/buildhints/IosPrivacy.java new file mode 100644 index 00000000000..0375ac2116a --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/IosPrivacy.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// iOS `Info.plist` privacy usage descriptions. Set the one for every protected +/// resource your app touches: the build server accepts an app without them, and +/// the App Store rejects it. +/// +/// Place this on your application's main class -- the class named by +/// `codename1.mainName`. An attribute you do not set is not written at all, so +/// the builder's own default applies; the values shown here are that default, +/// for reference. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface IosPrivacy { + + /// The text iOS shows when the app first asks for the calendars full access. It + /// becomes the `NSCalendarsFullAccessUsageDescription` key in `Info.plist`. The + /// App Store rejects an app that touches this resource without one. + String calendarsFullAccessUsageDescription() default "This app uses your calendars to read and schedule events."; + + /// The text iOS shows when the app first asks for the calendars. It becomes the + /// `NSCalendarsUsageDescription` key in `Info.plist`. The App Store rejects an + /// app that touches this resource without one. + String calendarsUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the calendars write only + /// access. It becomes the `NSCalendarsWriteOnlyAccessUsageDescription` key in + /// `Info.plist`. The App Store rejects an app that touches this resource + /// without one. + String calendarsWriteOnlyAccessUsageDescription() default "This app uses your calendar to schedule events."; + + /// The text iOS shows when the app first asks for the camera. It becomes the + /// `NSCameraUsageDescription` key in `Info.plist`. The App Store rejects an app + /// that touches this resource without one. + String cameraUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the health share. It becomes + /// the `NSHealthShareUsageDescription` key in `Info.plist`. The App Store + /// rejects an app that touches this resource without one. + String healthShareUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the health update. It becomes + /// the `NSHealthUpdateUsageDescription` key in `Info.plist`. The App Store + /// rejects an app that touches this resource without one. + String healthUpdateUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the local network. It becomes + /// the `NSLocalNetworkUsageDescription` key in `Info.plist`. The App Store + /// rejects an app that touches this resource without one. + String localNetworkUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the location always and when + /// in use. It becomes the `NSLocationAlwaysAndWhenInUseUsageDescription` key in + /// `Info.plist`. The App Store rejects an app that touches this resource + /// without one. + String locationAlwaysAndWhenInUseUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the location always. It + /// becomes the `NSLocationAlwaysUsageDescription` key in `Info.plist`. The App + /// Store rejects an app that touches this resource without one. + String locationAlwaysUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the location when in use. It + /// becomes the `NSLocationWhenInUseUsageDescription` key in `Info.plist`. The + /// App Store rejects an app that touches this resource without one. + String locationWhenInUseUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the microphone. It becomes + /// the `NSMicrophoneUsageDescription` key in `Info.plist`. The App Store + /// rejects an app that touches this resource without one. + String microphoneUsageDescription() default ""; + + /// The text iOS shows when the app first asks for the reminders full access. It + /// becomes the `NSRemindersFullAccessUsageDescription` key in `Info.plist`. The + /// App Store rejects an app that touches this resource without one. + String remindersFullAccessUsageDescription() default "This app uses your reminders to read and schedule tasks."; + + /// The text iOS shows when the app first asks for the reminders. It becomes the + /// `NSRemindersUsageDescription` key in `Info.plist`. The App Store rejects an + /// app that touches this resource without one. + String remindersUsageDescription() default ""; +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/IosProjectType.java b/CodenameOne/src/com/codename1/annotations/buildhints/IosProjectType.java new file mode 100644 index 00000000000..1e18538ee3b --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/IosProjectType.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `ios.project_type` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum IosProjectType { + IOS("ios"), + IPAD("ipad"), + IPHONE("iphone"); + + private final String wire; + + IosProjectType(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/IosThemeMode.java b/CodenameOne/src/com/codename1/annotations/buildhints/IosThemeMode.java new file mode 100644 index 00000000000..ca2a7d24aa7 --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/IosThemeMode.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `ios.themeMode` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum IosThemeMode { + AUTO("auto"), + MODERN("modern"), + IOS7("ios7"), + LEGACY("legacy"); + + private final String wire; + + IosThemeMode(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/NativeThemeMode.java b/CodenameOne/src/com/codename1/annotations/buildhints/NativeThemeMode.java new file mode 100644 index 00000000000..7b9f2291ebb --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/NativeThemeMode.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +/// Accepted values of the `nativeTheme` build hint. +/// +/// Each constant carries the string the build actually receives, which is not +/// always the constant's own name. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +public enum NativeThemeMode { + MODERN("modern"), + LEGACY("legacy"), + CUSTOM("custom"); + + private final String wire; + + NativeThemeMode(String wire) { + this.wire = wire; + } + + /// The value written into the build hint. + public String wireValue() { + return wire; + } +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java b/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java new file mode 100644 index 00000000000..14a467ace1e --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations.buildhints; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// On-device debugging build hints for iOS and Android. +/// +/// Place this on your application's main class -- the class named by +/// `codename1.mainName`. An attribute you do not set is not written at all, so +/// the builder's own default applies; the values shown here are that default, +/// for reference. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface OnDeviceDebug { + + /// Boolean true/false defaults to false. When `true`, the generated + /// `AndroidManifest.xml` is marked `android:debuggable="true"`, R8/proguard is + /// disabled, and the build is pinned to debug-only (`android.release` is forced + /// off and `android.debug` is forced on) so a stray hint can't ship a + /// release-signed APK that's `debuggable="true"`. Pair with the + /// `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run + /// configs) to install, launch, forward JDWP, and stream logcat through adb. + /// Has no effect on builds that don't carry it — release builds are unaffected. + /// See the On-Device Debugging (Android) chapter for the full flow. + boolean android() default false; + + /// Boolean true/false defaults to false. When `true`, the iOS build links a + /// small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM + /// translator emits source-line and locals metadata so a desktop proxy can + /// serve the running app to any JDWP-speaking debugger. Has no effect on + /// release builds. See the On-Device Debugging (iOS) chapter for the full flow. + boolean ios() default false; + + /// Hostname or IP address the device-side listener dials to reach the desktop + /// proxy. Default `127.0.0.1` (correct for the native iOS simulator). For a + /// physical device, set this to the developer laptop's LAN IP. Has no effect + /// unless `ios.onDeviceDebug=true`. + String iosProxyHost() default "127.0.0.1"; + + /// TCP port on `ios.onDeviceDebug.proxyHost` where the proxy is listening for + /// the device. Default `55333`. Has no effect unless `ios.onDeviceDebug=true`. + int iosProxyPort() default 55333; + + /// Boolean true/false defaults to false. When `true`, the app blocks at startup + /// until the proxy connects and the IDE tells the VM to continue. Useful when + /// the breakpoint to investigate fires during app boot. Has no effect unless + /// `ios.onDeviceDebug=true`. + boolean iosWaitForAttach() default false; +} diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/package-info.java b/CodenameOne/src/com/codename1/annotations/buildhints/package-info.java new file mode 100644 index 00000000000..f040888412c --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/buildhints/package-info.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +/// Build hints expressed as annotations, so the compiler checks them. +/// +/// A build hint used to be a `codename1.arg.=` line in +/// `codenameone_settings.properties`. Nothing validated it, so a misspelled +/// name was copied into the build request, never read, and silently dropped: +/// the build stayed green and the setting simply did nothing. Written as an +/// annotation the same mistake is an unknown symbol, a wrong value type is a +/// type error, and a value outside a hint's supported set is an unknown enum +/// constant. +/// +/// Put the annotations on your application's main class: +/// +/// ```java +/// @Ios(newStorageLocation = true, themeMode = IosThemeMode.MODERN) +/// @Android(themeMode = AndroidThemeMode.MODERN) +/// @Desktop(titleBar = DesktopTitleBar.NATIVE) +/// public class MyApplication { +/// } +/// ``` +/// +/// These annotations cover the hints most applications set. The rest, and the +/// open-ended families such as `android.permission.` that an annotation +/// cannot express, are still set in `codenameone_settings.properties`, which +/// continues to work exactly as before. Setting the same hint in both places is +/// a build error. +/// +/// Generated from com.codename1.build.shared.BuildHints by +/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and +/// re-run scripts/gen-build-hint-annotations.sh. +package com.codename1.annotations.buildhints; diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java new file mode 100644 index 00000000000..477dbc68ce8 --- /dev/null +++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.impl.javase; + +/** + * Build Hint editor schema for every hint that has a build hint annotation. + * + *

Generated from com.codename1.build.shared.BuildHints by + * BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and re-run + * scripts/gen-build-hint-annotations.sh.

+ * + *

Registered after {@link BuildHintSchemaDefaults}, whose hand-written + * entries take precedence because the shared setter never overwrites.

+ */ +final class BuildHintCatalogDefaults { + + private BuildHintCatalogDefaults() { + } + + static void register() { + + set("{{@IosPrivacy}}.label", "iOS Privacy Strings"); + set("{{#IosPrivacy#ios.NSCalendarsFullAccessUsageDescription}}.label", "Calendars full access usage description"); + set("{{#IosPrivacy#ios.NSCalendarsFullAccessUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSCalendarsUsageDescription}}.label", "Calendars usage description"); + set("{{#IosPrivacy#ios.NSCalendarsUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSCalendarsWriteOnlyAccessUsageDescription}}.label", "Calendars write only access usage description"); + set("{{#IosPrivacy#ios.NSCalendarsWriteOnlyAccessUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSCameraUsageDescription}}.label", "Camera usage description"); + set("{{#IosPrivacy#ios.NSCameraUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSHealthShareUsageDescription}}.label", "Health share usage description"); + set("{{#IosPrivacy#ios.NSHealthShareUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSHealthUpdateUsageDescription}}.label", "Health update usage description"); + set("{{#IosPrivacy#ios.NSHealthUpdateUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSLocalNetworkUsageDescription}}.label", "Local network usage description"); + set("{{#IosPrivacy#ios.NSLocalNetworkUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSLocationAlwaysAndWhenInUseUsageDescription}}.label", "Location always and when in use usage description"); + set("{{#IosPrivacy#ios.NSLocationAlwaysAndWhenInUseUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSLocationAlwaysUsageDescription}}.label", "Location always usage description"); + set("{{#IosPrivacy#ios.NSLocationAlwaysUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSLocationWhenInUseUsageDescription}}.label", "Location when in use usage description"); + set("{{#IosPrivacy#ios.NSLocationWhenInUseUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSMicrophoneUsageDescription}}.label", "Microphone usage description"); + set("{{#IosPrivacy#ios.NSMicrophoneUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSRemindersFullAccessUsageDescription}}.label", "Reminders full access usage description"); + set("{{#IosPrivacy#ios.NSRemindersFullAccessUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSRemindersUsageDescription}}.label", "Reminders usage description"); + set("{{#IosPrivacy#ios.NSRemindersUsageDescription}}.type", "TextField"); + + set("{{@Ios}}.label", "iOS"); + set("{{#Ios#ios.add_libs}}.label", "Add libs"); + set("{{#Ios#ios.add_libs}}.type", "TextArea"); + set("{{#Ios#ios.add_libs}}.description", "A semicolon separated list of libraries that should be linked to the app to build it"); + set("{{#Ios#ios.applicationQueriesSchemes}}.label", "Application queries schemes"); + set("{{#Ios#ios.applicationQueriesSchemes}}.type", "TextArea"); + set("{{#Ios#ios.applicationQueriesSchemes}}.description", "Comma separated list of url schemes that `canExecute` will respect on iOS. If the url scheme isn't mentioned here `canExecute` will return false starting with iOS 9. Notice that this collides with `ios.plistInject` when used with the `LSApplicationQueriesSchemes...` value so you should use one or the other. For example, to enable `canExecute` for a url like `myurl://xys` you can use: `myurl,myotherurl`"); + set("{{#Ios#ios.beforeFinishLaunching}}.label", "Before finish launching"); + set("{{#Ios#ios.beforeFinishLaunching}}.type", "TextArea"); + set("{{#Ios#ios.beforeFinishLaunching}}.description", "Objective-C code that can be injected into the iOS app delegate at the top of the body of the didFinishLaunchingWithOptions callback method"); + set("{{#Ios#ios.bundleVersion}}.label", "Bundle version"); + set("{{#Ios#ios.bundleVersion}}.type", "TextField"); + set("{{#Ios#ios.bundleVersion}}.description", "Indicates the version number of the bundle, this is useful if you want to create a minor version number change for the beta testing support"); + set("{{#Ios#ios.dependencyManager}}.label", "Dependency manager"); + set("{{#Ios#ios.dependencyManager}}.type", "Select"); + set("{{#Ios#ios.dependencyManager}}.values", "auto,cocoapods,spm,both,none"); + set("{{#Ios#ios.dependencyManager}}.description", "Which native dependency manager to use: auto picks one from whichever of ios.pods and ios.spm.packages is set, and cocoapods, spm or both require the matching hint to be set. An unrecognized value fails the build."); + set("{{#Ios#ios.deployment_target}}.label", "Deployment target"); + set("{{#Ios#ios.deployment_target}}.type", "TextField"); + set("{{#Ios#ios.deployment_target}}.description", "Minimum iOS version the build targets. Set it to the lowest iOS you actually support; a higher value excludes older devices from the App Store listing."); + set("{{#Ios#ios.glAppDelegateHeader}}.label", "Gl app delegate header"); + set("{{#Ios#ios.glAppDelegateHeader}}.type", "TextArea"); + set("{{#Ios#ios.glAppDelegateHeader}}.description", "Objective-C code that can be injected into the iOS app delegate at the top of the file. For example, if you need to include headers or make special imports for other injected code"); + set("{{#Ios#ios.includePush}}.label", "Include push"); + set("{{#Ios#ios.includePush}}.type", "Checkbox"); + set("{{#Ios#ios.includePush}}.description", "true/false (defaults to false). Whether to include the push capabilities in the iOS build. Notice that the IDE plugin has an \"Include Push\" check box you *should* use under the iOS section."); + set("{{#Ios#ios.interface_orientation}}.label", "Interface orientation"); + set("{{#Ios#ios.interface_orientation}}.type", "TextField"); + set("{{#Ios#ios.interface_orientation}}.description", "UIInterfaceOrientationPortrait by default. Indicates the orientation, one or more of (separated by colon :): `UIInterfaceOrientationPortrait`, `UIInterfaceOrientationPortraitUpsideDown`, `UIInterfaceOrientationLandscapeLeft`, `UIInterfaceOrientationLandscapeRight`. Notice that the IDE plugin has an \"Interface Orientation\" combo box you *should* use under the iOS section."); + set("{{#Ios#ios.minDeploymentTarget}}.label", "Min deployment target"); + set("{{#Ios#ios.minDeploymentTarget}}.type", "TextField"); + set("{{#Ios#ios.minDeploymentTarget}}.description", "The null and empty-string reads of this hint are presence checks; 6.0 is the substantive default (IPhoneBuilder.java:4671)."); + set("{{#Ios#ios.newStorageLocation}}.label", "New storage location"); + set("{{#Ios#ios.newStorageLocation}}.type", "Checkbox"); + set("{{#Ios#ios.newStorageLocation}}.description", "true/false defaults to false but defined on new projects as true by default. This changes the storage directory on iOS from using caches to using the documents directory which is the recommended location but might break compatibility. This is described in https://github.com/codenameone/CodenameOne/issues/1480[this issue]"); + set("{{#Ios#ios.objC}}.label", "Obj c"); + set("{{#Ios#ios.objC}}.type", "Checkbox"); + set("{{#Ios#ios.objC}}.description", "Added the `-ObjC` compile flag to the project files which some native libraries require"); + set("{{#Ios#ios.plistInject}}.label", "Plist inject"); + set("{{#Ios#ios.plistInject}}.type", "TextArea"); + set("{{#Ios#ios.plistInject}}.description", "entries to inject into the iOS plist file during build."); + set("{{#Ios#ios.pods}}.label", "Pods"); + set("{{#Ios#ios.pods}}.type", "TextArea"); + set("{{#Ios#ios.pods}}.description", "A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be linked to the app to build it. For example, `AFNetworking ~> 2.6, ORStackView ~> 3.0, SwiftyJSON ~> 2.3`"); + set("{{#Ios#ios.pods.platform}}.label", "Pods platform"); + set("{{#Ios#ios.pods.platform}}.type", "TextField"); + set("{{#Ios#ios.pods.platform}}.description", "Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a minimum platform level. For example, `ios.pods.platform=7.0`."); + set("{{#Ios#ios.pods.sources}}.label", "Pods sources"); + set("{{#Ios#ios.pods.sources}}.type", "TextArea"); + set("{{#Ios#ios.pods.sources}}.description", "Extra CocoaPods spec repositories to search, in addition to the default trunk."); + set("{{#Ios#ios.prerendered_icon}}.label", "Prerendered icon"); + set("{{#Ios#ios.prerendered_icon}}.type", "Checkbox"); + set("{{#Ios#ios.prerendered_icon}}.description", "true/false defaults to false. The iOS build process adapts the submitted icon for iOS conventions (adding an overlay) that might not be appropriate on some icons. Setting this to true leaves the icon unchanged (only scaled)."); + set("{{#Ios#ios.project_type}}.label", "Project type"); + set("{{#Ios#ios.project_type}}.type", "Select"); + set("{{#Ios#ios.project_type}}.values", "ios,ipad,iphone"); + set("{{#Ios#ios.project_type}}.description", "one of ios, ipad, iphone (defaults to ios). Indicates whether the resulting binary is targeted to the iphone only or ipad only. Notice that the IDE plugin has a \"Project Type\" combo box you *should* use under the iOS section."); + set("{{#Ios#ios.spm.packages}}.label", "Spm packages"); + set("{{#Ios#ios.spm.packages}}.type", "TextArea"); + set("{{#Ios#ios.spm.packages}}.description", "Swift Package Manager packages to link, one per entry, each written as identity|url|requirement."); + set("{{#Ios#ios.teamId}}.label", "Team id"); + set("{{#Ios#ios.teamId}}.type", "TextField"); + set("{{#Ios#ios.teamId}}.description", "Specifies the team ID associated with the iOS provisioning profile and certificate. Use `ios.debug.teamId` and `ios.release.teamId` to specify different team IDs for debug and release builds respectively."); + set("{{#Ios#ios.themeMode}}.label", "Theme mode"); + set("{{#Ios#ios.themeMode}}.type", "Select"); + set("{{#Ios#ios.themeMode}}.values", "auto,modern,ios7,legacy"); + set("{{#Ios#ios.themeMode}}.description", "`auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the existing iOS 7 flat theme so pre-refactor screenshot goldens and apps see no behavior change. `modern` / `liquid` opts in to the CSS-generated iOS Modern (liquid-glass) theme shipped from `native-themes/ios-modern/theme.css`. `ios7` / `flat` is the same as `auto` - pre-liquid iOS 7 flat theme; `legacy` / `iphone` loads the pre-iOS 7 iPhone theme. The `auto` -> modern flip is planned for a future release."); + set("{{#Ios#ios.uiscene}}.label", "Uiscene"); + set("{{#Ios#ios.uiscene}}.type", "Checkbox"); + set("{{#Ios#ios.uiscene}}.description", "true/false (defaults to true). Enables iOS UIScene lifecycle support. UIScene lets iOS manage one or more app UI sessions independently, improving lifecycle handling in modern iOS versions. Apple has indicated UIScene will be required starting with iOS 27, so this is now on by default; set the flag to `false` only if you need to temporarily fall back to the legacy `UIApplicationDelegate` lifecycle."); + set("{{#Ios#ios.urlScheme}}.label", "Url scheme"); + set("{{#Ios#ios.urlScheme}}.type", "TextField"); + set("{{#Ios#ios.urlScheme}}.description", "Allows intercepting a URL call using the syntax `urlPrefix`"); + + set("{{@Android}}.label", "Android"); + set("{{#Android#android.activity.launchMode}}.label", "Activity launch mode"); + set("{{#Android#android.activity.launchMode}}.type", "TextField"); + set("{{#Android#android.activity.launchMode}}.description", "Allows explicitly setting the `android:launchMode` attribute of the main activity in android. Default is \"singleTop,\" but for some applications you may need to change this behaviour. In particular, apps that are meant to open a file type will need to set this to \"singleTask.\" See https://developer.android.com/guide/topics/manifest/activity-element.html[Android docs for the activity element] for more information about the `android:launchMode` attribute."); + set("{{#Android#android.appBundle}}.label", "App bundle"); + set("{{#Android#android.appBundle}}.type", "Checkbox"); + set("{{#Android#android.appBundle}}.description", "Produces an Android App Bundle (.aab) rather than an APK. Required for new Play Store submissions."); + set("{{#Android#android.buildToolsVersion}}.label", "Build tools version"); + set("{{#Android#android.buildToolsVersion}}.type", "TextField"); + set("{{#Android#android.buildToolsVersion}}.description", "Android build-tools version. It also selects the compile SDK, so there is no separate compile-SDK hint."); + set("{{#Android#android.captureRecord}}.label", "Capture record"); + set("{{#Android#android.captureRecord}}.type", "TextField"); + set("{{#Android#android.captureRecord}}.description", "Indicates whether the `RECORD_AUDIO` permission should be requested. Can be `enabled` or any other value to disable this option"); + set("{{#Android#android.debug}}.label", "Debug"); + set("{{#Android#android.debug}}.type", "Checkbox"); + set("{{#Android#android.debug}}.description", "true/false defaults to true - indicates whether to include the debug version in the build. Defaults conditionally rather than to a fixed value: when android.release is on it defaults to false, and when release is off it defaults to true, so a build that selects neither still produces something installable (AndroidGradleBuilder.java:447-451)."); + set("{{#Android#android.disableR8}}.label", "Disable r8"); + set("{{#Android#android.disableR8}}.type", "Checkbox"); + set("{{#Android#android.disableR8}}.description", "Turns off R8, falling back to the older shrinker. Note that hardening requires R8, so this conflicts with harden.level."); + set("{{#Android#android.enableProguard}}.label", "Enable proguard"); + set("{{#Android#android.enableProguard}}.type", "Checkbox"); + set("{{#Android#android.enableProguard}}.description", "Boolean true/false defaults to true. Allows disabling the proguard obfuscation even on release builds, notice that this isn't recommended"); + set("{{#Android#android.gradleDep}}.label", "Gradle dep"); + set("{{#Android#android.gradleDep}}.type", "TextArea"); + set("{{#Android#android.gradleDep}}.description", "Gradle dependency statements to add to the app module, such as implementation 'com.example:lib:1.0'."); + set("{{#Android#android.hideStatusBar}}.label", "Hide status bar"); + set("{{#Android#android.hideStatusBar}}.type", "Checkbox"); + set("{{#Android#android.hideStatusBar}}.description", "Hides the Android status bar."); + set("{{#Android#android.installLocation}}.label", "Install location"); + set("{{#Android#android.installLocation}}.type", "Select"); + set("{{#Android#android.installLocation}}.values", "auto,internalOnly,preferExternal"); + set("{{#Android#android.installLocation}}.description", "Maps to android:installLocation manifest entry defaults to auto. Can also be set to internalOnly or preferExternal."); + set("{{#Android#android.licenseKey}}.label", "License key"); + set("{{#Android#android.licenseKey}}.type", "TextField"); + set("{{#Android#android.licenseKey}}.description", "The license key for the Android app, this is required if you use in-app purchase on Android"); + set("{{#Android#android.min_sdk_version}}.label", "Min sdk version"); + set("{{#Android#android.min_sdk_version}}.type", "TextField"); + set("{{#Android#android.min_sdk_version}}.description", "The least SDK required to run this app, the default value changes based on functionality but can be as low as 7. This corresponds to the XML attribute `android:minSdkVersion`."); + set("{{#Android#android.multidex}}.label", "Multidex"); + set("{{#Android#android.multidex}}.type", "Checkbox"); + set("{{#Android#android.multidex}}.description", "Boolean true/false defaults to false. Multidex allows Android binaries to reference more than 65536 methods. This slows builds a bit so you have it off by default but if you get a build error mentioning this limit you should turn this on."); + set("{{#Android#android.newFirebaseMessaging}}.label", "New firebase messaging"); + set("{{#Android#android.newFirebaseMessaging}}.type", "Checkbox"); + set("{{#Android#android.newFirebaseMessaging}}.description", "Uses the current Firebase Cloud Messaging integration. Requires AndroidX and Gradle 8.13 or newer."); + set("{{#Android#android.proguardKeep}}.label", "Proguard keep"); + set("{{#Android#android.proguardKeep}}.type", "TextArea"); + set("{{#Android#android.proguardKeep}}.description", "Arguments for the keep option in proguard allowing you to keep a pattern of files for example, `-keep class com.mypackage.ProblemClass { *; }`"); + set("{{#Android#android.release}}.label", "Release"); + set("{{#Android#android.release}}.type", "Checkbox"); + set("{{#Android#android.release}}.description", "true/false defaults to true - indicates whether to include the release version in the build"); + set("{{#Android#android.repositories}}.label", "Repositories"); + set("{{#Android#android.repositories}}.type", "TextArea"); + set("{{#Android#android.repositories}}.description", "Extra Gradle repositories to resolve dependencies from."); + set("{{#Android#android.targetSDKVersion}}.label", "Target sDKVersion"); + set("{{#Android#android.targetSDKVersion}}.type", "TextField"); + set("{{#Android#android.targetSDKVersion}}.description", "Indicates the Android SDK used to compile the Android build defaults to 21. Notice that not all targets will work since the source might have some limitations and not all SDK targets are installed on the build servers."); + set("{{#Android#and.themeMode}}.label", "Theme mode"); + set("{{#Android#and.themeMode}}.type", "Select"); + set("{{#Android#and.themeMode}}.values", "auto,modern,hololight,legacy"); + set("{{#Android#and.themeMode}}.description", "`auto`, `modern` / `material`, `hololight` (default for existing apps), `legacy`. `auto` and `modern` / `material` opt in to the CSS-generated Android Material 3 theme from `native-themes/android-material/theme.css`. `hololight` is Android Holo Light (what the framework shipped on API 14+ before this refactor). `legacy` loads the pre-Holo Android theme. The legacy alias `cn1.androidTheme` is still accepted, and `and.hololight=true` still maps to `hololight`. The default stays on `hololight` for existing apps until you flip in a future release."); + set("{{#Android#android.topDependency}}.label", "Top dependency"); + set("{{#Android#android.topDependency}}.type", "TextArea"); + set("{{#Android#android.topDependency}}.description", "Statements added to the top-level Gradle build file rather than the app module."); + set("{{#Android#android.useAndroidX}}.label", "Use android x"); + set("{{#Android#android.useAndroidX}}.type", "Checkbox"); + set("{{#Android#android.useAndroidX}}.description", "Use Android X instead of support libraries. This will also run a find/replace on all source files to replace support libraries and artifacts with AndroidX equivalents."); + set("{{#Android#android.xapplication}}.label", "Xapplication"); + set("{{#Android#android.xapplication}}.type", "TextArea"); + set("{{#Android#android.xapplication}}.description", "defaults to an empty string. Allows developers of native Android code to add text within the application block to define things such as widgets, services etc."); + set("{{#Android#android.xgradle}}.label", "Xgradle"); + set("{{#Android#android.xgradle}}.type", "TextArea"); + set("{{#Android#android.xgradle}}.description", "Arbitrary text spliced into the generated app-module Gradle file."); + set("{{#Android#android.xpermissions}}.label", "Xpermissions"); + set("{{#Android#android.xpermissions}}.type", "TextArea"); + set("{{#Android#android.xpermissions}}.description", "more permissions for the Android manifest"); + + set("{{@Desktop}}.label", "Desktop"); + set("{{#Desktop#desktop.adaptToRetina}}.label", "Adapt to retina"); + set("{{#Desktop#desktop.adaptToRetina}}.type", "Checkbox"); + set("{{#Desktop#desktop.adaptToRetina}}.description", "Boolean true/false defaults to true. When set to true some values will ve implicitly doubled to deal with retina displays and icons etc. Will use higher DPI's"); + set("{{#Desktop#desktop.fullscreen}}.label", "Fullscreen"); + set("{{#Desktop#desktop.fullscreen}}.type", "Checkbox"); + set("{{#Desktop#desktop.fullscreen}}.description", "Starts the desktop build in full-screen mode."); + set("{{#Desktop#desktop.height}}.label", "Height"); + set("{{#Desktop#desktop.height}}.type", "TextField"); + set("{{#Desktop#desktop.height}}.description", "Height in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 600."); + set("{{#Desktop#desktop.interactiveScrollbars}}.label", "Interactive scrollbars"); + set("{{#Desktop#desktop.interactiveScrollbars}}.type", "Checkbox"); + set("{{#Desktop#desktop.interactiveScrollbars}}.description", "Enables grab-able, click-to-page desktop scrollbars."); + set("{{#Desktop#desktop.resizable}}.label", "Resizable"); + set("{{#Desktop#desktop.resizable}}.type", "Checkbox"); + set("{{#Desktop#desktop.resizable}}.description", "Boolean true/false defaults to true. Indicates whether the UI in the desktop build is resizable"); + set("{{#Desktop#desktop.titleBar}}.label", "Title bar"); + set("{{#Desktop#desktop.titleBar}}.type", "Select"); + set("{{#Desktop#desktop.titleBar}}.values", "native,custom,toolbar"); + set("{{#Desktop#desktop.titleBar}}.description", "How the desktop window is framed: native for the OS title bar and menu bar, custom for an undecorated window with a Codename One drawn title bar, or toolbar for the legacy in-app Toolbar. An unrecognized value falls back to native with a warning."); + set("{{#Desktop#desktop.width}}.label", "Width"); + set("{{#Desktop#desktop.width}}.type", "TextField"); + set("{{#Desktop#desktop.width}}.description", "Width in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 800."); + + set("{{@OnDeviceDebug}}.label", "On-Device Debugging"); + set("{{#OnDeviceDebug#android.onDeviceDebug}}.label", "Android"); + set("{{#OnDeviceDebug#android.onDeviceDebug}}.type", "Checkbox"); + set("{{#OnDeviceDebug#android.onDeviceDebug}}.description", "Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable=\"true\"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable=\"true\"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it — release builds are unaffected. See the On-Device Debugging (Android) chapter for the full flow."); + set("{{#OnDeviceDebug#ios.onDeviceDebug}}.label", "Ios"); + set("{{#OnDeviceDebug#ios.onDeviceDebug}}.type", "Checkbox"); + set("{{#OnDeviceDebug#ios.onDeviceDebug}}.description", "Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits source-line and locals metadata so a desktop proxy can serve the running app to any JDWP-speaking debugger. Has no effect on release builds. See the On-Device Debugging (iOS) chapter for the full flow."); + set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyHost}}.label", "Ios proxy host"); + set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyHost}}.type", "TextField"); + set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyHost}}.description", "Hostname or IP address the device-side listener dials to reach the desktop proxy. Default `127.0.0.1` (correct for the native iOS simulator). For a physical device, set this to the developer laptop's LAN IP. Has no effect unless `ios.onDeviceDebug=true`."); + set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyPort}}.label", "Ios proxy port"); + set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyPort}}.type", "TextField"); + set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyPort}}.description", "TCP port on `ios.onDeviceDebug.proxyHost` where the proxy is listening for the device. Default `55333`. Has no effect unless `ios.onDeviceDebug=true`."); + set("{{#OnDeviceDebug#ios.onDeviceDebug.waitForAttach}}.label", "Ios wait for attach"); + set("{{#OnDeviceDebug#ios.onDeviceDebug.waitForAttach}}.type", "Checkbox"); + set("{{#OnDeviceDebug#ios.onDeviceDebug.waitForAttach}}.description", "Boolean true/false defaults to false. When `true`, the app blocks at startup until the proxy connects and the IDE tells the VM to continue. Useful when the breakpoint to investigate fires during app boot. Has no effect unless `ios.onDeviceDebug=true`."); + + set("{{@Build}}.label", "General"); + set("{{#Build#facebook.appId}}.label", "Facebook app id"); + set("{{#Build#facebook.appId}}.type", "TextField"); + set("{{#Build#facebook.appId}}.description", "The application ID for an app that requires native Facebook login integration, this defaults to null which means native Facebook support shouldn't be in the app"); + set("{{#Build#gcm.sender_id}}.label", "Gcm sender id"); + set("{{#Build#gcm.sender_id}}.type", "TextField"); + set("{{#Build#gcm.sender_id}}.description", "The Android/chrome push identifier, see the push section for more details"); + set("{{#Build#nativeTheme}}.label", "Native theme"); + set("{{#Build#nativeTheme}}.type", "Select"); + set("{{#Build#nativeTheme}}.values", "modern,legacy,custom"); + set("{{#Build#nativeTheme}}.description", "`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both `ios.themeMode` and `and.themeMode` together when those aren't set explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the framework native theme entirely. The legacy alias `cn1.nativeTheme` is still accepted."); + set("{{#Build#noExtraResources}}.label", "No extra resources"); + set("{{#Build#noExtraResources}}.type", "Checkbox"); + set("{{#Build#noExtraResources}}.description", "true/false (defaults to false). Blocks codename one from injecting its own resources when set to true, the only effect this has is in slightly reducing archive size. This might have adverse effects on some features of Codename One so it isn't recommended."); + + set("{{@Hardening}}.label", "App Hardening"); + set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.label", "Allow unhardened local build"); + set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.type", "Checkbox"); + set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.description", "Permits a local or source build to run with hardening requested but not applied. Without it such a build is refused, so a hardened app is never shipped from a target that cannot actually harden it."); + set("{{#Hardening#harden.controlFlow}}.label", "Control flow"); + set("{{#Hardening#harden.controlFlow}}.type", "Select"); + set("{{#Hardening#harden.controlFlow}}.values", "off,on"); + set("{{#Hardening#harden.controlFlow}}.description", "Overrides control-flow obfuscation independently of harden.level."); + set("{{#Hardening#harden.keep}}.label", "Keep"); + set("{{#Hardening#harden.keep}}.type", "TextArea"); + set("{{#Hardening#harden.keep}}.description", "Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at runtime and so cannot be found by the automatic analysis. Same syntax as android.proguardKeep, so existing rules port directly. Rules are separated by newlines only, because a semicolon is legal inside a rule body such as { *; }."); + set("{{#Hardening#harden.level}}.label", "Level"); + set("{{#Hardening#harden.level}}.type", "Select"); + set("{{#Hardening#harden.level}}.values", "off,standard,aggressive,paranoid"); + set("{{#Hardening#harden.level}}.description", "Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized value fails the build rather than being quietly treated as off."); + set("{{#Hardening#harden.rename}}.label", "Rename"); + set("{{#Hardening#harden.rename}}.type", "Checkbox"); + set("{{#Hardening#harden.rename}}.description", "Overrides symbol renaming independently of harden.level."); + set("{{#Hardening#harden.strings}}.label", "Strings"); + set("{{#Hardening#harden.strings}}.type", "Select"); + set("{{#Hardening#harden.strings}}.values", "off,constants,all"); + set("{{#Hardening#harden.strings}}.description", "Overrides string obfuscation independently of harden.level: off, constants or all."); + } + + /** Idempotent setter: does not overwrite user or project-level metadata. */ + private static void set(String suffix, String value) { + String key = "codename1.arg." + suffix; + if (System.getProperty(key) == null) { + System.setProperty(key, value); + } + } +} diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java index 584e2dd75ae..1f709f35387 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java @@ -235,6 +235,12 @@ static void register() { + "category, android.software.leanback uses-feature, touchscreen " + "required=false) and a generated tv_banner drawable. With the " + "hint off the manifest is unchanged."); + + // Everything else that has a build hint annotation, generated from the + // catalog. Registered last on purpose: set() never overwrites, so the + // hand-written labels and descriptions above win and this only fills in + // the hints nobody has written prose for. + BuildHintCatalogDefaults.register(); } /** Idempotent setter: does not overwrite user / project-level hint metadata. */ diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index c89f10b3775..5fa3c829dba 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -160,6 +160,7 @@ public static void main(final String[] argv) throws Exception { files.add(commonClasses.getAbsoluteFile()); } loadSimulatorProperties(cn1Props.getParentFile()); + publishAnnotationBuildHints(cn1Props.getParentFile()); } if (isDebug && usingHotswapAgent) { HotswapProperties hotswapProperties = new HotswapProperties(); @@ -451,4 +452,69 @@ private List getExtraClasses() { } } + + /** + * Publishes build hints declared as annotations so the simulator sees them. + * + *

The simulator never runs {@code cn1:build}, so it never sees the build + * request the annotations feed. Without this, moving a hint like + * {@code desktop.titleBar} or {@code nativeTheme} out of + * {@code codenameone_settings.properties} and onto the main class would + * silently stop it working under {@code cn1:run} -- the build would still be + * right and only the simulator would be wrong, which is the hardest kind of + * discrepancy to track down.

+ * + *

Published as system properties rather than added as another source to + * {@code JavaSEPort.buildHint} because several readers bypass that method + * and call {@code System.getProperty("codename1.arg....")} directly. Setting + * the property fixes those, and every future one, with no change to them.

+ * + *

An existing value always wins, which is what preserves {@code -D}: the + * JVM has already applied the command line by the time this runs.

+ * + *

Read straight off disk, not through {@code getResourceAsStream}: at this + * point in {@code main} the application classes are not on any classloader + * yet -- the loader is built from {@code files} further down.

+ */ + private static void publishAnnotationBuildHints(File projectDir) { + if (projectDir == null) { + return; + } + File f = new File(projectDir, "target" + File.separator + "classes" + + File.separator + "META-INF" + File.separator + "codenameone" + + File.separator + "build-hints.properties"); + if (!f.isFile()) { + return; + } + java.util.Properties p = new java.util.Properties(); + FileInputStream in = null; + try { + in = new FileInputStream(f); + p.load(in); + } catch (IOException ex) { + System.err.println("Warning: could not read " + f + ": " + ex.getMessage()); + return; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + // read-only stream; nothing useful to do + } + } + } + int applied = 0; + for (String key : p.stringPropertyNames()) { + if (!key.startsWith("codename1.arg.")) { + continue; + } + if (System.getProperty(key) == null) { + System.setProperty(key, p.getProperty(key)); + applied++; + } + } + if (applied > 0) { + System.out.println("Applied " + applied + " build hint(s) from annotations"); + } + } } diff --git a/docs/demos/common/codenameone_settings.properties b/docs/demos/common/codenameone_settings.properties index 8494fded9dd..f096134cbe8 100644 --- a/docs/demos/common/codenameone_settings.properties +++ b/docs/demos/common/codenameone_settings.properties @@ -1,7 +1,6 @@ codename1.android.keystore= codename1.android.keystoreAlias= codename1.android.keystorePassword= -codename1.arg.ios.newStorageLocation=true codename1.arg.java.version=17 codename1.displayName=DemoCode codename1.icon=icon.png diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java index 759bde784ab..6ed5a799391 100644 --- a/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java @@ -1,10 +1,12 @@ package com.codenameone.developerguide; import com.codename1.system.Lifecycle; +import com.codename1.annotations.buildhints.*; /** * Application entry point that launches the demo browser. */ +@Ios(newStorageLocation = true) public class DemoCode extends Lifecycle { @Override public void runApp() { diff --git a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc index 4509a700c8a..fb3dd6f1703 100644 --- a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc +++ b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc @@ -25,715 +25,19 @@ Application code can read a custom build argument through `Display.getProperty() include::../demos/common/src/main/java/com/codenameone/developerguide/advancedtopics/AppArgSnippet.java[tag=appArg,indent=0] ---- -Here is the current list of supported arguments. Build hints change over time, so consult the discussion forum if you don't find what you need here: +Here is the current list of supported arguments, generated from the same catalog +the builders and the annotations are generated from: -.Build hints -|=== -|Name |Description - -|build.cn1Version -|Pro/Enterprise only. Pins the cloud build to a specific released Codename One version using the Maven release scheme (for example `7.0.182`), or to `master` to build against the current development head. The build server fetches that version's framework artifacts. Pro accounts can target versions published within the last two months; Enterprise within the last six months. Requesting an older version, a version that was never published, or using this hint without a Pro/Enterprise subscription fails the build with an explanatory error. See <>. - -|android.debug -|true/false defaults to true - indicates whether to include the debug version in the build - -|android.release -|true/false defaults to true - indicates whether to include the release version in the build - -|android.onDeviceDebug -|Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable="true"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable="true"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it — release builds are unaffected. See the <> for the full flow. - -|android.installLocation -|Maps to android:installLocation manifest entry defaults to auto. Can also be set to internalOnly or preferExternal. - -|android.xapplication -|defaults to an empty string. Allows developers of native Android code to add text within the application block to define things such as widgets, services etc. - -|android.permission.PERMISSION_NAME -|true/false Whether to include a particular permission. Use of these build hints is preferred to `android.xpermissions` since they avoid possible conflicts with libraries. See https://developer.android.com/reference/android/Manifest.permission.html[Android's `Manifest.permission` docs] for a full list of permissions. - -|android.permission.PERMISSION_NAME.maxSdkVersion -|Will be translated to the `maxSdkVersion` attribute of the `` tag for the corresponding `android.permission.PERMISSION_NAME` build hint. (Optional) - -|android.permission.PERMISSION_NAME.required -|true/false Will be translated to the `required` attribute of the `` tag for the corresponding `android.permission.PERMISSION_NAME` build hint. (Optional) - -|android.xpermissions -|more permissions for the Android manifest - -|android.xintent_filter -|Allows adding an intent filter to the main android activity - -|android.tv -|true/false (defaults to false). Marks the build as an Android TV / Google TV app. Adds the `LEANBACK_LAUNCHER` intent category to the launcher activity (so the app appears on the TV home screen), declares the `android.software.leanback` feature, makes `android.hardware.touchscreen` optional (so it installs on touchless TVs), and generates a 320×180 launcher banner (`@drawable/tv_banner`) from the app icon. The same APK still installs and runs on phones and tablets, and `CN.isTV()` returns true at runtime on a TV. - - -|android.activity.launchMode -|Allows explicitly setting the `android:launchMode` attribute of the main activity in android. Default is "singleTop," but for some applications you may need to change this behaviour. In particular, apps that are meant to open a file type will need to set this to "singleTask." See https://developer.android.com/guide/topics/manifest/activity-element.html[Android docs for the activity element] for more information about the `android:launchMode` attribute. - - -|android.licenseKey -|The license key for the Android app, this is required if you use in-app purchase on Android - -|android.signingV1 -|true/false Default true. See https://source.android.com/docs/security/features/apksigning - -|android.signingV2 -|true/false Default true. See https://source.android.com/docs/security/features/apksigning - -|android.signingV3 -|true/false Default true. See https://source.android.com/docs/security/features/apksigning - -|android.signingV4 -|true/false Default true. See https://source.android.com/docs/security/features/apksigning - -|android.stack_size -|Size in bytes for the Android stack thread - -|android.statusbar_hidden -|true/false defaults to false. When set to true hides the status bar on Android devices. - -|android.facebook_permissions -|Permissions for Facebook used in the Android build target, applicable only if Facebook native integration is used. - -|android.googleAdUnitId -|Allows integrating admob/google play ads, this is effectively identical to google.adUnitId but only applies to Android - -|android.googleAdUnitTestDevice -|Device key used to mark a specific Android device as a test device for Google Play ads defaults to C6783E2486F0931D9D09FABC65094FDF - -|android.includeGPlayServices -|*Deprecated, please android.playService.+++*+++!* Indicates whether Google Play Services should be included into the build, defaults to false but that might change based on the functionality of the application and other build hints. Adding Google Play Services support allows you to use a more refined location implementation and invoke some Google specific functionality from native code. - -|android.playService.plus, android.playService.auth, android.playService.base, android.playService.identity, android.playService.indexing, android.playService.appInvite, android.playService.analytics, android.playService.cast, android.playService.gcm, android.playService.drive, android.playService.fitness, android.playService.location, android.playService.maps, android.playService.ads, android.playService.vision, android.playService.nearby, android.playService.panorama, android.playService.games, android.playService.safetynet, android.playService.wallet, android.playService.wearable -|Allows including only a specific play services library portion. Notice that this setting conflicts with the deprecated `android.includeGPlayServices` and only works with the Gradle-based Android build pipeline. + - -If none of the services are defined to true then plus, auth, base, analytics, gcm, location, maps & ads will be set to true. If one or more of the `android.playService` entries are defined to something then all entries will default to false. - -|android.playServicesVersion -| The version number of play services to build against. Experimental. **Use with caution** as building against versions other than the server default may introduce incompatibilities with some Codename One APIs. - -|xxx.minPlayServicesVersion -|This is a special case build hint. You can use any prefix to the build hint and the convention is to use your cn1lib name. It's identical to `android.minPlayServicesVersion` with the exception that the "highest version wins." That way if your cn1lib requires play services 9+ and uses: `myLib.minPlayServicesVersion=9.0.0` and another library has `otherLib.minPlayServicesVersion=10.0.0` then play services will be 10.0.0 - -|android.multidex -|Boolean true/false defaults to false. Multidex allows Android binaries to reference more than 65536 methods. This slows builds a bit so you have it off by default but if you get a build error mentioning this limit you should turn this on. - -|android.headphoneCallback -|Boolean true/false defaults to false. When set to true it assumes the main class has two methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately as needed - -|android.gpsPermission -|Indicates whether the GPS permission should be requested, it's autodetected by default if you use the location API. But, some code might want to explicitly define it - -|android.asyncPaint -|Boolean true/false defaults to true. Toggles the Android pipeline between the legacy pipeline (false) and new pipeline (true) - -|android.stringsXml -|Allows injecting more entries into the strings.xml file using a value that includes something like this `value1value2` - -|android.supportV4 -|Boolean true/false defaults to false but that can change based on usage (for example, push implicitly activates this). Indicates whether the android support v4 library should be included in the build - -|android.style -|Allows injecting more data into the `styles.xml` file right before the closing resources tag - -|android.enableAdaptiveIcons -|Boolean true/false defaults to false. Enables Android adaptive icon generation in Android Gradle builds. When enabled, Codename One generates `mipmap` launcher resources (`ic_launcher`, `ic_launcher_foreground`, and adaptive XML in `mipmap-anydpi-v26`) and uses them in the application manifest (`android:icon` and `android:roundIcon`). - -|android.adaptiveIconBackground -|Background color to use for adaptive icons when `android.enableAdaptiveIcons=true` and no background image is supplied. Defaults to `#ffffff` and is written as `@color/ic_launcher_background`. - -|android.adaptiveIconBackgroundImage -|Optional path (relative to the root of the native Android project) to an image file to use as the adaptive icon background when `android.enableAdaptiveIcons=true`. If this property is set, it overrides `android.adaptiveIconBackground`. - -|android.cusom_layout1 -|Applies to any number of layouts as long as they're in sequence (for example, android.cusom_layout2, android.cusom_layout3 etc.). Will write the content of the argument as a layout XML file and give it the name `cusom_layout1.xml` onwards. This can be used by native code to work with XML files - -|android.keyboardOpen -|Boolean true/false defaults to true. Toggles the new async keyboard mode that leaves the keyboard open while you move between text components - -|android.versionCode -|Allows overriding the auto generated version number with a custom internal version number specifically used for the XML attribute `android:versionCode` - -|android.captureRecord -|Indicates whether the `RECORD_AUDIO` permission should be requested. Can be `enabled` or any other value to disable this option - -|android.nonconsumable -|Comma delimited string of items that are non-consumable in the in-app purchase API - -|android.removeBasePermissions -|Boolean true/false defaults to false. Disables the built-in permissions specifically `INTERNET` permission (that is, no networking...) - -|android.blockExternalStoragePermission -|Boolean true/false defaults to false. Disables the external storage (SD card) permission - -|android.blockReadMediaPermissions -|Boolean true/false, defaults to the value of `android.blockExternalStoragePermission`. Suppresses the `READ_MEDIA_VIDEO` and `READ_MEDIA_AUDIO` permissions that playing a URI adds on API 33 and above - -|android.requestReadMediaPermissions -|Boolean true/false defaults to false. Declares `READ_MEDIA_IMAGES`, `READ_MEDIA_VIDEO` and `READ_MEDIA_AUDIO` on API 33 and above even when the build detected no media playback. `READ_MEDIA_IMAGES` is only ever added by this hint - -|android.min_sdk_version -|The least SDK required to run this app, the default value changes based on functionality but can be as low as 7. This corresponds to the XML attribute `android:minSdkVersion`. - -|android.manifest.queries -|Embeds XML content into the section of the Android manifest file. This is https://developer.android.com/training/package-visibility[required in Android 11 for package visibility]. See https://developer.android.com/guide/topics/manifest/queries-element[queries element Android documentation]. - -|android.mockLocation -|Boolean true/false defaults to true. Toggles the mock location permission which is on by default, this allows easier debugging of Android device location based services - -|android.smallScreens -|Boolean true/false defaults to true. Corresponds to the `android:smallScreens` XML attribute and allows disabling the support for small phones - -|android.xapplication_attr -|Allows injecting more attributes into the `application`` tag in the Android XML - -|android.xactivity -|Allows injecting more attributes into the `activity` tag in the Android XML - -|android.streamMode -|The mode in which the volume key should behave, defaults to OS default. Allows setting it to `music` for music playback apps - -|android.pushVibratePattern -|Comma delimited long values to describe the push pattern of vibrate used for the `setVibrate` native method - -|android.enableProguard -|Boolean true/false defaults to true. Allows disabling the proguard obfuscation even on release builds, notice that this isn't recommended - -|android.proguardKeep -|Arguments for the keep option in proguard allowing you to keep a pattern of files for example, `-keep class com.mypackage.ProblemClass { *; }` - -|android.shrinkResources -|Boolean true/false defaults to false. Used only in conjunction with android.enableProguard. Strips out unused resources to reduce apk size. Since 7.0 - -|android.sharedUserId -|Allows adding a manifest attribute for the sharedUserId option - -|android.sharedUserLabel -|Allows adding a manifest attribute for the sharedUserLabel option - -|android.targetSDKVersion -|Indicates the Android SDK used to compile the Android build defaults to 21. Notice that not all targets will work since the source might have some limitations and not all SDK targets are installed on the build servers. - -|android.useAndroidX -|Use Android X instead of support libraries. This will also run a find/replace on all source files to replace support libraries and artifacts with AndroidX equivalents. - -|android.rootCheck -|Boolean true/false defaults to false. Indicates whether the app should check for root access on the device. If root access is detected, the app will exit. - -|android.tapjackingGuard -|Boolean true/false defaults to false. Switches on tapjacking / screen-overlay protection at launch, so touches that arrive while another app's window covers this one are detected and dropped. See the security chapter. - -|android.tapjackingGuard.mode -|`block` (default), `strict`, `report` or `off`. `block` drops gestures that start on a fully obscured window, `report` only observes, `strict` also drops touches where only part of the window is covered (which benign system UI can trigger). Only relevant if `android.tapjackingGuard=true`. - -|android.tapjackingGuard.hideOverlays -|Boolean true/false defaults to true. Also asks Android 12+ to hide overlay windows drawn over the app, which is the only mitigation that covers native peer components, and declares the `HIDE_OVERLAY_WINDOWS` permission it requires. Only relevant if `android.tapjackingGuard=true`. - -|android.hideOverlayWindows -|Boolean true/false defaults to false. Declares the `android.permission.HIDE_OVERLAY_WINDOWS` permission needed by `DeviceIntegrity.setHideOverlayWindows()` on Android 12+, for apps that call the runtime API without enabling `android.tapjackingGuard`. A normal install-time permission, so the user sees no prompt. - -|android.fridaDetection -|Boolean true/false defaults to false. Indicates whether the app should check for the presence of the https://www.frida.re/[Frida] dynamic instrumentation toolkit on the device. If Frida is detected, the app will exit. This uses the [frida-blocker](https://github.com/shannah/frida-blocker) library to perform the frida detection. - -|android.fridaVersion -|x.y.z The version of [frida-blocker](https://github.com/shannah/frida-blocker) to use to perform frida detection. This is only relevant if `android.fridaDetection=true`. If omitted, it will use the latest tested version in the build server. - -|android.fridaDebugLogging -|Boolean true/false defaults to false. If true, it will add verbose debug logs during frida detection to show which check if fails on. - -|android.theme -|Light or Dark defaults to Light. On Android 4+ the default Holo theme is used to render the native widgets sometimes and this indicates whether holo light or holo dark is used. This doesn't affect the Codename One theme but that might change in the future. - -|android.web_loading_hidden -|true/false defaults to false - set to true to hide the progress indicator that appears when loading a web page on Android. - -|block_server_registration -|true/false flag defaults to false. By default Codename One applications register with the Codename One server. Setting this to true blocks them from sending information to the Codename One cloud, which is kept for statistical purposes and may be used to provide more installation stats in the future. - -|facebook.appId -|The application ID for an app that requires native Facebook login integration, this defaults to null which means native Facebook support shouldn't be in the app - -|facebook.clientToken -|The client token for an app that requires native Facebook login integration, this is required if the facebook.appId is set. - -|gcm.sender_id -|The Android/chrome push identifier, see the push section for more details - -| android.background_push_handling -| Deliver push messages on Android when the app is minimized by setting this to "true." Default behaviour is to deliver the message only if the app is in the foreground when received, or after the user taps on the notification to open the app, if the app was in the background when the message was received. - -| desktop.mac.plist.PLISTKEY -| Set the key `PLISTKEY` in the Info.plist file for desktop mac build. For example, `desktop.mac.plist.LSApplicationCategoryType=public.app-category.business`. See https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html[Apple Documentation of Info.plist keys and values for a full list of supported keys]. -+ -Only supported for App Store builds. See https://www.codenameone.com/developer-guide.html#_mac_os_desktop_build_options[macOS Desktop Build Options] for more information. - -| desktop.mac.plistInject -| Injects raw XML into the Info.plist file for desktop builds. For example, `desktop.mac.plistInject=LSApplicationCategoryTypepublic.app-category.business` -+ -Only supported for App Store builds. See https://www.codenameone.com/developer-guide.html#_mac_os_desktop_build_options[macOS Desktop Build Options] for more information. - -| windows.arch -| Native Windows (`windows-device`) target CPU: `x64` (default), `arm64`, or `both`. An x64 binary also runs on Windows-on-ARM via the OS's x64 emulation. See <>. - -| windows.debug -| Native Windows target: `true`/`false` (default `false`). When `false` the `.exe` is optimized and stripped (with the `.pdb` in its own file); `true` keeps symbols (a single x64 build) for crash symbolication. Optimizations stay on either way. - -| windows.signing.pkcs12 / windows.signing.password / windows.signing.timestampUrl / windows.signing.digest / windows.signing.name / windows.signing.url -| Native Windows Authenticode signing of the produced `.exe` (via `osslsigncode`). A certificate is taken from `windows.signing.pkcs12` or the build's uploaded certificate; set `windows.signing=false` to skip. Unsigned binaries run but trip SmartScreen / "Unknown publisher." - -| linux.arch -| Native Linux (`linux-device`) target CPU: `x64` (default), `arm64`, or `both`. See <>. - -| linux.debug -| Native Linux target: `true`/`false` (default `false`). When `false` the ELF is optimized and stripped (debug info is split into a separate `.debug`); `true` keeps symbols (`RelWithDebInfo`) for crash symbolication. Optimizations stay on either way. - -| linux.libc -| Native Linux target: `glibc` (default) or `musl`. The default compiles against an old glibc so the ELF runs on essentially any mainstream distro; `musl` targets Alpine (where the GTK stack is itself musl-built). A glibc binary and a musl binary aren't interchangeable. - -|ios.associatedDomains -|Comma-delimited list of domains associated with this app. Each domain should be prefixed by a supported prefix. For example, "applinks:" or "webcredentials:." See https://developer.apple.com/documentation/security/password_autofill/setting_up_an_app_s_associated_domains?language=objc[Apple's documentation on Associated domains] for more information. - -|ios.bitcode -|true/false defaults to false. Enables bitcode support for the build. - -|ios.debug.archs -|Can be set to "armv7" to force iOS debug builds to be 32 bit. By default, debug builds are 64 bit only. - -|ios.release.archs -|Can be set to "arm64" to only build iOS release builds for 64 bit. By default, release builds are both 32 and 64 bit. - -|ios.distributionMethod -|Specifies distribution type for debug iOS builds. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). - -|ios.debug.distributionMethod -|Specifies distribution type for debug iOS builds only. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). - -|ios.release.distributionMethod -|Specifies distribution type for release iOS builds only. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). - -|ios.keyboardOpen -|Flips between iOS keyboard open mode and autofold keyboard mode. Defaults to true which means the keyboard will remain open and not fold automatically when editing moves to another field. - -|ios.uiscene -|true/false (defaults to true). Enables iOS UIScene lifecycle support. UIScene lets iOS manage one or more app UI sessions independently, improving lifecycle handling in modern iOS versions. Apple has indicated UIScene will be required starting with iOS 27, so this is now on by default; set the flag to `false` only if you need to temporarily fall back to the legacy `UIApplicationDelegate` lifecycle. - -|ios.urlScheme -|Allows intercepting a URL call using the syntax `urlPrefix` - -|ios.useAVKit -|Use AVKit for video components on iOS rather than `MPMoviePlayerController` on iOS versions 8 through 12. iOS 13 will always use AVKit, and iOS 7 and lower will always use `MPMoviePlayerController`. Default value `false` - -|ios.teamId -|Specifies the team ID associated with the iOS provisioning profile and certificate. Use `ios.debug.teamId` and `ios.release.teamId` to specify different team IDs for debug and release builds respectively. - -|ios.debug.teamId -|Specifies the team ID associated with the iOS debug provisioning profile and certificate. - -|ios.release.teamId -|Specifies the team ID associated with the iOS release provisioning profile and certificate. - -|ios.project_type -|one of ios, ipad, iphone (defaults to ios). Indicates whether the resulting binary is targeted to the iphone only or ipad only. Notice that the IDE plugin has a "Project Type" combo box you *should* use under the iOS section. - -|ios.rpmalloc -// vale-skip: write-good.TooWordy — 'minimum' refers to the deployment-target floor; 'least' would change the meaning. -|`true`/`false` Use https://github.com/rampantpixels/rpmalloc[rpmalloc] instead of malloc/free for memory allocation in ParparVM. This will cause the deployment target to be changed to a minimum of iOS 8.0. - -|ios.statusbar_hidden -|true/false defaults to false. Hides the iOS status bar if set to true. - -|ios.newStorageLocation -|true/false defaults to false but defined on new projects as true by default. This changes the storage directory on iOS from using caches to using the documents directory which is the recommended location but might break compatibility. This is described in https://github.com/codenameone/CodenameOne/issues/1480[this issue] - -|ios.prerendered_icon -|true/false defaults to false. The iOS build process adapts the submitted icon for iOS conventions (adding an overlay) that might not be appropriate on some icons. Setting this to true leaves the icon unchanged (only scaled). - -|ios.app_groups -|Space-delimited list of app groups that this app belongs to as described in https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19[Apple's documentation]. These are added to the entitlements file with key `com.apple.security.application-groups`. - -|ios.keychainAccessGroup -|Space-delimited list of keychain access groups that this app has access to as described in https://developer.apple.com/library/content/documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html#//apple_ref/doc/uid/TP30000897-CH204-SW11[Apple's documentation]. These are added to the entitlements file with the key `keychain-access-groups`. - -|ios.application_exits -|true/false (defaults to false). Indicates whether the application should exit on home button press. The default is to exit, leaving the application running is only tested at the moment. - -|ios.blockScreenshotsOnEnterBackground -|true/false (defaults to false). Indicates that app should prevent iOS from taking screenshots when app enters background. Described https://shannah.github.io/cn1-recipes/#_hiding_sensitive_data_when_entering_background[here]. - -|ios.detectJailbreak -|true/false (defaults to false). When true, the iOS app will exit on launch if it detects that it's running on a jailbroken device. - -|ios.notificationPermissionAtLaunch -|true/false (defaults to false). Backward-compatibility flag for the pre-issue-#4876 behavior. By default, the iOS notification permission prompt is deferred until the app calls `Push.register()` or schedules a `LocalNotification`, matching the Android flow and giving the developer a chance to display a rationale screen first. Set this hint to `true` to restore the legacy behavior in which the prompt fires automatically inside `application:didFinishLaunchingWithOptions:` as soon as the app launches. Existing apps relying on the prompt being shown at launch should set this to `true`; new apps should leave it disabled and trigger the prompt explicitly when they're ready to ask for permission. - -|ios.applicationQueriesSchemes -|Comma separated list of url schemes that `canExecute` will respect on iOS. If the url scheme isn't mentioned here `canExecute` will return false starting with iOS 9. Notice that this collides with `ios.plistInject` when used with the `LSApplicationQueriesSchemes...` value so you should use one or the other. For example, to enable `canExecute` for a url like `myurl://xys` you can use: `myurl,myotherurl` - -|ios.themeMode -|`auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the existing iOS 7 flat theme so pre-refactor screenshot goldens and apps see no behavior change. `modern` / `liquid` opts in to the CSS-generated iOS Modern (liquid-glass) theme shipped from `native-themes/ios-modern/theme.css`. `ios7` / `flat` is the same as `auto` - pre-liquid iOS 7 flat theme; `legacy` / `iphone` loads the pre-iOS 7 iPhone theme. The `auto` -> modern flip is planned for a future release. - -|and.themeMode -|`auto`, `modern` / `material`, `hololight` (default for existing apps), `legacy`. `auto` and `modern` / `material` opt in to the CSS-generated Android Material 3 theme from `native-themes/android-material/theme.css`. `hololight` is Android Holo Light (what the framework shipped on API 14+ before this refactor). `legacy` loads the pre-Holo Android theme. The legacy alias `cn1.androidTheme` is still accepted, and `and.hololight=true` still maps to `hololight`. The default stays on `hololight` for existing apps until you flip in a future release. - -|nativeTheme -|`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both `ios.themeMode` and `and.themeMode` together when those aren't set explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the framework native theme entirely. The legacy alias `cn1.nativeTheme` is still accepted. +Most of the commonly used hints also have a compiler-checked form: an annotation +in `com.codename1.annotations.buildhints` that you put on the application's main +class. Written that way a misspelled name is an unknown symbol and an +unsupported value is an unknown enum constant, instead of a properties line that +is accepted, never read, and silently does nothing. The Annotation column below +names that form where one exists. Setting the same hint both ways fails the +build. -|ios.interface_orientation -|UIInterfaceOrientationPortrait by default. Indicates the orientation, one or more of (separated by colon :): `UIInterfaceOrientationPortrait`, `UIInterfaceOrientationPortraitUpsideDown`, `UIInterfaceOrientationLandscapeLeft`, `UIInterfaceOrientationLandscapeRight`. Notice that the IDE plugin has an "Interface Orientation" combo box you *should* use under the iOS section. - -|ios.xcode_version -|The version of Xcode used on the server. Defaults to 4.5; accepts 5.0 as an option and nothing else. - -|ios.multitasking -|Set to true to enable iOS multitasking and split-screen support. This only works if `ios.xcode_verson=9.2`. - -|java.version -|Valid values include 5 or 8. Indicates the JVM version that should be used for server compilation, this is defined by default for newly created apps based on the Java 8 mode selection - -|javascript.inject_proxy -|true/false (defaults to `true`). The ParparVM builder generates a same-origin proxy bundle and configures the app to use it. Setting this to `false` disables both proxy generation and proxy URL injection. - -|javascript.inject.beforeHead -| Content to be injected into the index.html file at the beginning of the `` tag. - -|javascript.inject.afterHead -| Content to be injected into the index.html file at the end of the `` tag. - -|javascript.minifying -|true/false (defaults to `true`). By default the JavaScript code is minified to reduce file size. You may optionally disable minification by setting `javascript.minifying` to `false`. - -|javascript.port -|`parparvm` (default) or `teavm`. Selects the public JavaScript compiler for cloud builds. `teavm` retains the original builder as a compatibility fallback. - -|javascript.proxy.allowedTargets -|Comma-separated target origins, host names, or wildcard subdomains that a generated proxy may access, for example `https://api.example.com,*.services.example.org`. If omitted, the proxy accepts any HTTP or HTTPS target and the build emits a warning. - -|javascript.proxy.target -|The generated ParparVM proxy deployment platform. Supported values are `jakarta-servlet` (default), `javax-servlet`, `node`, `php`, `aws-lambda`, `google-cloud-functions`, `cloudflare-workers`, and `none`. - -|javascript.proxy.url -|The URL of an existing proxy to use for network requests. Setting it suppresses generated proxy packaging unless `javascript.proxy.target` is also set. If `javascript.inject_proxy` is `false`, this build hint is ignored. - -|javascript.sourceFilesCopied -|true/false (defaults to `false`). Setting this flag to `true` will cause available java source files to be included in the resulting .zip and .war files. These may be used by Chrome during debugging. - -|javascript.stopOnErrors -|true/false (defaults to `true`). Causes a TeaVM JavaScript build to fail when the compiler reports warnings. Setting this to `false` may allow the fallback builder to complete, but can turn compiler diagnostics into runtime failures that are more difficult to debug. - -|javascript.teavm.version -| (Optional) The version of TeaVM to use for the build. *Use caution*, only use this property if you know what you're doing! - - -|google.adUnitId -|Allows integrating Admob/Google Play ads into the application see link:https://www.codenameone.com/blog/adding-google-play-ads.html[this] - -|ios.entitlementsInject -|Content to inject into the iOS entitlements file. This should be in the Plist XML format. See https://developer.apple.com/documentation/bundleresources/entitlements?language=objc[Apple Entitlements Documentation]. - -|ios.plistInject -|entries to inject into the iOS plist file during build. - -|ios.includePush -|true/false (defaults to false). Whether to include the push capabilities in the iOS build. Notice that the IDE plugin has an "Include Push" check box you *should* use under the iOS section. - -|ios.newPipeline -|Boolean true/false defaults to true. Allows toggling the OpenGL ES 2.0 drawing pipeline off to the older OGL ES 1.0 pipeline. - -|ios.headphoneCallback -|Boolean true/false defaults to false. When set to true it assumes the main class has two methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately as needed - -|ios.facebook_permissions -|Permissions for Facebook used in the Android build target, applicable only if Facebook native integration is used. - -|ios.applicationDidEnterBackground -|Objective-C code that can be injected into the iOS callback method (message) `applicationDidEnterBackground`. - -|ios.enableAutoplayVideo -|Boolean true/false defaults to false. Makes videos "autoplay" when loaded on iOS - -|ios.googleAdUnitId -|Allows integrating admob/google play ads, this is effectively identical to google.adUnitId but only applies to iOS - -|ios.viewDidLoad -|Objective-C code that can be injected into the iOS callback method (message) `viewDidLoad` - -|ios.googleAdUnitIdPadding -|Indicates the amount of padding to pass to the Google Ads placed at the bottom of the screen with `google.adUnitId` - -|ios.enableBadgeClear -|Boolean true/false defaults to true. Clears the badge value with every load of the app, this is useful if the app doesn't manually keep track of number values for the badge - -|ios.glAppDelegateHeader -|Objective-C code that can be injected into the iOS app delegate at the top of the file. For example, if you need to include headers or make special imports for other injected code - -|ios.glAppDelegateBody -|Objective-C code that can be injected into the iOS app delegate within the body of the file before the end. This only makes sence for methods that aren't already declared in the class - -|ios.beforeFinishLaunching -|Objective-C code that can be injected into the iOS app delegate at the top of the body of the didFinishLaunchingWithOptions callback method - -|ios.afterFinishLaunching -|Objective-C code that can be injected into the iOS app delegate at the bottom of the body of the didFinishLaunchingWithOptions callback method - -|ios.locationUsageDescription -|This flag is required for iOS 8 and newer if you're using the location API. It needs to include a description of the reason for which you need access to the users location - -|ios.NSXXXUsageDescription -|iOS privacy flags for using certain APIs. Starting with Xcode 8, you're required to add usage description strings for certain APIs. Find a full list of the available keys in https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html[Apple's docs]. Some relevant ones include `ios.NSCameraUsageDescription`, `ios.NSContactsUsageDescription`, `ios.NSLocationAlwaysUsageDescription`, `NSLocationUsageDescription`, `ios.NSMicrophoneUsageDescription`, `ios.NSPhotoLibraryAddUsageDescription`, `ios.NSSpeechRecognitionUsageDescription`, `ios.NSSiriUsageDescription` - -|ios.add_libs -|A semicolon separated list of libraries that should be linked to the app to build it - -|ios.pods -|A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be linked to the app to build it. For example, `AFNetworking ~> 2.6, ORStackView ~> 3.0, SwiftyJSON ~> 2.3` - -|ios.pods.platform -// vale-skip: write-good.TooWordy — 'minimum platform level' is the standard CocoaPods term; 'least platform level' is wrong. -| Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a minimum platform level. For example, `ios.pods.platform=7.0`. - -| ios.deployment_target -// vale-skip: write-good.TooWordy — 'minimum version' is the standard term for a deployment-target floor. -| Sets the deployment target for iOS builds. This is the minimum version of iOS required by a device to install the app. For example, `ios.deployment_target=8.0`. Default is '6.0'. Note: This build hint interacts with the `ios.rpmalloc` build hint. If `ios.deployment_target` is 8.0 or higher, ParparVM will use https://github.com/rampantpixels/rpmalloc[rpmalloc] by default. You can disable this default and revert back to using malloc/free by setting the `ios.rpmalloc=false` build hint. - -|ios.bundleVersion -|Indicates the version number of the bundle, this is useful if you want to create a minor version number change for the beta testing support - -|ios.objC -|Added the `-ObjC` compile flag to the project files which some native libraries require - -|ios.testFlight -|Boolean true/false defaults to false and works only for pro accounts. Enables the testflight support in the release binaries for easy beta testing. Notice that the IDE plugin has a "Test Flight" check box you *should* use under the iOS section. - -|ios.metal -|Boolean true/false defaults to true. Selects the Metal rendering backend (`CAMetalLayer`) over the legacy OpenGL ES 2 path (`CAEAGLLayer`). Metal is the supported iOS graphics API; OpenGL ES is deprecated. Set to `false` to opt out if you hit a Metal-only rendering regression. See link:#_metal_renderer[Working with iOS / Metal renderer] for details. - -|ios.metal.colorSpace -|Selects the `CAMetalLayer.colorspace` for the Metal renderer. Accepts `sRGB` (default), `displayP3`, `deviceRGB`, `linearSRGB`, `extendedSRGB`, `extendedLinearSRGB`, or `none`. Has no effect when `ios.metal=false`. See link:#_choosing_a_color_space_for_the_metal_renderer[Working with iOS / Choosing a color space] for the full table. - -|ios.generateSplashScreens -|Boolean true/false defaults to false. Enables legacy generation of splash screen images instead of the current launch storyboards. - -|ios.onDeviceDebug -|Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits source-line and locals metadata so a desktop proxy can serve the running app to any JDWP-speaking debugger. Has no effect on release builds. See the <> for the full flow. - -|ios.onDeviceDebug.proxyHost -|Hostname or IP address the device-side listener dials to reach the desktop proxy. Default `127.0.0.1` (correct for the native iOS simulator). For a physical device, set this to the developer laptop's LAN IP. Has no effect unless `ios.onDeviceDebug=true`. - -|ios.onDeviceDebug.proxyPort -|TCP port on `ios.onDeviceDebug.proxyHost` where the proxy is listening for the device. Default `55333`. Has no effect unless `ios.onDeviceDebug=true`. - -|ios.onDeviceDebug.waitForAttach -|Boolean true/false defaults to false. When `true`, the app blocks at startup until the proxy connects and the IDE tells the VM to continue. Useful when the breakpoint to investigate fires during app boot. Has no effect unless `ios.onDeviceDebug=true`. - -|ios.wallet.extension -|Boolean true/false defaults to false. Generates an Apple Wallet issuer provisioning extension (the "From apps on your iPhone" flow in the Wallet app) and embeds it in the build. Requires `ios.wallet.appGroup` and `ios.wallet.issuerEndpoint`. See the <>. - -|ios.wallet.appGroup -|App Group id starting with `group.` shared by the app and the generated Wallet extensions. The app publishes pass entries into this group through `com.codename1.payment.WalletExtension` and the group is added to the app and extension entitlements automatically. Required when `ios.wallet.extension=true`. - -|ios.wallet.issuerEndpoint -|HTTPS URL of the issuer backend endpoint that produces the encrypted provisioning payload. The generated extension POSTs Apple's certificates/nonce plus the card identifier and auth token there as JSON. Required when `ios.wallet.extension=true`. - -|ios.wallet.includeUI -|Boolean true/false defaults to false. Also generates the Wallet authorization UI extension - a login form shown inside the Wallet app when the app reports that authentication is required. Requires `ios.wallet.authEndpoint`. - -|ios.wallet.authEndpoint -|HTTPS URL the generated login UI extension POSTs `{"username","password"}` to; the JSON response's `token` is stored in the App Group for the provisioning request. Required when `ios.wallet.includeUI=true`. - -|ios.wallet.nonuiExtensionName / ios.wallet.uiExtensionName -|Names of the generated extension targets, also used as the bundle id suffix (`.`). Default `WalletNonUIExtension` / `WalletUIExtension`. The matching App IDs must be registered with the payment-pass-provisioning entitlement and listed by the card network. - -|ios.wallet.nonuiProvisioningProfile / ios.wallet.uiProvisioningProfile -|Cloud device builds only. File name of the extension's `.mobileprovision` placed under `common/src/main/resources`. The profile must match the app's distribution certificate and carry the `com.apple.developer.payment-pass-provisioning` entitlement; the build keeps it out of the app bundle. - -|ios.wallet.nonuiProvisioningURL / ios.wallet.uiProvisioningURL -|Cloud device builds only. URL fallback for the extension provisioning profile when it isn't bundled in resources, mirroring `ios.notificationServiceExtensionProvisioningURL`. - -|ios.wallet.nonui.buildSettings.SETTING / ios.wallet.ui.buildSettings.SETTING -|Extra Xcode build settings applied to the generated extension targets, for example `ios.wallet.nonui.buildSettings.DEVELOPMENT_TEAM=ABCD123456`. Applied last so they override the generated defaults. - -|ios.wallet.nonuiImportsInject, ios.wallet.statusInject, ios.wallet.passEntriesInject, ios.wallet.remotePassEntriesInject, ios.wallet.generateRequestInject, ios.wallet.generateResponseInject, ios.wallet.uiImportsInject, ios.wallet.uiViewDidLoadInject, ios.wallet.uiAuthRequestInject, ios.wallet.uiAuthResponseInject -|Objective-C code injected at the matching marker comment in the generated Wallet extension sources, for custom behavior at each callback (for example adding fields to the issuer endpoint payload in `generateRequestInject`). See the <>. - -|ios.appext.NAME.provisioningURL -|Cloud device builds only. URL of the provisioning profile for a generic app extension dropped into `ios/app_extensions/NAME/` (or a generated extension such as `CN1Widgets`), used when the extension folder doesn't bundle a `.mobileprovision` itself. The profile is installed on the build machine and added to the export options per bundle id. Used for both debug and release builds unless a qualified variant (below) is set. An extension is signed against its own App ID, so a device build with no profile for it -- by any of the three carriers -- is refused unless the app's own profile is a wildcard that covers the extension's bundle id. - -|ios.debug.appext.NAME.provisioningURL / ios.release.appext.NAME.provisioningURL -|Cloud device builds only. Build-type-specific variants of `ios.appext.NAME.provisioningURL`: point the `debug` variant at the extension's development profile and the `release` variant at its distribution profile. The Maven build resolves the variant matching the build target (`ios-device` and `ios-on-device-debug` are debug, `ios-device-release` is release) into the unqualified hint before submitting the build; the unqualified hint acts as the fallback. The same qualifiers work for the local-path settings `codename1.ios.debug.appext.NAME.provision` / `codename1.ios.release.appext.NAME.provision`. - -|codename1.mac.appid -|Mac Native cloud builds only. The Mac bundle identifier registered in App Store Connect / Apple Developer. Distinct from `codename1.ios.appid` because Apple treats the iOS and Mac App Store records as separate products. Required for cloud Mac builds. - -|codename1.mac.certificate -|Mac Native cloud builds only. Path to the `.p12` file containing the Mac signing certificate(s) — _Mac App Distribution_ (3rd Party Mac Developer Application) for App Store builds, _Developer ID Application_ for Developer ID builds, or both bundled into the same P12 when `macNative.distribution=both`. Not interchangeable with the iOS distribution certificate. Required for cloud Mac builds. - -|codename1.mac.certificatePassword -|Mac Native cloud builds only. Password to unlock the P12 referenced by `codename1.mac.certificate`. Required for cloud Mac builds. - -|codename1.mac.provision -|Mac Native cloud builds only. Path to the Mac provisioning profile (`.provisionprofile`). Apple issues distinct provisioning profiles for Mac App Store and Developer ID distribution — pass the one that matches the chosen channel. - -|macNative.distribution -|Mac Native builds only. `appStore` (default), `developerID`, or `both`. Selects which entitlements + ExportOptions plist + signing certificate to emit. `both` emits parallel `*-AppStore.entitlements` / `*-DeveloperID.entitlements` and matching `ExportOptions-*-Mac.plist` files so a single project can be archived to either channel. - -|macNative.teamId -|Mac Native builds only. Apple Developer Team ID (alphanumeric). Falls back to `ios.release.teamId` → `ios.teamId` → `ios.debug.teamId` since most apps share a single Apple Developer Team for iOS and Mac. - -|macNative.bundleId -|Mac Native builds only. Used only when `macNative.deriveBundleId=false`. Default: `.mac`. - -|macNative.deriveBundleId -|Mac Native builds only. `true` (default) maps to Xcode's `DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER=YES` (Xcode appends `.maccatalyst` to the iOS bundle ID). Set to `false` to take the bundle ID verbatim from `macNative.bundleId`. - -|macNative.minDeploymentTarget -|Mac Native builds only. Minimum macOS version (`MACOSX_DEPLOYMENT_TARGET`). Default `10.15` — earlier versions don't support Mac Catalyst. - -|macNative.iosMinDeploymentTarget -|Mac Native builds only. iOS deployment-target floor for the Catalyst slice (`IPHONEOS_DEPLOYMENT_TARGET`). Default `13.1`. The plugin coerces the iOS slice's minimum upward when set. - -|macNative.appCategory -|Mac Native builds only. `LSApplicationCategoryType` in the generated Info.plist. Default `public.app-category.utilities`. See https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype[Apple's category list]. - -|macNative.copyright -|Mac Native builds only. `NSHumanReadableCopyright` in the Info.plist. Defaults to `Copyright (c) `. - -|macNative.signing.style -|Mac Native builds only. `automatic` (default) lets Xcode pick the signing certificate; `manual` forces the certificate identity hints below to be respected verbatim. - -|macNative.signingIdentity.appStore -|Mac Native builds only. Signing certificate identity for the App Store channel. Default `Apple Distribution`. - -|macNative.signingIdentity.developerID -|Mac Native builds only. Signing certificate identity for the Developer ID channel. Default `Developer ID Application`. - -|macNative.provisioningProfile.appStore -|Mac Native builds only. Provisioning profile name for App Store distribution — used only when `macNative.signing.style=manual`. - -|macNative.provisioningProfile.developerID -|Mac Native builds only. Provisioning profile name for Developer ID distribution — used only when `macNative.signing.style=manual`. - -|macNative.entitlements.appSandbox -|Mac Native builds only. `true` enables `com.apple.security.app-sandbox`. Default is `true` for the `appStore` channel (Mac App Store requires the sandbox), `false` for `developerID`. - -|macNative.entitlements.network.client -|Mac Native builds only. Toggles `com.apple.security.network.client`. Default `true`. - -|macNative.entitlements.network.server -|Mac Native builds only. Toggles `com.apple.security.network.server`. Default `false`. - -|macNative.entitlements.files.userSelected -|Mac Native builds only. `readwrite` (default), `readonly`, or `none`. Sets the matching `com.apple.security.files.user-selected.*` entitlement. - -|macNative.entitlements.hardenedRuntime -|Mac Native builds only. `true` enables hardened runtime restrictions. Default is `true` for `developerID` (notarization requires it), `false` for `appStore`. - -|macNative.entitlements.allowJit -|Mac Native builds only. `true` enables `com.apple.security.cs.allow-jit` for hardened runtime. ParparVM is AOT-compiled so this is `false` by default; flip when bundling a JIT-using cn1lib. - -|macNative.entitlements.extra -|Mac Native builds only. Free-form XML inserted verbatim inside the `…` of the generated entitlements plist. Use for entitlements Codename One doesn't expose individually. - -|macNative.fixedWindowSize -|Mac Native builds only. Opt-in. Format `x` — for example `1024x685`. When set, the Catalyst window's `UISceneSession.sizeRestrictions` minimum and maximum are pinned to the requested size so every launch produces a byte-identical window. Default unset, in which case the window is resizable. The CI screenshot pipeline turns this on to keep the strict-pixel golden comparison stable; production apps should leave it off. - -|desktop.width -|Width in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 800. - -|desktop.height -|Height in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 600. - -|desktop.adaptToRetina -|Boolean true/false defaults to true. When set to true some values will ve implicitly doubled to deal with retina displays and icons etc. Will use higher DPI's - -|desktop.resizable -|Boolean true/false defaults to true. Indicates whether the UI in the desktop build is resizable - -|desktop.fontSizes -|Indicates the sizes in pixels for the system fonts as a comma delimited string containing 3 numbers for small,medium,large fonts. - -|desktop.theme -|Name of the theme res file (without the ".res" extension) to use as the "native" theme. By default this is native indicating iOS theme on Mac and Windows Metro on Windows. If its something else then the app will try to load the file /themeName.res (placed in native/Java SE directory). - -|desktop.themeMac -|Same as `desktop.theme` but specific to macOS - -|desktop.themeWin -|Same as `desktop.theme` but specific to Windows - -|desktop.windowsOutput -|Can be exe or msi depending on desired results - -|desktop.win.cef -|Whether to use CEF for media and BrowserComponent instead of JavaFX in windows desktop builds. true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a future version. - -|desktop.mac.cef -|Whetherto use CEF for media or BrowserComponent instead of JavaFX in Mac desktop builds. true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a future version. - -|tvNative.enabled -|true/false (defaults to false). Adds an Apple TV (tvOS) application target to the iOS build. The tvOS app is a separate `appletvos` target built from the same Java/Kotlin sources through ParparVM (UIKit + Metal; tvOS has no OpenGL ES). Enabling it doesn't change the iOS app -- in particular it doesn't override the iOS app's `ios.metal` setting. Also turned on implicitly by `codename1.tvMain`. - -|tvNative.mainClass (a.k.a. codename1.tvMain) -|Fully-qualified tvOS lifecycle entry class. Setting it auto enables the tvOS target. If omitted while `tvNative.enabled=true`, the tvOS app reuses the phone main class. - -|tvNative.bundleId -|Bundle identifier of the tvOS app. Defaults to `.tvos`. - -|tvNative.minDeploymentTarget -|`TVOS_DEPLOYMENT_TARGET` for the tvOS target. Defaults to `13.0`. - -|tvNative.displayName -|The tvOS app name shown on Apple TV. Defaults to the app's display name. - -|tvNative.teamId -|Apple Developer Team ID used to sign the tvOS target. Falls back to the iOS team id (`ios.release.teamId` / `ios.teamId` / `ios.debug.teamId`). - -|mac.desktop-vm -|The JVM the should be bundled with Mac desktop build. Mac desktop builds only. Supported values: zuluFx8, zulu11, zuluFx11 - -|win.desktop-vm -|The JVM that should be bundled in the Windows desktop build. Windows desktop builds only. Supported values: zulu8, zuluFx8, zulu8-32bit, zuluFx8-32bit, zulu11, zuluFx11, zulu11-32bit, zuluFx11-32bit - -|windows.extensions -|Historical build hint for the discontinued UWP target. It's retained here only for legacy reference and isn't used by current supported build targets. - -|win.vm32bit -|true/false (defaults to false). Forces windows desktop builds to use the Win32 JVM instead of the 64 bit VM making them compatible with older Windows Machines. This is off by default at the moment because of a bug in JDK 8 update 112 that might cause this to fail for some cases - -|win.installDirName -|Windows desktop builds only. Overrides the default installation folder name suggested by the installer (under `Program Files`). Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly installation folder name (for example, `win.installDirName=My Application`). The application ID used by Windows for upgrade detection is unaffected, so existing installations continue to upgrade. - -|win.shortcutName -|Windows desktop builds only. Overrides the name used for the Start Menu shortcut, the Desktop shortcut and (when `win.launchOnStart=true`) the autostart shortcut. Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly shortcut label (for example, `win.shortcutName=My Application`). - -|noExtraResources -|true/false (defaults to false). Blocks codename one from injecting its own resources when set to true, the only effect this has is in slightly reducing archive size. This might have adverse effects on some features of Codename One so it isn't recommended. - -|windows.arch -|Native Windows port only (the `windows-native` build target -- not the JVM `win.*` desktop hints above). Target CPU architecture for the standalone `.exe`: `x64` (the default) or `arm64`. Accepts the usual synonyms (`x86_64`/`amd64`, `aarch64`). clang-cl cross-compiles to the chosen architecture from either host. See the link:#_working_with_the_native_windows_port[Working with the native Windows port chapter]. - -|windows.debug -|Native Windows port only. true/false (defaults to false). When `false` the `.exe` is built optimized and *stripped* -- no PDB, dead-stripped unreferenced code (`/OPT:REF`) and folded identical functions (`/OPT:ICF`) -- which is the shipping default. Set `true` to keep debug symbols (a `.pdb` next to the exe, via `RelWithDebInfo` / clang-cl `/Zi` + linker `/DEBUG`) so a native crash address can be symbolized during development. Optimizations stay on in both cases. - -|windows.sdkRoot -|Native Windows port only; used when building on a *non-Windows* host (for example a Linux build server). Path to a Windows SDK laid out by https://github.com/Jake-Shadle/xwin[`xwin splat`] (a directory containing `crt/include` and `sdk/include/um`), used to cross-compile the `.exe` with clang-cl + lld-link instead of a Visual Studio environment. If unset, the `CN1_XWIN_SYSROOT` environment variable is used. Ignored on Windows hosts, which build through Visual Studio. The same SDK serves both `windows.arch` targets (its `x86_64` / `aarch64` lib subdirs). - -|(signing certificate) -|Native Windows port only. The code-signing certificate itself isn't a build-hint argument; configure it through project settings as `codename1.windows.signing.certificate` (path to a PKCS#12 `.pfx`/`.p12` file holding the certificate + key) and `codename1.windows.signing.password`. The build uses it for both local and cloud builds -- a cloud build uploads it with the build request automatically, exactly like the iOS / Android signing certificates. When a certificate is present the produced `.exe` is Authenticode-signed with `osslsigncode` (which signs Windows PE files on any OS, so it works in the Linux build cloud); without one the exe ships unsigned (it runs, but shows "Unknown publisher" in UAC and trips SmartScreen on download). - -|windows.signing.timestampUrl -|Native Windows port only. RFC 3161 timestamp server used when signing, so the signature stays valid after the certificate expires. Default `http://timestamp.digicert.com`; set empty to disable timestamping. - -|windows.signing.digest -|Native Windows port only. Signature digest algorithm. Default `sha256`. - -|windows.signing.name / windows.signing.url -|Native Windows port only. The description and URL embedded in the signature (the "More info" shown by Windows). Default: the app's display name, and no URL. - -|windows.signing -|Native Windows port only. `true`/`false` (default `true`). Set `false` to force an unsigned build even when a certificate is available. - -|=== +.Build hints +include::_generated-build-hints.adoc[] === Versioned builds diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc new file mode 100644 index 00000000000..38586afdbfd --- /dev/null +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -0,0 +1,3187 @@ +// Generated from com.codename1.build.shared.BuildHints by +// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and re-run +// scripts/gen-build-hint-annotations.sh. +// +// The Annotation column names the compiler-checked form where one exists; +// those hints can be written on the application's main class instead of in +// codenameone_settings.properties. + +[cols="2,1,1,2,4"] +|=== +|Name |Type |Default |Annotation |Description + +|and.captureRecord +|string +|_(none)_ +|_(none)_ +| + +|and.facebook_permissions +|string +|_(none)_ +|_(none)_ +| + +|and.themeMode +|`auto`, `modern`, `hololight`, `legacy` +|_(none)_ +|`@Android(themeMode)` +|`auto`, `modern` / `material`, `hololight` (default for existing apps), `legacy`. `auto` and `modern` / `material` opt in to the CSS-generated Android Material 3 theme from `native-themes/android-material/theme.css`. `hololight` is Android Holo Light (what the framework shipped on API 14+ before this refactor). `legacy` loads the pre-Holo Android theme. The legacy alias `cn1.androidTheme` is still accepted, and `and.hololight=true` still maps to `hololight`. The default stays on `hololight` for existing apps until you flip in a future release. + +|android.NotificationChannel.description +|string +|`Remote notifications` +|_(none)_ +| + +|android.NotificationChannel.enableLights +|boolean +|`true` +|_(none)_ +| + +|android.NotificationChannel.enableVibration +|boolean +|`false` +|_(none)_ +| + +|android.NotificationChannel.id +|string +|`cn1-channel` +|_(none)_ +| + +|android.NotificationChannel.importance +|int +|`2` +|_(none)_ +| + +|android.NotificationChannel.lightColor +|string +|_(none)_ +|_(none)_ +| + +|android.NotificationChannel.name +|string +|`Notifications` +|_(none)_ +| + +|android.NotificationChannel.vibrationPattern +|string +|_(none)_ +|_(none)_ +| + +|android.accessibilityGuard +|boolean +|`false` +|_(none)_ +| + +|android.accessibilityGuard.allow +|string +|_(none)_ +|_(none)_ +| + +|android.accessibilityGuard.mode +|string +|`exit` +|_(none)_ +| + +|android.activity.launchMode +|string +|`singleTop` +|`@Android(activityLaunchMode)` +|Allows explicitly setting the `android:launchMode` attribute of the main activity in android. Default is "singleTop," but for some applications you may need to change this behaviour. In particular, apps that are meant to open a file type will need to set this to "singleTask." See https://developer.android.com/guide/topics/manifest/activity-element.html[Android docs for the activity element] for more information about the `android:launchMode` attribute. + +|android.activityClassBody +|string +|_(none)_ +|_(none)_ +| + +|android.activityClassImports +|string +|_(none)_ +|_(none)_ +| + +|android.adaptiveIconBackground +|string +|`#ffffff` +|_(none)_ +|Background color to use for adaptive icons when `android.enableAdaptiveIcons=true` and no background image is supplied. Defaults to `#ffffff` and is written as `@color/ic_launcher_background`. + +|android.adaptiveIconBackgroundImage +|string +|_(none)_ +|_(none)_ +|Optional path (relative to the root of the native Android project) to an image file to use as the adaptive icon background when `android.enableAdaptiveIcons=true`. If this property is set, it overrides `android.adaptiveIconBackground`. + +|android.allowBackup +|boolean +|`true` +|_(none)_ +| + +|android.androidAuto.messaging +|boolean +|`false` +|_(none)_ +| + +|android.androidAuto.minCarApiLevel +|int +|`1` +|_(none)_ +| + +|android.androidAuto.navigation +|boolean +|`false` +|_(none)_ +| + +|android.androidAuto.poi +|boolean +|`false` +|_(none)_ +| + +|android.anyDensity +|boolean +|`true` +|_(none)_ +| + +|android.apacheLegacy +|boolean +|`false` +|_(none)_ +| + +|android.appBundle +|boolean +|_(none)_ +|`@Android(appBundle)` +|Produces an Android App Bundle (.aab) rather than an APK. Required for new Play Store submissions. + +|android.appReview.version +|version +|`2.0.1` +|_(none)_ +| + +|android.ar.required +|boolean +|`false` +|_(none)_ +| + +|android.arrcompile +|string +|_(none)_ +|_(none)_ +| + +|android.arrimplementation +|string +|_(none)_ +|_(none)_ +| + +|android.asyncPaint +|boolean +|`true` +|_(none)_ +|Boolean true/false defaults to true. Toggles the Android pipeline between the legacy pipeline (false) and new pipeline (true) + +|android.background_push_handling +|boolean +|`false` +|_(none)_ +| + +|android.billingclient.version +|version +|`4.0.0` +|_(none)_ +| + +|android.blockExternalStoragePermission +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Disables the external storage (SD card) permission + +|android.blockReadMediaPermissions +|boolean +|_(none)_ +|_(none)_ +|Boolean true/false, defaults to the value of `android.blockExternalStoragePermission`. Suppresses the `READ_MEDIA_VIDEO` and `READ_MEDIA_AUDIO` permissions that playing a URI adds on API 33 and above + +|android.bluetooth.neverForLocation +|boolean +|`true` +|_(none)_ +| + +|android.bluetooth.required +|boolean +|`false` +|_(none)_ +| + +|android.buildToolsVersion +|version +|_(none)_ +|`@Android(buildToolsVersion)` +|Android build-tools version. It also selects the compile SDK, so there is no separate compile-SDK hint. + +|android.captureRecord +|string +|`enabled` +|`@Android(captureRecord)` +|Indicates whether the `RECORD_AUDIO` permission should be requested. Can be `enabled` or any other value to disable this option + +|android.carAppVersion +|version +|`1.4.0` +|_(none)_ +| + +|android.credentialsPlayServicesVersion +|string +|_(none)_ +|_(none)_ +| + +|android.credentialsVersion +|version +|`1.3.0` +|_(none)_ +| + +|android.cusom_layout +|string +|_(none)_ +|_(none)_ +| + +|android.cusom_layout* +|string +|_(none)_ +|_(properties file only)_ +|Numbered custom layout resources: android.cusom_layout1, 2, and so on. The misspelling is load-bearing -- it is the key the builder actually reads, so correcting it would silently drop the layout. + +|android.cusom_layout1 +|string +|_(none)_ +|_(none)_ +|Applies to any number of layouts as long as they're in sequence (for example, android.cusom_layout2, android.cusom_layout3 etc.). Will write the content of the argument as a layout XML file and give it the name `cusom_layout1.xml` onwards. This can be used by native code to work with XML files + +|android.customActivity +|string +|`CodenameOneActivity` +|_(none)_ +| + +|android.customTabsVersion +|version +|`1.8.0` +|_(none)_ +| + +|android.debug +|boolean +|`false` +|`@Android(debug)` +|true/false defaults to true - indicates whether to include the debug version in the build. Defaults conditionally rather than to a fixed value: when android.release is on it defaults to false, and when release is off it defaults to true, so a build that selects neither still produces something installable (AndroidGradleBuilder.java:447-451). + +|android.decouplePlayServiceVersions +|string +|_(none)_ +|_(none)_ +| + +|android.delayPushCompletion +|boolean +|`false` +|_(none)_ +| + +|android.disableR8 +|boolean +|`false` +|`@Android(disableR8)` +|Turns off R8, falling back to the older shrinker. Note that hardening requires R8, so this conflicts with harden.level. + +|android.disableR8FullMode +|boolean +|`true` +|_(none)_ +| + +|android.disableScreenshots +|boolean +|`false` +|_(none)_ +| + +|android.enableAdaptiveIcons +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Enables Android adaptive icon generation in Android Gradle builds. When enabled, Codename One generates `mipmap` launcher resources (`ic_launcher`, `ic_launcher_foreground`, and adaptive XML in `mipmap-anydpi-v26`) and uses them in the application manifest (`android:icon` and `android:roundIcon`). + +|android.enableProguard +|boolean +|`true` +|`@Android(enableProguard)` +|Boolean true/false defaults to true. Allows disabling the proguard obfuscation even on release builds, notice that this isn't recommended + +|android.excludeBolts +|boolean +|`false` +|_(none)_ +| + +|android.extendAppCompatActivity +|boolean +|`false` +|_(none)_ +| + +|android.facebookSdkVersion +|version +|`16.2.0` +|_(none)_ +| + +|android.facebook_permissions +|string +|`\"public_profile\",\"email\",\"user_friends\"` +|_(none)_ +|Permissions for Facebook used in the Android build target, applicable only if Facebook native integration is used. + +|android.file_paths +|string +|` ` +|_(none)_ +| + +|android.firebaseAnalytics +|boolean +|`false` +|_(none)_ +| + +|android.firebaseAnalyticsVersion +|version +|`21.5.0` +|_(none)_ +| + +|android.firebaseCoreVersion +|string +|_(none)_ +|_(none)_ +| + +|android.firebaseMessagingVersion +|string +|_(none)_ +|_(none)_ +| + +|android.foldableSupport +|boolean +|`false` +|_(none)_ +| + +|android.forceJava8Builder +|boolean +|`false` +|_(none)_ +| + +|android.foregroundServiceType +|string +|`dataSync` +|_(none)_ +| + +|android.fridaDebugLogging +|boolean +|_(none)_ +|_(none)_ +|Boolean true/false defaults to false. If true, it will add verbose debug logs during frida detection to show which check if fails on. + +|android.fridaDetection +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Indicates whether the app should check for the presence of the https://www.frida.re/[Frida] dynamic instrumentation toolkit on the device. If Frida is detected, the app will exit. This uses the [frida-blocker](https://github.com/shannah/frida-blocker) library to perform the frida detection. + +|android.fridaVersion +|string +|_(none)_ +|_(none)_ +|x.y.z The version of [frida-blocker](https://github.com/shannah/frida-blocker) to use to perform frida detection. This is only relevant if `android.fridaDetection=true`. If omitted, it will use the latest tested version in the build server. + +|android.fullScreenIntent +|boolean +|`false` +|_(none)_ +| + +|android.googleAdUnitId +|string +|_(none)_ +|_(none)_ +|Allows integrating admob/google play ads, this is effectively identical to google.adUnitId but only applies to Android + +|android.googleAdUnitTestDevice +|string +|`C6783E2486F0931D9D09FABC65094FDF` +|_(none)_ +|Device key used to mark a specific Android device as a test device for Google Play ads defaults to C6783E2486F0931D9D09FABC65094FDF + +|android.gpsPermission +|boolean +|`false` +|_(none)_ +|Indicates whether the GPS permission should be requested, it's autodetected by default if you use the location API. But, some code might want to explicitly define it + +|android.gradle.androidx +|list (newline delimited) +|_(none)_ +|_(none)_ +| + +|android.gradleDep +|list (`;` delimited) +|_(none)_ +|`@Android(gradleDep)` +|Gradle dependency statements to add to the app module, such as implementation 'com.example:lib:1.0'. + +|android.gradlePlugin +|list (newline delimited) +|_(none)_ +|_(none)_ +| + +|android.hce +|boolean +|`false` +|_(none)_ +| + +|android.hceAids +|string +|`F0010203040506` +|_(none)_ +| + +|android.hceCategory +|string +|`other` +|_(none)_ +| + +|android.hceDescription +|string +|_(none)_ +|_(none)_ +| + +|android.hceRequireUnlock +|boolean +|`false` +|_(none)_ +| + +|android.headphoneCallback +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. When set to true it assumes the main class has two methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately as needed + +|android.health.background +|boolean +|`false` +|_(none)_ +| + +|android.health.connectVersion +|string +|`1.1.0-alpha07` +|_(none)_ +| + +|android.health.history +|boolean +|`false` +|_(none)_ +| + +|android.health.privacyPolicyUrl +|string +|_(none)_ +|_(none)_ +| + +|android.health.read +|string +|_(none)_ +|_(none)_ +| + +|android.health.write +|string +|_(none)_ +|_(none)_ +| + +|android.hideOverlayWindows +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Declares the `android.permission.HIDE_OVERLAY_WINDOWS` permission needed by `DeviceIntegrity.setHideOverlayWindows()` on Android 12+, for apps that call the runtime API without enabling `android.tapjackingGuard`. A normal install-time permission, so the user sees no prompt. + +|android.hideStatusBar +|boolean +|`false` +|`@Android(hideStatusBar)` +|Hides the Android status bar. + +|android.hms.pushVersion +|string +|`6.3.0.302` +|_(none)_ +| + +|android.home.playServicesVersion +|string +|`16.0.0-beta1` +|_(none)_ +| + +|android.includeGPlayServices +|boolean +|`true` +|_(none)_ +|*Deprecated, please android.playService.+++*+++!* Indicates whether Google Play Services should be included into the build, defaults to false but that might change based on the functionality of the application and other build hints. Adding Google Play Services support allows you to use a more refined location implementation and invoke some Google specific functionality from native code. + +|android.includeMavenCentral +|boolean +|`false` +|_(none)_ +| + +|android.installLocation +|`auto`, `internalOnly`, `preferExternal` +|`auto` +|`@Android(installLocation)` +|Maps to android:installLocation manifest entry defaults to auto. Can also be set to internalOnly or preferExternal. + +|android.java8 +|string +|_(none)_ +|_(none)_ +| + +|android.keyboardOpen +|boolean +|`true` +|_(none)_ +|Boolean true/false defaults to true. Toggles the new async keyboard mode that leaves the keyboard open while you move between text components + +|android.largeScreens +|boolean +|`true` +|_(none)_ +| + +|android.licenseKey +|string +|_(none)_ +|`@Android(licenseKey)` +|The license key for the Android app, this is required if you use in-app purchase on Android + +|android.locales +|string +|_(none)_ +|_(none)_ +| + +|android.manifest.queries +|string +|_(none)_ +|_(none)_ +|Embeds XML content into the section of the Android manifest file. This is https://developer.android.com/training/package-visibility[required in Android 11 for package visibility]. See https://developer.android.com/guide/topics/manifest/queries-element[queries element Android documentation]. + +|android.messagingService +|string +|_(none)_ +|_(none)_ +| + +|android.migrateToAndroidX +|boolean +|`true` +|_(none)_ +| + +|android.min_sdk_version +|int +|`19` +|`@Android(minSdkVersion)` +|The least SDK required to run this app, the default value changes based on functionality but can be as low as 7. This corresponds to the XML attribute `android:minSdkVersion`. + +|android.mockLocation +|boolean +|`true` +|_(none)_ +|Boolean true/false defaults to true. Toggles the mock location permission which is on by default, this allows easier debugging of Android device location based services + +|android.mopubId +|string +|_(none)_ +|_(none)_ +| + +|android.multidex +|boolean +|`true` +|`@Android(multidex)` +|Boolean true/false defaults to false. Multidex allows Android binaries to reference more than 65536 methods. This slows builds a bit so you have it off by default but if you get a build error mentioning this limit you should turn this on. + +|android.newFirebaseMessaging +|boolean +|`true` +|`@Android(newFirebaseMessaging)` +|Uses the current Firebase Cloud Messaging integration. Requires AndroidX and Gradle 8.13 or newer. + +|android.nonconsumable +|string +|_(none)_ +|_(none)_ +|Comma delimited string of items that are non-consumable in the in-app purchase API + +|android.normalScreens +|boolean +|`true` +|_(none)_ +| + +|android.onCreate +|string +|_(none)_ +|_(none)_ +| + +|android.onDeviceDebug +|boolean +|`false` +|`@OnDeviceDebug(android)` +|Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable="true"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable="true"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it — release builds are unaffected. See the On-Device Debugging (Android) chapter for the full flow. + +|android.permission.* +|string +|_(none)_ +|_(properties file only)_ +|true/false. Whether to include a particular permission. Preferred over android.xpermissions because it avoids conflicts with libraries. See Android's Manifest.permission documentation for the full list. The optional .maxSdkVersion suffix becomes the maxSdkVersion attribute of the generated tag, and .required marks the permission required. + +|android.playIntegrity +|boolean +|`false` +|_(none)_ +| + +|android.playIntegrity.verifyUrl +|string +|_(none)_ +|_(none)_ +| + +|android.playIntegrityVersion +|version +|`1.4.0` +|_(none)_ +| + +|android.playService.* +|string +|_(none)_ +|_(properties file only)_ +|Opts a single Google Play service in or out. The sibling .minPlayServicesVersion pins its version. + +|android.playService.ads +|boolean +|`false` +|_(none)_ +| + +|android.playService.analytics +|string +|_(none)_ +|_(none)_ +| + +|android.playService.appInvite +|boolean +|`false` +|_(none)_ +| + +|android.playService.auth +|string +|_(none)_ +|_(none)_ +| + +|android.playService.base +|string +|_(none)_ +|_(none)_ +| + +|android.playService.cast +|boolean +|`false` +|_(none)_ +| + +|android.playService.drive +|boolean +|`false` +|_(none)_ +| + +|android.playService.fitness +|boolean +|`false` +|_(none)_ +| + +|android.playService.games +|boolean +|`false` +|_(none)_ +| + +|android.playService.gcm +|string +|_(none)_ +|_(none)_ +| + +|android.playService.identity +|boolean +|`false` +|_(none)_ +| + +|android.playService.indexing +|boolean +|`false` +|_(none)_ +| + +|android.playService.location +|string +|_(none)_ +|_(none)_ +| + +|android.playService.maps +|string +|_(none)_ +|_(none)_ +| + +|android.playService.nearby +|boolean +|`false` +|_(none)_ +| + +|android.playService.panorama +|boolean +|`false` +|_(none)_ +| + +|android.playService.plus +|boolean +|`false` +|_(none)_ +| + +|android.playService.safetynet +|boolean +|`false` +|_(none)_ +| + +|android.playService.vision +|boolean +|`false` +|_(none)_ +| + +|android.playService.wallet +|boolean +|`false` +|_(none)_ +| + +|android.playService.wearable +|boolean +|`false` +|_(none)_ +| + +|android.playServicesVersion +|string +|_(none)_ +|_(none)_ +|The version number of play services to build against. Experimental. **Use with caution** as building against versions other than the server default may introduce incompatibilities with some Codename One APIs. + +|android.proguardKeep +|list (newline delimited) +|_(none)_ +|`@Android(proguardKeep)` +|Arguments for the keep option in proguard allowing you to keep a pattern of files for example, `-keep class com.mypackage.ProblemClass { *; }` + +|android.proguardKeepOverride +|string +|`Exceptions, InnerClasses, Signature, Deprecated, SourceFile, LineNumberTable, *Annotation*, EnclosingMethod` +|_(none)_ +| + +|android.pushSound +|string +|_(none)_ +|_(none)_ +| + +|android.pushVibratePattern +|string +|_(none)_ +|_(none)_ +|Comma delimited long values to describe the push pattern of vibrate used for the `setVibrate` native method + +|android.release +|boolean +|`true` +|`@Android(release)` +|true/false defaults to true - indicates whether to include the release version in the build + +|android.removeBasePermissions +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Disables the built-in permissions specifically `INTERNET` permission (that is, no networking...) + +|android.repositories +|list (newline delimited) +|_(none)_ +|`@Android(repositories)` +|Extra Gradle repositories to resolve dependencies from. + +|android.requestReadMediaPermissions +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Declares `READ_MEDIA_IMAGES`, `READ_MEDIA_VIDEO` and `READ_MEDIA_AUDIO` on API 33 and above even when the build detected no media playback. `READ_MEDIA_IMAGES` is only ever added by this hint + +|android.rootCheck +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Indicates whether the app should check for root access on the device. If root access is detected, the app will exit. + +|android.rootbeerVersion +|version +|`0.1.0` +|_(none)_ +| + +|android.shareFilter +|string +|_(none)_ +|_(none)_ +| + +|android.sharedUserId +|string +|_(none)_ +|_(none)_ +|Allows adding a manifest attribute for the sharedUserId option + +|android.sharedUserLabel +|string +|_(none)_ +|_(none)_ +|Allows adding a manifest attribute for the sharedUserLabel option + +|android.shrinkResources +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Used only in conjunction with android.enableProguard. Strips out unused resources to reduce apk size. Since 7.0 + +|android.signingV1 +|boolean +|_(none)_ +|_(none)_ +|true/false Default true. See https://source.android.com/docs/security/features/apksigning + +|android.signingV2 +|boolean +|_(none)_ +|_(none)_ +|true/false Default true. See https://source.android.com/docs/security/features/apksigning + +|android.signingV3 +|boolean +|_(none)_ +|_(none)_ +|true/false Default true. See https://source.android.com/docs/security/features/apksigning + +|android.signingV4 +|boolean +|_(none)_ +|_(none)_ +|true/false Default true. See https://source.android.com/docs/security/features/apksigning + +|android.smallScreens +|boolean +|`true` +|_(none)_ +|Boolean true/false defaults to true. Corresponds to the `android:smallScreens` XML attribute and allows disabling the support for small phones + +|android.stack_size +|string +|_(none)_ +|_(none)_ +|Size in bytes for the Android stack thread + +|android.statusbar_hidden +|boolean +|`false` +|_(none)_ +|true/false defaults to false. When set to true hides the status bar on Android devices. + +|android.store_ids +|string +|_(none)_ +|_(none)_ +| + +|android.streamMode +|string +|_(none)_ +|_(none)_ +|The mode in which the volume key should behave, defaults to OS default. Allows setting it to `music` for music playback apps + +|android.stringsXml +|string +|_(none)_ +|_(none)_ +|Allows injecting more entries into the strings.xml file using a value that includes something like this `value1value2` + +|android.style +|string +|_(none)_ +|_(none)_ +|Allows injecting more data into the `styles.xml` file right before the closing resources tag + +|android.supportScreens +|string +|_(none)_ +|_(none)_ +| + +|android.supportV4 +|boolean +|_(none)_ +|_(none)_ +|Boolean true/false defaults to false but that can change based on usage (for example, push implicitly activates this). Indicates whether the android support v4 library should be included in the build + +|android.supportv4Dep +|list (newline delimited) +|_(none)_ +|_(none)_ +| + +|android.surfaces.exactAlarms +|boolean +|`false` +|_(none)_ +| + +|android.tapjackingGuard +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Switches on tapjacking / screen-overlay protection at launch, so touches that arrive while another app's window covers this one are detected and dropped. See the security chapter. + +|android.tapjackingGuard.hideOverlays +|boolean +|`true` +|_(none)_ +|Boolean true/false defaults to true. Also asks Android 12+ to hide overlay windows drawn over the app, which is the only mitigation that covers native peer components, and declares the `HIDE_OVERLAY_WINDOWS` permission it requires. Only relevant if `android.tapjackingGuard=true`. + +|android.tapjackingGuard.mode +|string +|`block` +|_(none)_ +|`block` (default), `strict`, `report` or `off`. `block` drops gestures that start on a fully obscured window, `report` only observes, `strict` also drops touches where only part of the window is covered (which benign system UI can trigger). Only relevant if `android.tapjackingGuard=true`. + +|android.targetSDKVersion +|int +|_(none)_ +|`@Android(targetSDKVersion)` +|Indicates the Android SDK used to compile the Android build defaults to 21. Notice that not all targets will work since the source might have some limitations and not all SDK targets are installed on the build servers. + +|android.textureView +|boolean +|`false` +|_(none)_ +| + +|android.theme +|string +|`Light` +|_(none)_ +|Light or Dark defaults to Light. On Android 4+ the default Holo theme is used to render the native widgets sometimes and this indicates whether holo light or holo dark is used. This doesn't affect the Codename One theme but that might change in the future. + +|android.topDependency +|list (newline delimited) +|_(none)_ +|`@Android(topDependency)` +|Statements added to the top-level Gradle build file rather than the app module. + +|android.tv +|boolean +|`false` +|_(none)_ +|true/false (defaults to false). Marks the build as an Android TV / Google TV app. Adds the `LEANBACK_LAUNCHER` intent category to the launcher activity (so the app appears on the TV home screen), declares the `android.software.leanback` feature, makes `android.hardware.touchscreen` optional (so it installs on touchless TVs), and generates a 320×180 launcher banner (`@drawable/tv_banner`) from the app icon. The same APK still installs and runs on phones and tablets, and `CN.isTV()` returns true at runtime on a TV. + +|android.useAndroidX +|boolean +|_(none)_ +|`@Android(useAndroidX)` +|Use Android X instead of support libraries. This will also run a find/replace on all source files to replace support libraries and artifacts with AndroidX equivalents. + +|android.useGradle8 +|string +|_(none)_ +|_(none)_ +| + +|android.uses_feature.* +|string +|_(none)_ +|_(properties file only)_ +|Adds a element named by the suffix. + +|android.uses_permission.* +|string +|_(none)_ +|_(properties file only)_ +|Adds a element named by the suffix. + +|android.versionCode +|string +|_(none)_ +|_(none)_ +|Allows overriding the auto generated version number with a custom internal version number specifically used for the XML attribute `android:versionCode` + +|android.wear +|boolean +|`false` +|_(none)_ +| + +|android.wear.standalone +|string +|_(none)_ +|_(none)_ +| + +|android.web_loading_hidden +|boolean +|`false` +|_(none)_ +|true/false defaults to false - set to true to hide the progress indicator that appears when loading a web page on Android. + +|android.windowVersion +|version +|`1.3.0` +|_(none)_ +| + +|android.xactivity +|xml +|_(none)_ +|_(none)_ +|Allows injecting more attributes into the `activity` tag in the Android XML + +|android.xapplication +|xml +|_(none)_ +|`@Android(xapplication)` +|defaults to an empty string. Allows developers of native Android code to add text within the application block to define things such as widgets, services etc. + +|android.xapplication_attr +|xml +|_(none)_ +|_(none)_ +|Allows injecting more attributes into the `application`` tag in the Android XML + +|android.xgradle +|list (newline delimited) +|_(none)_ +|`@Android(xgradle)` +|Arbitrary text spliced into the generated app-module Gradle file. + +|android.xgradle_default_config +|list (newline delimited) +|_(none)_ +|_(none)_ +| + +|android.xintent_filter +|xml +|_(none)_ +|_(none)_ +|Allows adding an intent filter to the main android activity + +|android.xlargeScreens +|boolean +|`true` +|_(none)_ +| + +|android.xlayout_attr +|string +|_(none)_ +|_(none)_ +| + +|android.xmanifest +|xml +|_(none)_ +|_(none)_ +| + +|android.xpermissions +|xml +|_(none)_ +|`@Android(xpermissions)` +|more permissions for the Android manifest + +|desktop.adaptToRetina +|boolean +|`true` +|`@Desktop(adaptToRetina)` +|Boolean true/false defaults to true. When set to true some values will ve implicitly doubled to deal with retina displays and icons etc. Will use higher DPI's + +|desktop.fontSizes +|string +|_(none)_ +|_(none)_ +|Indicates the sizes in pixels for the system fonts as a comma delimited string containing 3 numbers for small,medium,large fonts. + +|desktop.fullscreen +|boolean +|`false` +|`@Desktop(fullscreen)` +|Starts the desktop build in full-screen mode. + +|desktop.height +|int +|`600` +|`@Desktop(height)` +|Height in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 600. + +|desktop.interactiveScrollbars +|boolean +|`true` +|`@Desktop(interactiveScrollbars)` +|Enables grab-able, click-to-page desktop scrollbars. + +|desktop.resizable +|boolean +|`true` +|`@Desktop(resizable)` +|Boolean true/false defaults to true. Indicates whether the UI in the desktop build is resizable + +|desktop.theme +|string +|_(none)_ +|_(none)_ +|Name of the theme res file (without the ".res" extension) to use as the "native" theme. By default this is native indicating iOS theme on Mac and Windows Metro on Windows. If its something else then the app will try to load the file /themeName.res (placed in native/Java SE directory). + +|desktop.themeMac +|string +|_(none)_ +|_(none)_ +|Same as `desktop.theme` but specific to macOS + +|desktop.themeWin +|string +|_(none)_ +|_(none)_ +|Same as `desktop.theme` but specific to Windows + +|desktop.title +|string +|_(none)_ +|_(none)_ +| + +|desktop.titleBar +|`native`, `custom`, `toolbar` +|`native` +|`@Desktop(titleBar)` +|How the desktop window is framed: native for the OS title bar and menu bar, custom for an undecorated window with a Codename One drawn title bar, or toolbar for the legacy in-app Toolbar. An unrecognized value falls back to native with a warning. + +|desktop.width +|int +|`800` +|`@Desktop(width)` +|Width in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 800. + +|desktop.win.cef +|boolean +|_(none)_ +|_(none)_ +|Whether to use CEF for media and BrowserComponent instead of JavaFX in windows desktop builds. true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a future version. + +|desktop.windowsOutput +|string +|_(none)_ +|_(none)_ +|Can be exe or msi depending on desired results + +|KeepScreenOn +|boolean +|`false` +|_(none)_ +| + +|androidx.appcompat.version +|string +|_(none)_ +|_(none)_ +| + +|block_server_registration +|boolean +|_(none)_ +|_(none)_ +|true/false flag defaults to false. By default Codename One applications register with the Codename One server. Setting this to true blocks them from sending information to the Codename One cloud, which is kept for statistical purposes and may be used to provide more installation stats in the future. + +|build.cn1Version +|string +|_(none)_ +|_(none)_ +|Pro/Enterprise only. Pins the cloud build to a specific released Codename One version using the Maven release scheme (for example `7.0.182`), or to `master` to build against the current development head. The build server fetches that version's framework artifacts. Pro accounts can target versions published within the last two months; Enterprise within the last six months. Requesting an older version, a version that was never published, or using this hint without a Pro/Enterprise subscription fails the build with an explanatory error. See Versioned builds. + +|build.incSources +|string +|_(none)_ +|_(none)_ +| + +|build.testReporter +|string +|_(none)_ +|_(none)_ +| + +|build.unitTest +|string +|_(none)_ +|_(none)_ +| + +|cn1.androidTheme +|string +|_(none)_ +|_(none)_ +| + +|cn1.buildKey +|string +|_(none)_ +|_(none)_ +| + +|cn1.entitled +|boolean +|`true` +|_(none)_ +| + +|cn1.harden.forceOff +|string +|_(none)_ +|_(none)_ +| + +|cn1.hardenLevel +|string +|`off` +|_(none)_ +| + +|cn1.hardened +|boolean +|`false` +|_(none)_ +| + +|cn1.hardening.libraryJars +|string +|_(none)_ +|_(none)_ +| + +|cn1.mappingId +|string +|_(none)_ +|_(none)_ +| + +|cn1.nativeTheme +|string +|_(none)_ +|_(none)_ +| + +|codename1.mac.appid +|string +|_(none)_ +|_(none)_ +|Mac Native cloud builds only. The Mac bundle identifier registered in App Store Connect / Apple Developer. Distinct from `codename1.ios.appid` because Apple treats the iOS and Mac App Store records as separate products. Required for cloud Mac builds. + +|codename1.mac.certificate +|string +|_(none)_ +|_(none)_ +|Mac Native cloud builds only. Path to the `.p12` file containing the Mac signing certificate(s) — _Mac App Distribution_ (3rd Party Mac Developer Application) for App Store builds, _Developer ID Application_ for Developer ID builds, or both bundled into the same P12 when `macNative.distribution=both`. Not interchangeable with the iOS distribution certificate. Required for cloud Mac builds. + +|codename1.mac.certificatePassword +|string +|_(none)_ +|_(none)_ +|Mac Native cloud builds only. Password to unlock the P12 referenced by `codename1.mac.certificate`. Required for cloud Mac builds. + +|codename1.mac.provision +|string +|_(none)_ +|_(none)_ +|Mac Native cloud builds only. Path to the Mac provisioning profile (`.provisionprofile`). Apple issues distinct provisioning profiles for Mac App Store and Developer ID distribution — pass the one that matches the chosen channel. + +|db.legacy +|string +|_(none)_ +|_(none)_ +| + +|delayPushCompletion +|boolean +|`false` +|_(none)_ +| + +|facebook.appId +|string +|`706695982682332` +|`@Build(facebookAppId)` +|The application ID for an app that requires native Facebook login integration, this defaults to null which means native Facebook support shouldn't be in the app + +|facebook.clientToken +|string +|_(none)_ +|_(none)_ +|The client token for an app that requires native Facebook login integration, this is required if the facebook.appId is set. + +|gcm.sender_id +|string +|_(none)_ +|`@Build(gcmSenderId)` +|The Android/chrome push identifier, see the push section for more details + +|google.adUnitId +|string +|_(none)_ +|_(none)_ +|Allows integrating Admob/Google Play ads into the application see link:https://www.codenameone.com/blog/adding-google-play-ads.html[this] + +|gradleDependencies +|list (newline delimited) +|_(none)_ +|_(none)_ +| + +|harden.* +|string +|_(none)_ +|_(properties file only)_ +|The whole hardening namespace is swept into the hardening engine's configuration, so a hint added there reaches it without a dedicated reader. + +|harden.*.enabled +|string +|_(none)_ +|_(properties file only)_ +|Enables or disables hardening for one platform slice. + +|harden.allowUnhardenedLocalBuild +|boolean +|`false` +|`@Hardening(allowUnhardenedLocalBuild)` +|Permits a local or source build to run with hardening requested but not applied. Without it such a build is refused, so a hardened app is never shipped from a target that cannot actually harden it. + +|harden.controlFlow +|`off`, `on` +|_(none)_ +|`@Hardening(controlFlow)` +|Overrides control-flow obfuscation independently of harden.level. + +|harden.ios.enabled +|boolean +|`true` +|_(none)_ +| + +|harden.keep +|text_block +|_(none)_ +|`@Hardening(keep)` +|Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at runtime and so cannot be found by the automatic analysis. Same syntax as android.proguardKeep, so existing rules port directly. Rules are separated by newlines only, because a semicolon is legal inside a rule body such as { *; }. + +|harden.level +|`off`, `standard`, `aggressive`, `paranoid` +|`off` +|`@Hardening(level)` +|Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized value fails the build rather than being quietly treated as off. + +|harden.mac.enabled +|boolean +|`true` +|_(none)_ +| + +|harden.rename +|boolean +|_(none)_ +|`@Hardening(rename)` +|Overrides symbol renaming independently of harden.level. + +|harden.strings +|`off`, `constants`, `all` +|_(none)_ +|`@Hardening(strings)` +|Overrides string obfuscation independently of harden.level: off, constants or all. + +|harden.tv.enabled +|boolean +|`true` +|_(none)_ +| + +|harden.watch.enabled +|boolean +|`true` +|_(none)_ +| + +|java.version +|int +|`8` +|_(none)_ +|Valid values include 5 or 8. Indicates the JVM version that should be used for server compilation, this is defined by default for newly created apps based on the Java 8 mode selection + +|mac.desktop-vm +|string +|_(none)_ +|_(none)_ +|The JVM the should be bundled with Mac desktop build. Mac desktop builds only. Supported values: zuluFx8, zulu11, zuluFx11 + +|maps.provider +|string +|_(none)_ +|_(none)_ +| + +|nativeTheme +|`modern`, `legacy`, `custom` +|_(none)_ +|`@Build(nativeTheme)` +|`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both `ios.themeMode` and `and.themeMode` together when those aren't set explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the framework native theme entirely. The legacy alias `cn1.nativeTheme` is still accepted. + +|noExtraResources +|boolean +|`false` +|`@Build(noExtraResources)` +|true/false (defaults to false). Blocks codename one from injecting its own resources when set to true, the only effect this has is in slightly reducing archive size. This might have adverse effects on some features of Codename One so it isn't recommended. + +|requireKotlinStdlib +|string +|_(none)_ +|_(none)_ +| + +|tvMain +|string +|_(none)_ +|_(none)_ +| + +|var.* +|string +|_(none)_ +|_(properties file only)_ +|Defines a variable that any other hint can interpolate as ${var.name}, with ${var.name:default} for a fallback. + +|vserv.allowSkipping +|boolean +|`true` +|_(none)_ +| + +|vserv.category +|int +|`29` +|_(none)_ +| + +|vserv.countryCode +|string +|`null` +|_(none)_ +| + +|vserv.locale +|string +|`en_US` +|_(none)_ +| + +|vserv.networkCode +|string +|`null` +|_(none)_ +| + +|vserv.scaleMode +|boolean +|`false` +|_(none)_ +| + +|vserv.transition +|int +|`300000` +|_(none)_ +| + +|vserv.zone +|string +|_(none)_ +|_(none)_ +| + +|watchMain +|string +|_(none)_ +|_(none)_ +| + +|watchStandalone +|boolean +|`false` +|_(none)_ +| + +|xxx.minPlayServicesVersion +|string +|_(none)_ +|_(none)_ +|This is a special case build hint. You can use any prefix to the build hint and the convention is to use your cn1lib name. It's identical to `android.minPlayServicesVersion` with the exception that the "highest version wins." That way if your cn1lib requires play services 9+ and uses: `myLib.minPlayServicesVersion=9.0.0` and another library has `otherLib.minPlayServicesVersion=10.0.0` then play services will be 10.0.0 + +|ios.*.appext.* +|string +|_(none)_ +|_(properties file only)_ +|Per-app-extension signing. ios.debug.appext..* and ios.release.appext..* are collapsed to unqualified keys before the request is sent. + +|ios.NFCReaderUsageDescription +|string +|_(none)_ +|_(none)_ +| + +|ios.NS*UsageDescription +|string +|_(none)_ +|_(properties file only)_ +|Info.plist privacy strings. The commonly used keys are catalogued individually and exposed through @IosPrivacy; this entry covers the open tail that the builder sweeps by prefix. + +|ios.NSBonjourServices +|string +|_(none)_ +|_(none)_ +| + +|ios.NSCalendarsFullAccessUsageDescription +|string +|`This app uses your calendars to read and schedule events.` +|`@IosPrivacy(calendarsFullAccessUsageDescription)` +| + +|ios.NSCalendarsUsageDescription +|string +|_(none)_ +|`@IosPrivacy(calendarsUsageDescription)` +| + +|ios.NSCalendarsWriteOnlyAccessUsageDescription +|string +|`This app uses your calendar to schedule events.` +|`@IosPrivacy(calendarsWriteOnlyAccessUsageDescription)` +| + +|ios.NSCameraUsageDescription +|string +|_(none)_ +|`@IosPrivacy(cameraUsageDescription)` +| + +|ios.NSHealthShareUsageDescription +|string +|_(none)_ +|`@IosPrivacy(healthShareUsageDescription)` +| + +|ios.NSHealthUpdateUsageDescription +|string +|_(none)_ +|`@IosPrivacy(healthUpdateUsageDescription)` +| + +|ios.NSLocalNetworkUsageDescription +|string +|_(none)_ +|`@IosPrivacy(localNetworkUsageDescription)` +| + +|ios.NSLocationAlwaysAndWhenInUseUsageDescription +|string +|_(none)_ +|`@IosPrivacy(locationAlwaysAndWhenInUseUsageDescription)` +| + +|ios.NSLocationAlwaysUsageDescription +|string +|_(none)_ +|`@IosPrivacy(locationAlwaysUsageDescription)` +| + +|ios.NSLocationWhenInUseUsageDescription +|string +|_(none)_ +|`@IosPrivacy(locationWhenInUseUsageDescription)` +| + +|ios.NSMicrophoneUsageDescription +|string +|_(none)_ +|`@IosPrivacy(microphoneUsageDescription)` +| + +|ios.NSRemindersFullAccessUsageDescription +|string +|`This app uses your reminders to read and schedule tasks.` +|`@IosPrivacy(remindersFullAccessUsageDescription)` +| + +|ios.NSRemindersUsageDescription +|string +|_(none)_ +|`@IosPrivacy(remindersUsageDescription)` +| + +|ios.NSXXXUsageDescription +|string +|_(none)_ +|_(none)_ +|iOS privacy flags for using certain APIs. Starting with Xcode 8, you're required to add usage description strings for certain APIs. Find a full list of the available keys in https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html[Apple's docs]. Some relevant ones include `ios.NSCameraUsageDescription`, `ios.NSContactsUsageDescription`, `ios.NSLocationAlwaysUsageDescription`, `NSLocationUsageDescription`, `ios.NSMicrophoneUsageDescription`, `ios.NSPhotoLibraryAddUsageDescription`, `ios.NSSpeechRecognitionUsageDescription`, `ios.NSSiriUsageDescription` + +|ios.UIRequiredDeviceCapabilities +|string +|_(none)_ +|_(none)_ +| + +|ios.actionSheetStyle +|string +|_(none)_ +|_(none)_ +| + +|ios.add_libs +|list (`;` delimited) +|_(none)_ +|`@Ios(addLibs)` +|A semicolon separated list of libraries that should be linked to the app to build it + +|ios.afterFinishLaunching +|string +|_(none)_ +|_(none)_ +|Objective-C code that can be injected into the iOS app delegate at the bottom of the body of the didFinishLaunchingWithOptions callback method + +|ios.appAttest +|boolean +|`false` +|_(none)_ +| + +|ios.appAttest.environment +|string +|_(none)_ +|_(none)_ +| + +|ios.appUsesNonExemptEncryption +|string +|_(none)_ +|_(none)_ +| + +|ios.app_groups +|string +|_(none)_ +|_(none)_ +|Space-delimited list of app groups that this app belongs to as described in https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19[Apple's documentation]. These are added to the entitlements file with key `com.apple.security.application-groups`. + +|ios.appext.NAME.provisioningURL +|string +|_(none)_ +|_(none)_ +|Cloud device builds only. URL of the provisioning profile for a generic app extension dropped into `ios/app_extensions/NAME/` (or a generated extension such as `CN1Widgets`), used when the extension folder doesn't bundle a `.mobileprovision` itself. The profile is installed on the build machine and added to the export options per bundle id. Used for both debug and release builds unless a qualified variant (below) is set. An extension is signed against its own App ID, so a device build with no profile for it -- by any of the three carriers -- is refused unless the app's own profile is a wildcard that covers the extension's bundle id. + +|ios.applicationDidEnterBackground +|string +|_(none)_ +|_(none)_ +|Objective-C code that can be injected into the iOS callback method (message) `applicationDidEnterBackground`. + +|ios.applicationQueriesSchemes +|list (`,` delimited) +|_(none)_ +|`@Ios(applicationQueriesSchemes)` +|Comma separated list of url schemes that `canExecute` will respect on iOS. If the url scheme isn't mentioned here `canExecute` will return false starting with iOS 9. Notice that this collides with `ios.plistInject` when used with the `LSApplicationQueriesSchemes...` value so you should use one or the other. For example, to enable `canExecute` for a url like `myurl://xys` you can use: `myurl,myotherurl` + +|ios.application_exits +|boolean +|_(none)_ +|_(none)_ +|true/false (defaults to false). Indicates whether the application should exit on home button press. The default is to exit, leaving the application running is only tested at the moment. + +|ios.associatedDomains +|string +|_(none)_ +|_(none)_ +|Comma-delimited list of domains associated with this app. Each domain should be prefixed by a supported prefix. For example, "applinks:" or "webcredentials:." See https://developer.apple.com/documentation/security/password_autofill/setting_up_an_app_s_associated_domains?language=objc[Apple's documentation on Associated domains] for more information. + +|ios.backgroundProcessingIds +|string +|_(none)_ +|_(none)_ +| + +|ios.background_modes +|string +|_(none)_ +|_(none)_ +| + +|ios.beforeFinishLaunching +|text_block +|_(none)_ +|`@Ios(beforeFinishLaunching)` +|Objective-C code that can be injected into the iOS app delegate at the top of the body of the didFinishLaunchingWithOptions callback method + +|ios.bitcode +|boolean +|`false` +|_(none)_ +|true/false defaults to false. Enables bitcode support for the build. + +|ios.blockScreenshotsOnEnterBackground +|boolean +|`false` +|_(none)_ +|true/false (defaults to false). Indicates that app should prevent iOS from taking screenshots when app enters background. Described https://shannah.github.io/cn1-recipes/#_hiding_sensitive_data_when_entering_background[here]. + +|ios.bluetooth.background +|string +|_(none)_ +|_(none)_ +| + +|ios.buildType +|string +|`debug` +|_(none)_ +| + +|ios.bundleVersion +|version +|_(none)_ +|`@Ios(bundleVersion)` +|Indicates the version number of the bundle, this is useful if you want to create a minor version number change for the beta testing support + +|ios.carplay.audio +|boolean +|`false` +|_(none)_ +| + +|ios.carplay.messaging +|boolean +|`false` +|_(none)_ +| + +|ios.carplay.navigation +|boolean +|`false` +|_(none)_ +| + +|ios.carplay.poi +|boolean +|`false` +|_(none)_ +| + +|ios.convertSignalsToExceptions +|boolean +|`true` +|_(none)_ +| + +|ios.criticalAlerts +|boolean +|`false` +|_(none)_ +| + +|ios.crypto.gcm +|boolean +|`false` +|_(none)_ +| + +|ios.debug.archs +|string +|_(none)_ +|_(none)_ +|Can be set to "armv7" to force iOS debug builds to be 32 bit. By default, debug builds are 64 bit only. + +|ios.debug.distributionMethod +|string +|_(none)_ +|_(none)_ +|Specifies distribution type for debug iOS builds only. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). + +|ios.debug.teamId +|string +|_(none)_ +|_(none)_ +|Specifies the team ID associated with the iOS debug provisioning profile and certificate. + +|ios.delayPushCompletion +|boolean +|`false` +|_(none)_ +| + +|ios.dependencyManager +|`auto`, `cocoapods`, `spm`, `both`, `none` +|`auto` +|`@Ios(dependencyManager)` +|Which native dependency manager to use: auto picks one from whichever of ios.pods and ios.spm.packages is set, and cocoapods, spm or both require the matching hint to be set. An unrecognized value fails the build. + +|ios.deployment_target +|version +|_(none)_ +|`@Ios(deploymentTarget)` +|Minimum iOS version the build targets. Set it to the lowest iOS you actually support; a higher value excludes older devices from the App Store listing. + +|ios.detectJailbreak +|boolean +|`false` +|_(none)_ +|true/false (defaults to false). When true, the iOS app will exit on launch if it detects that it's running on a jailbroken device. + +|ios.devLocale +|string +|_(none)_ +|_(none)_ +| + +|ios.disableScreenshots +|boolean +|`false` +|_(none)_ +| + +|ios.distributionMethod +|string +|_(none)_ +|_(none)_ +|Specifies distribution type for debug iOS builds. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). + +|ios.enableAutoplayVideo +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Makes videos "autoplay" when loaded on iOS + +|ios.enableBadgeClear +|boolean +|`true` +|_(none)_ +|Boolean true/false defaults to true. Clears the badge value with every load of the app, this is useful if the app doesn't manually keep track of number values for the badge + +|ios.enableGalleryMultiselect +|boolean +|`false` +|_(none)_ +| + +|ios.enableStatusBar7 +|boolean +|`true` +|_(none)_ +| + +|ios.entitlements.* +|string +|_(none)_ +|_(properties file only)_ +|Adds an arbitrary entitlement key to the generated entitlements file. + +|ios.entitlements.com.apple.developer +|string +|_(none)_ +|_(none)_ +| + +|ios.entitlements.com.apple.developer.applesignin +|string +|_(none)_ +|_(none)_ +| + +|ios.entitlements.com.apple.developer.healthkit +|boolean +|`false` +|_(none)_ +| + +|ios.entitlements.com.apple.developer.homekit +|string +|_(none)_ +|_(none)_ +| + +|ios.entitlements.com.apple.developer.networking.HotspotConfiguration +|string +|_(none)_ +|_(none)_ +| + +|ios.entitlements.com.apple.developer.nfc.hce +|string +|_(none)_ +|_(none)_ +| + +|ios.entitlements.com.apple.developer.nfc.readersession.formats +|string +|_(none)_ +|_(none)_ +| + +|ios.entitlementsInject +|xml +|_(none)_ +|_(none)_ +|Content to inject into the iOS entitlements file. This should be in the Plist XML format. See https://developer.apple.com/documentation/bundleresources/entitlements?language=objc[Apple Entitlements Documentation]. + +|ios.facebook.usePods +|boolean +|`true` +|_(none)_ +| + +|ios.facebook.version +|string +|`~>5.6.0` +|_(none)_ +| + +|ios.facebook_permissions +|string +|_(none)_ +|_(none)_ +|Permissions for Facebook used in the Android build target, applicable only if Facebook native integration is used. + +|ios.failOnWarning +|boolean +|`false` +|_(none)_ +| + +|ios.fieldNullChecks +|boolean +|`false` +|_(none)_ +| + +|ios.fileSharingEnabled +|boolean +|`false` +|_(none)_ +| + +|ios.firebaseAnalytics +|boolean +|`false` +|_(none)_ +| + +|ios.firebaseAnalyticsVersion +|string +|_(none)_ +|_(none)_ +| + +|ios.force64 +|boolean +|`false` +|_(none)_ +| + +|ios.generateSplashScreens +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Enables legacy generation of splash screen images instead of the current launch storyboards. + +|ios.glAppDelegateBody +|string +|_(none)_ +|_(none)_ +|Objective-C code that can be injected into the iOS app delegate within the body of the file before the end. This only makes sence for methods that aren't already declared in the class + +|ios.glAppDelegateHeader +|text_block +|_(none)_ +|`@Ios(glAppDelegateHeader)` +|Objective-C code that can be injected into the iOS app delegate at the top of the file. For example, if you need to include headers or make special imports for other injected code + +|ios.googleAdUnitId +|string +|_(none)_ +|_(none)_ +|Allows integrating admob/google play ads, this is effectively identical to google.adUnitId but only applies to iOS + +|ios.googleAdUnitIdPadding +|string +|_(none)_ +|_(none)_ +|Indicates the amount of padding to pass to the Google Ads placed at the bottom of the screen with `google.adUnitId` + +|ios.googleAdUnitTestDevice +|string +|`97cfc76e5efbc6dfa7eb2e6857b613a0` +|_(none)_ +| + +|ios.gplus.clientId +|string +|_(none)_ +|_(none)_ +| + +|ios.hceAids +|string +|_(none)_ +|_(none)_ +| + +|ios.headphoneCallback +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. When set to true it assumes the main class has two methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately as needed + +|ios.health.backgroundDelivery +|boolean +|`false` +|_(none)_ +| + +|ios.health.recalibrateEstimates +|boolean +|`false` +|_(none)_ +| + +|ios.health.required +|boolean +|`false` +|_(none)_ +| + +|ios.home.appGroup +|string +|_(none)_ +|_(none)_ +| + +|ios.home.commissioning +|boolean +|`true` +|_(none)_ +| + +|ios.home.commissioning.buildSettings.* +|string +|_(none)_ +|_(properties file only)_ +|Overrides an Xcode build setting for the Matter commissioning extension. + +|ios.home.commissioning.displayName +|string +|_(none)_ +|_(none)_ +| + +|ios.home.commissioning.fabric +|string +|_(none)_ +|_(none)_ +| + +|ios.home.commissioning.vendorId +|string +|`0xFFF1` +|_(none)_ +| + +|ios.home.required +|boolean +|`false` +|_(none)_ +| + +|ios.includeNullChecks +|boolean +|`true` +|_(none)_ +| + +|ios.includePush +|boolean +|`false` +|`@Ios(includePush)` +|true/false (defaults to false). Whether to include the push capabilities in the iOS build. Notice that the IDE plugin has an "Include Push" check box you *should* use under the iOS section. + +|ios.intents.appIntents +|boolean +|`true` +|_(none)_ +| + +|ios.intents.minDeploymentTarget +|string +|_(none)_ +|_(none)_ +| + +|ios.interface_orientation +|string +|_(none)_ +|`@Ios(interfaceOrientation)` +|UIInterfaceOrientationPortrait by default. Indicates the orientation, one or more of (separated by colon :): `UIInterfaceOrientationPortrait`, `UIInterfaceOrientationPortraitUpsideDown`, `UIInterfaceOrientationLandscapeLeft`, `UIInterfaceOrientationLandscapeRight`. Notice that the IDE plugin has an "Interface Orientation" combo box you *should* use under the iOS section. + +|ios.keyboardOpen +|boolean +|`true` +|_(none)_ +|Flips between iOS keyboard open mode and autofold keyboard mode. Defaults to true which means the keyboard will remain open and not fold automatically when editing moves to another field. + +|ios.keychainAccessGroup +|string +|_(none)_ +|_(none)_ +|Space-delimited list of keychain access groups that this app has access to as described in https://developer.apple.com/library/content/documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html#//apple_ref/doc/uid/TP30000897-CH204-SW11[Apple's documentation]. These are added to the entitlements file with the key `keychain-access-groups`. + +|ios.launchPlaceholder +|boolean +|`true` +|_(none)_ +| + +|ios.launchStoryboardName +|string +|`LaunchScreen` +|_(none)_ +| + +|ios.locationUsageDescription +|string +|_(none)_ +|_(none)_ +|This flag is required for iOS 8 and newer if you're using the location API. It needs to include a description of the reason for which you need access to the users location + +|ios.lowMemCamera +|boolean +|`false` +|_(none)_ +| + +|ios.metal +|boolean +|`true` +|_(none)_ +|Boolean true/false defaults to true. Selects the Metal rendering backend (`CAMetalLayer`) over the legacy OpenGL ES 2 path (`CAEAGLLayer`). Metal is the supported iOS graphics API; OpenGL ES is deprecated. Set to `false` to opt out if you hit a Metal-only rendering regression. See link:#_metal_renderer[Working with iOS / Metal renderer] for details. + +|ios.metal.colorSpace +|string +|`sRGB` +|_(none)_ +|Selects the `CAMetalLayer.colorspace` for the Metal renderer. Accepts `sRGB` (default), `displayP3`, `deviceRGB`, `linearSRGB`, `extendedSRGB`, `extendedLinearSRGB`, or `none`. Has no effect when `ios.metal=false`. See link:#_choosing_a_color_space_for_the_metal_renderer[Working with iOS / Choosing a color space] for the full table. + +|ios.minDeploymentTarget +|version +|`6.0` +|`@Ios(minDeploymentTarget)` +|The null and empty-string reads of this hint are presence checks; 6.0 is the substantive default (IPhoneBuilder.java:4671). + +|ios.mopubAdSize +|string +|`MOPUB_BANNER_SIZE` +|_(none)_ +| + +|ios.mopubId +|string +|_(none)_ +|_(none)_ +| + +|ios.mopubTabletAdSize +|string +|`MOPUB_LEADERBOARD_SIZE` +|_(none)_ +| + +|ios.mopubTabletId +|string +|_(none)_ +|_(none)_ +| + +|ios.multitasking +|boolean +|`true` +|_(none)_ +|Set to true to enable iOS multitasking and split-screen support. This only works if `ios.xcode_verson=9.2`. + +|ios.newPipeline +|boolean +|_(none)_ +|_(none)_ +|Boolean true/false defaults to true. Allows toggling the OpenGL ES 2.0 drawing pipeline off to the older OGL ES 1.0 pipeline. + +|ios.newStorageLocation +|boolean +|`true` +|`@Ios(newStorageLocation)` +|true/false defaults to false but defined on new projects as true by default. This changes the storage directory on iOS from using caches to using the documents directory which is the recommended location but might break compatibility. This is described in https://github.com/codenameone/CodenameOne/issues/1480[this issue] + +|ios.noUIWebView +|boolean +|`true` +|_(none)_ +| + +|ios.no_strip +|boolean +|`false` +|_(none)_ +| + +|ios.notificationPermissionAtLaunch +|boolean +|`false` +|_(none)_ +|true/false (defaults to false). Backward-compatibility flag for the pre-issue-#4876 behavior. By default, the iOS notification permission prompt is deferred until the app calls `Push.register()` or schedules a `LocalNotification`, matching the Android flow and giving the developer a chance to display a rationale screen first. Set this hint to `true` to restore the legacy behavior in which the prompt fires automatically inside `application:didFinishLaunchingWithOptions:` as soon as the app launches. Existing apps relying on the prompt being shown at launch should set this to `true`; new apps should leave it disabled and trigger the prompt explicitly when they're ready to ask for permission. + +|ios.objC +|boolean +|`false` +|`@Ios(objC)` +|Added the `-ObjC` compile flag to the project files which some native libraries require + +|ios.onDeviceDebug +|boolean +|`false` +|`@OnDeviceDebug(ios)` +|Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits source-line and locals metadata so a desktop proxy can serve the running app to any JDWP-speaking debugger. Has no effect on release builds. See the On-Device Debugging (iOS) chapter for the full flow. + +|ios.onDeviceDebug.proxyHost +|string +|`127.0.0.1` +|`@OnDeviceDebug(iosProxyHost)` +|Hostname or IP address the device-side listener dials to reach the desktop proxy. Default `127.0.0.1` (correct for the native iOS simulator). For a physical device, set this to the developer laptop's LAN IP. Has no effect unless `ios.onDeviceDebug=true`. + +|ios.onDeviceDebug.proxyPort +|int +|`55333` +|`@OnDeviceDebug(iosProxyPort)` +|TCP port on `ios.onDeviceDebug.proxyHost` where the proxy is listening for the device. Default `55333`. Has no effect unless `ios.onDeviceDebug=true`. + +|ios.onDeviceDebug.waitForAttach +|boolean +|`false` +|`@OnDeviceDebug(iosWaitForAttach)` +|Boolean true/false defaults to false. When `true`, the app blocks at startup until the proxy connects and the IDE tells the VM to continue. Useful when the breakpoint to investigate fires during app boot. Has no effect unless `ios.onDeviceDebug=true`. + +|ios.openURLInject +|xml +|_(none)_ +|_(none)_ +| + +|ios.optimizer +|string +|`on` +|_(none)_ +| + +|ios.plistInject +|xml +|_(none)_ +|`@Ios(plistInject)` +|entries to inject into the iOS plist file during build. + +|ios.pods +|list (`,` delimited) +|_(none)_ +|`@Ios(pods)` +|A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be linked to the app to build it. For example, `AFNetworking ~> 2.6, ORStackView ~> 3.0, SwiftyJSON ~> 2.3` + +|ios.pods.build.* +|string +|_(none)_ +|_(properties file only)_ +|Overrides an Xcode build setting for the generated CocoaPods project. + +|ios.pods.build.CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES +|string +|_(none)_ +|_(none)_ +| + +|ios.pods.build.CLANG_ENABLE_MODULES +|string +|_(none)_ +|_(none)_ +| + +|ios.pods.platform +|version +|_(none)_ +|`@Ios(podsPlatform)` +|Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a minimum platform level. For example, `ios.pods.platform=7.0`. + +|ios.pods.sources +|list (`,` delimited) +|_(none)_ +|`@Ios(podsSources)` +|Extra CocoaPods spec repositories to search, in addition to the default trunk. + +|ios.pods.use_frameworks! +|boolean +|`false` +|_(none)_ +| + +|ios.prerendered_icon +|boolean +|`false` +|`@Ios(prerenderedIcon)` +|true/false defaults to false. The iOS build process adapts the submitted icon for iOS conventions (adding an overlay) that might not be appropriate on some icons. Setting this to true leaves the icon unchanged (only scaled). + +|ios.project_type +|`ios`, `ipad`, `iphone` +|`ios` +|`@Ios(projectType)` +|one of ios, ipad, iphone (defaults to ios). Indicates whether the resulting binary is targeted to the iphone only or ipad only. Notice that the IDE plugin has a "Project Type" combo box you *should* use under the iOS section. + +|ios.release.archs +|string +|_(none)_ +|_(none)_ +|Can be set to "arm64" to only build iOS release builds for 64 bit. By default, release builds are both 32 and 64 bit. + +|ios.release.distributionMethod +|string +|_(none)_ +|_(none)_ +|Specifies distribution type for release iOS builds only. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). + +|ios.release.teamId +|string +|_(none)_ +|_(none)_ +|Specifies the team ID associated with the iOS release provisioning profile and certificate. + +|ios.rpmalloc +|string +|_(none)_ +|_(none)_ +|`true`/`false` Use https://github.com/rampantpixels/rpmalloc[rpmalloc] instead of malloc/free for memory allocation in ParparVM. This will cause the deployment target to be changed to a minimum of iOS 8.0. + +|ios.shareAppGroup +|string +|_(none)_ +|_(none)_ +| + +|ios.spm.packages +|list (`;` delimited) +|_(none)_ +|`@Ios(spmPackages)` +|Swift Package Manager packages to link, one per entry, each written as identity|url|requirement. + +|ios.spm.products.* +|string +|_(none)_ +|_(properties file only)_ +|Selects which products of a Swift Package Manager package to link, keyed by package identity. + +|ios.statusBarFG +|string +|_(none)_ +|_(none)_ +| + +|ios.statusbar_hidden +|boolean +|_(none)_ +|_(none)_ +|true/false defaults to false. Hides the iOS status bar if set to true. + +|ios.superfastBuild +|boolean +|`false` +|_(none)_ +| + +|ios.surfaces.appGroup +|string +|_(none)_ +|_(none)_ +| + +|ios.surfaces.buildSettings.* +|string +|_(none)_ +|_(properties file only)_ +|Overrides an Xcode build setting for the external-surfaces extension. + +|ios.surfaces.deploymentTarget +|version +|`16.1` +|_(none)_ +| + +|ios.surfaces.extension +|boolean +|`true` +|_(none)_ +| + +|ios.surfaces.frequentUpdates +|boolean +|`false` +|_(none)_ +| + +|ios.swiftVersion +|version +|`5.0` +|_(none)_ +| + +|ios.teamId +|string +|_(none)_ +|`@Ios(teamId)` +|Specifies the team ID associated with the iOS provisioning profile and certificate. Use `ios.debug.teamId` and `ios.release.teamId` to specify different team IDs for debug and release builds respectively. + +|ios.testFlight +|boolean +|_(none)_ +|_(none)_ +|Boolean true/false defaults to false and works only for pro accounts. Enables the testflight support in the release binaries for easy beta testing. Notice that the IDE plugin has a "Test Flight" check box you *should* use under the iOS section. + +|ios.themeMode +|`auto`, `modern`, `ios7`, `legacy` +|_(none)_ +|`@Ios(themeMode)` +|`auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the existing iOS 7 flat theme so pre-refactor screenshot goldens and apps see no behavior change. `modern` / `liquid` opts in to the CSS-generated iOS Modern (liquid-glass) theme shipped from `native-themes/ios-modern/theme.css`. `ios7` / `flat` is the same as `auto` - pre-liquid iOS 7 flat theme; `legacy` / `iphone` loads the pre-iOS 7 iPhone theme. The `auto` -> modern flip is planned for a future release. + +|ios.timeSensitiveNotifications +|boolean +|`false` +|_(none)_ +| + +|ios.twoDigitVersion +|boolean +|`false` +|_(none)_ +| + +|ios.uiscene +|boolean +|`true` +|`@Ios(uiscene)` +|true/false (defaults to true). Enables iOS UIScene lifecycle support. UIScene lets iOS manage one or more app UI sessions independently, improving lifecycle handling in modern iOS versions. Apple has indicated UIScene will be required starting with iOS 27, so this is now on by default; set the flag to `false` only if you need to temporarily fall back to the legacy `UIApplicationDelegate` lifecycle. + +|ios.urlScheme +|string +|_(none)_ +|`@Ios(urlScheme)` +|Allows intercepting a URL call using the syntax `urlPrefix` + +|ios.urlSchemes +|string +|_(none)_ +|_(none)_ +| + +|ios.useAVKit +|boolean +|`true` +|_(none)_ +|Use AVKit for video components on iOS rather than `MPMoviePlayerController` on iOS versions 8 through 12. iOS 13 will always use AVKit, and iOS 7 and lower will always use `MPMoviePlayerController`. Default value `false` + +|ios.useJavascriptCore +|boolean +|`false` +|_(none)_ +| + +|ios.usePhotoKitForMultigallery +|boolean +|`false` +|_(none)_ +| + +|ios.usePrintf +|boolean +|`false` +|_(none)_ +| + +|ios.useWKWebView +|boolean +|`true` +|_(none)_ +| + +|ios.usesBackgroundProcessing +|boolean +|`false` +|_(none)_ +| + +|ios.viewDidLoad +|string +|_(none)_ +|_(none)_ +|Objective-C code that can be injected into the iOS callback method (message) `viewDidLoad` + +|ios.viewDidLoadInclude +|string +|_(none)_ +|_(none)_ +| + +|ios.wallet.appGroup +|string +|_(none)_ +|_(none)_ +|App Group id starting with `group.` shared by the app and the generated Wallet extensions. The app publishes pass entries into this group through `com.codename1.payment.WalletExtension` and the group is added to the app and extension entitlements automatically. Required when `ios.wallet.extension=true`. + +|ios.wallet.authEndpoint +|string +|_(none)_ +|_(none)_ +|HTTPS URL the generated login UI extension POSTs `{"username","password"}` to; the JSON response's `token` is stored in the App Group for the provisioning request. Required when `ios.wallet.includeUI=true`. + +|ios.wallet.extension +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Generates an Apple Wallet issuer provisioning extension (the "From apps on your iPhone" flow in the Wallet app) and embeds it in the build. Requires `ios.wallet.appGroup` and `ios.wallet.issuerEndpoint`. See the Apple Wallet Extension chapter. + +|ios.wallet.includeUI +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Also generates the Wallet authorization UI extension - a login form shown inside the Wallet app when the app reports that authentication is required. Requires `ios.wallet.authEndpoint`. + +|ios.wallet.issuerEndpoint +|string +|_(none)_ +|_(none)_ +|HTTPS URL of the issuer backend endpoint that produces the encrypted provisioning payload. The generated extension POSTs Apple's certificates/nonce plus the card identifier and auth token there as JSON. Required when `ios.wallet.extension=true`. + +|ios.wallet.nonuiExtensionName +|string +|`WalletNonUIExtension` +|_(none)_ +| + +|ios.wallet.uiExtensionName +|string +|`WalletUIExtension` +|_(none)_ +| + +|ios.xcode_version +|string +|_(none)_ +|_(none)_ +|The version of Xcode used on the server. Defaults to 4.5; accepts 5.0 as an option and nothing else. + +|ios.zbar_flash +|boolean +|`true` +|_(none)_ +| + +|javascript.includeVideoJS +|boolean +|`false` +|_(none)_ +| + +|javascript.inject.afterHead +|string +|_(none)_ +|_(none)_ +|Content to be injected into the index.html file at the end of the `` tag. + +|javascript.inject.beforeHead +|string +|_(none)_ +|_(none)_ +|Content to be injected into the index.html file at the beginning of the `` tag. + +|javascript.inject_proxy +|boolean +|`true` +|_(none)_ +|true/false (defaults to `true`). The ParparVM builder generates a same-origin proxy bundle and configures the app to use it. Setting this to `false` disables both proxy generation and proxy URL injection. + +|javascript.minifying +|boolean +|_(none)_ +|_(none)_ +|true/false (defaults to `true`). By default the JavaScript code is minified to reduce file size. You may optionally disable minification by setting `javascript.minifying` to `false`. + +|javascript.port +|string +|_(none)_ +|_(none)_ +|`parparvm` (default) or `teavm`. Selects the public JavaScript compiler for cloud builds. `teavm` retains the original builder as a compatibility fallback. + +|javascript.portSources +|string +|_(none)_ +|_(none)_ +| + +|javascript.proxy.allowedTargets +|string +|_(none)_ +|_(none)_ +|Comma-separated target origins, host names, or wildcard subdomains that a generated proxy may access, for example `https://api.example.com,*.services.example.org`. If omitted, the proxy accepts any HTTP or HTTPS target and the build emits a warning. + +|javascript.proxy.target +|string +|`jakarta-servlet` +|_(none)_ +|The generated ParparVM proxy deployment platform. Supported values are `jakarta-servlet` (default), `javax-servlet`, `node`, `php`, `aws-lambda`, `google-cloud-functions`, `cloudflare-workers`, and `none`. + +|javascript.proxy.url +|string +|_(none)_ +|_(none)_ +|The URL of an existing proxy to use for network requests. Setting it suppresses generated proxy packaging unless `javascript.proxy.target` is also set. If `javascript.inject_proxy` is `false`, this build hint is ignored. + +|javascript.sourceFilesCopied +|boolean +|_(none)_ +|_(none)_ +|true/false (defaults to `false`). Setting this flag to `true` will cause available java source files to be included in the resulting .zip and .war files. These may be used by Chrome during debugging. + +|javascript.stopOnErrors +|boolean +|_(none)_ +|_(none)_ +|true/false (defaults to `true`). Causes a TeaVM JavaScript build to fail when the compiler reports warnings. Setting this to `false` may allow the fallback builder to complete, but can turn compiler diagnostics into runtime failures that are more difficult to debug. + +|javascript.teavm.version +|string +|_(none)_ +|_(none)_ +|(Optional) The version of TeaVM to use for the build. *Use caution*, only use this property if you know what you're doing! + +|linux.arch +|string +|_(none)_ +|_(none)_ +| + +|linux.cc +|string +|_(none)_ +|_(none)_ +| + +|linux.debug +|boolean +|`false` +|_(none)_ +| + +|linux.libc +|string +|`glibc` +|_(none)_ +| + +|linux.musl +|boolean +|`false` +|_(none)_ +| + +|linux.muslNativeCc +|boolean +|`false` +|_(none)_ +| + +|linux.toolchain +|string +|_(none)_ +|_(none)_ +| + +|desktop.mac.cef +|boolean +|_(none)_ +|_(none)_ +|Whetherto use CEF for media or BrowserComponent instead of JavaFX in Mac desktop builds. true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a future version. + +|macNative.appCategory +|string +|`public.app-category.utilities` +|_(none)_ +|Mac Native builds only. `LSApplicationCategoryType` in the generated Info.plist. Default `public.app-category.utilities`. See https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype[Apple's category list]. + +|macNative.bundleId +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. Used only when `macNative.deriveBundleId=false`. Default: `.mac`. + +|macNative.copyright +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. `NSHumanReadableCopyright` in the Info.plist. Defaults to `Copyright (c) `. + +|macNative.deriveBundleId +|boolean +|`true` +|_(none)_ +|Mac Native builds only. `true` (default) maps to Xcode's `DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER=YES` (Xcode appends `.maccatalyst` to the iOS bundle ID). Set to `false` to take the bundle ID verbatim from `macNative.bundleId`. + +|macNative.distribution +|string +|`appStore` +|_(none)_ +|Mac Native builds only. `appStore` (default), `developerID`, or `both`. Selects which entitlements + ExportOptions plist + signing certificate to emit. `both` emits parallel `*-AppStore.entitlements` / `*-DeveloperID.entitlements` and matching `ExportOptions-*-Mac.plist` files so a single project can be archived to either channel. + +|macNative.enabled +|boolean +|`false` +|_(none)_ +| + +|macNative.entitlements.allowJit +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. `true` enables `com.apple.security.cs.allow-jit` for hardened runtime. ParparVM is AOT-compiled so this is `false` by default; flip when bundling a JIT-using cn1lib. + +|macNative.entitlements.appSandbox +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. `true` enables `com.apple.security.app-sandbox`. Default is `true` for the `appStore` channel (Mac App Store requires the sandbox), `false` for `developerID`. + +|macNative.entitlements.extra +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. Free-form XML inserted verbatim inside the `…` of the generated entitlements plist. Use for entitlements Codename One doesn't expose individually. + +|macNative.entitlements.files.userSelected +|string +|`readwrite` +|_(none)_ +|Mac Native builds only. `readwrite` (default), `readonly`, or `none`. Sets the matching `com.apple.security.files.user-selected.*` entitlement. + +|macNative.entitlements.hardenedRuntime +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. `true` enables hardened runtime restrictions. Default is `true` for `developerID` (notarization requires it), `false` for `appStore`. + +|macNative.entitlements.network.client +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. Toggles `com.apple.security.network.client`. Default `true`. + +|macNative.entitlements.network.server +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. Toggles `com.apple.security.network.server`. Default `false`. + +|macNative.fixedWindowSize +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. Opt-in. Format `x` — for example `1024x685`. When set, the Catalyst window's `UISceneSession.sizeRestrictions` minimum and maximum are pinned to the requested size so every launch produces a byte-identical window. Default unset, in which case the window is resizable. The CI screenshot pipeline turns this on to keep the strict-pixel golden comparison stable; production apps should leave it off. + +|macNative.iosMinDeploymentTarget +|version +|`13.1` +|_(none)_ +|Mac Native builds only. iOS deployment-target floor for the Catalyst slice (`IPHONEOS_DEPLOYMENT_TARGET`). Default `13.1`. The plugin coerces the iOS slice's minimum upward when set. + +|macNative.minDeploymentTarget +|version +|`10.15` +|_(none)_ +|Mac Native builds only. Minimum macOS version (`MACOSX_DEPLOYMENT_TARGET`). Default `10.15` — earlier versions don't support Mac Catalyst. + +|macNative.notarize +|boolean +|`false` +|_(none)_ +| + +|macNative.notarize.appleId +|string +|_(none)_ +|_(none)_ +| + +|macNative.notarize.keychainProfile +|string +|_(none)_ +|_(none)_ +| + +|macNative.notarize.password +|string +|_(none)_ +|_(none)_ +| + +|macNative.notarize.teamId +|string +|_(none)_ +|_(none)_ +| + +|macNative.provisioningProfile.* +|string +|_(none)_ +|_(properties file only)_ +|Per-profile provisioning data for a native macOS build, keyed by profile name. + +|macNative.provisioningProfile.appStore +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. Provisioning profile name for App Store distribution — used only when `macNative.signing.style=manual`. + +|macNative.provisioningProfile.developerID +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. Provisioning profile name for Developer ID distribution — used only when `macNative.signing.style=manual`. + +|macNative.signing.style +|string +|`automatic` +|_(none)_ +|Mac Native builds only. `automatic` (default) lets Xcode pick the signing certificate; `manual` forces the certificate identity hints below to be respected verbatim. + +|macNative.signingIdentity.appStore +|string +|`Apple Distribution` +|_(none)_ +|Mac Native builds only. Signing certificate identity for the App Store channel. Default `Apple Distribution`. + +|macNative.signingIdentity.developerID +|string +|`Developer ID Application` +|_(none)_ +|Mac Native builds only. Signing certificate identity for the Developer ID channel. Default `Developer ID Application`. + +|macNative.teamId +|string +|_(none)_ +|_(none)_ +|Mac Native builds only. Apple Developer Team ID (alphanumeric). Falls back to `ios.release.teamId` → `ios.teamId` → `ios.debug.teamId` since most apps share a single Apple Developer Team for iOS and Mac. + +|tvNative.bundleId +|string +|_(none)_ +|_(none)_ +|Bundle identifier of the tvOS app. Defaults to `.tvos`. + +|tvNative.displayName +|string +|_(none)_ +|_(none)_ +|The tvOS app name shown on Apple TV. Defaults to the app's display name. + +|tvNative.enabled +|boolean +|`false` +|_(none)_ +|true/false (defaults to false). Adds an Apple TV (tvOS) application target to the iOS build. The tvOS app is a separate `appletvos` target built from the same Java/Kotlin sources through ParparVM (UIKit + Metal; tvOS has no OpenGL ES). Enabling it doesn't change the iOS app -- in particular it doesn't override the iOS app's `ios.metal` setting. Also turned on implicitly by `codename1.tvMain`. + +|tvNative.mainClass +|string +|_(none)_ +|_(none)_ +| + +|tvNative.minDeploymentTarget +|version +|`13.0` +|_(none)_ +|`TVOS_DEPLOYMENT_TARGET` for the tvOS target. Defaults to `13.0`. + +|tvNative.teamId +|string +|_(none)_ +|_(none)_ +|Apple Developer Team ID used to sign the tvOS target. Falls back to the iOS team id (`ios.release.teamId` / `ios.teamId` / `ios.debug.teamId`). + +|watchNative.enabled +|boolean +|`false` +|_(none)_ +| + +|watchNative.health +|string +|_(none)_ +|_(none)_ +| + +|watchNative.health.workoutProcessing +|boolean +|`false` +|_(none)_ +| + +|watchNative.mainClass +|string +|_(none)_ +|_(none)_ +| + +|win.desktop-vm +|string +|_(none)_ +|_(none)_ +|The JVM that should be bundled in the Windows desktop build. Windows desktop builds only. Supported values: zulu8, zuluFx8, zulu8-32bit, zuluFx8-32bit, zulu11, zuluFx11, zulu11-32bit, zuluFx11-32bit + +|win.installDirName +|string +|_(none)_ +|_(none)_ +|Windows desktop builds only. Overrides the default installation folder name suggested by the installer (under `Program Files`). Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly installation folder name (for example, `win.installDirName=My Application`). The application ID used by Windows for upgrade detection is unaffected, so existing installations continue to upgrade. + +|win.shortcutName +|string +|_(none)_ +|_(none)_ +|Windows desktop builds only. Overrides the name used for the Start Menu shortcut, the Desktop shortcut and (when `win.launchOnStart=true`) the autostart shortcut. Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly shortcut label (for example, `win.shortcutName=My Application`). + +|win.vm32bit +|boolean +|_(none)_ +|_(none)_ +|true/false (defaults to false). Forces windows desktop builds to use the Win32 JVM instead of the 64 bit VM making them compatible with older Windows Machines. This is off by default at the moment because of a bug in JDK 8 update 112 that might cause this to fail for some cases + +|windows.arch +|string +|_(none)_ +|_(none)_ +|Native Windows port only (the `windows-native` build target -- not the JVM `win.*` desktop hints above). Target CPU architecture for the standalone `.exe`: `x64` (the default) or `arm64`. Accepts the usual synonyms (`x86_64`/`amd64`, `aarch64`). clang-cl cross-compiles to the chosen architecture from either host. See the link:#_working_with_the_native_windows_port[Working with the native Windows port chapter]. + +|windows.calendar.restrictedCapability +|boolean +|`false` +|_(none)_ +| + +|windows.debug +|boolean +|`false` +|_(none)_ +|Native Windows port only. true/false (defaults to false). When `false` the `.exe` is built optimized and *stripped* -- no PDB, dead-stripped unreferenced code (`/OPT:REF`) and folded identical functions (`/OPT:ICF`) -- which is the shipping default. Set `true` to keep debug symbols (a `.pdb` next to the exe, via `RelWithDebInfo` / clang-cl `/Zi` + linker `/DEBUG`) so a native crash address can be symbolized during development. Optimizations stay on in both cases. + +|windows.extensions +|string +|_(none)_ +|_(none)_ +|Historical build hint for the discontinued UWP target. It's retained here only for legacy reference and isn't used by current supported build targets. + +|windows.msix +|boolean +|`false` +|_(none)_ +| + +|windows.msix.identityName +|string +|_(none)_ +|_(none)_ +| + +|windows.msix.password +|string +|_(none)_ +|_(none)_ +| + +|windows.msix.pfx +|string +|_(none)_ +|_(none)_ +| + +|windows.msix.publisher +|string +|_(none)_ +|_(none)_ +| + +|windows.msix.version +|string +|_(none)_ +|_(none)_ +| + +|windows.sdkRoot +|string +|_(none)_ +|_(none)_ +|Native Windows port only; used when building on a *non-Windows* host (for example a Linux build server). Path to a Windows SDK laid out by https://github.com/Jake-Shadle/xwin[`xwin splat`] (a directory containing `crt/include` and `sdk/include/um`), used to cross-compile the `.exe` with clang-cl + lld-link instead of a Visual Studio environment. If unset, the `CN1_XWIN_SYSROOT` environment variable is used. Ignored on Windows hosts, which build through Visual Studio. The same SDK serves both `windows.arch` targets (its `x86_64` / `aarch64` lib subdirs). + +|windows.signing +|boolean +|`true` +|_(none)_ +|Native Windows port only. `true`/`false` (default `true`). Set `false` to force an unsigned build even when a certificate is available. + +|windows.signing.digest +|string +|`sha256` +|_(none)_ +|Native Windows port only. Signature digest algorithm. Default `sha256`. + +|windows.signing.name +|string +|_(none)_ +|_(none)_ +| + +|windows.signing.password +|string +|_(none)_ +|_(none)_ +| + +|windows.signing.pkcs12 +|string +|_(none)_ +|_(none)_ +| + +|windows.signing.timestampUrl +|string +|`http://timestamp.digicert.com` +|_(none)_ +|Native Windows port only. RFC 3161 timestamp server used when signing, so the signature stays valid after the certificate expires. Default `http://timestamp.digicert.com`; set empty to disable timestamping. + +|windows.signing.url +|string +|_(none)_ +|_(none)_ +| + +|=== diff --git a/maven/build-hint-catalog/pom.xml b/maven/build-hint-catalog/pom.xml new file mode 100644 index 00000000000..24045b96c83 --- /dev/null +++ b/maven/build-hint-catalog/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + com.codenameone + codenameone + 8.0-SNAPSHOT + + codenameone-build-hint-catalog + Codename One Build Hint Catalog + Shared registry describing every Codename One build hint: type, default, value domain and merge semantics + + 1.8 + 1.8 + UTF-8 + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + diff --git a/maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties b/maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties index 8f0bf0d6d5e..33d9b8ef65d 100644 --- a/maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties +++ b/maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties @@ -4,17 +4,14 @@ codename1.android.keystore= codename1.android.keystoreAlias= codename1.android.keystorePassword= -codename1.arg.ios.newStorageLocation=true -# Modern native themes (iOS liquid-glass + Material 3) - opt-in. -codename1.arg.nativeTheme=modern -codename1.arg.ios.themeMode=modern -codename1.arg.and.themeMode=modern -# Desktop integration (only takes effect when the app runs on the desktop). titleBar mode is -# one of: native (OS title bar + native menu bar), custom (undecorated, CN1-drawn title bar) or -# toolbar (legacy in-app CN1 Toolbar). interactiveScrollbars enables grab-able, click-to-page -# desktop scrollbars. These are honored by the generated desktop Stub. -codename1.arg.desktop.titleBar=native -codename1.arg.desktop.interactiveScrollbars=true +# Build hints are now declared as annotations on the main class, where the +# compiler checks them -- see the @Ios / @Android / @Desktop / @Build +# annotations on ${mainName}. Setting the same hint here as well is a build +# error, so move a hint rather than copying it. +# +# java.version stays here on purpose: it selects the toolchain that compiles +# the very class the annotations live on, and the project generator resolves it +# before any code is compiled. codename1.arg.java.version=${javaVersion} codename1.displayName=${mainName} codename1.icon=icon.png @@ -35,6 +32,11 @@ codename1.ios.release.provision= # See the "On-Device Debugging (iOS)" chapter of the developer guide # for the full setup (the IntelliJ Run/Debug configs that come with # this project's .idea/ directory are wired against these hints). +# +# These have a checked form too. On the main class: +# @OnDeviceDebug(ios = true, iosProxyHost = "127.0.0.1", iosProxyPort = 55333) +# and iosWaitForAttach = true to block at boot until the debugger attaches. +# Use one form or the other -- declaring a hint in both places fails the build. #codename1.arg.ios.onDeviceDebug=true #codename1.arg.ios.onDeviceDebug.proxyHost=127.0.0.1 #codename1.arg.ios.onDeviceDebug.proxyPort=55333 @@ -47,7 +49,8 @@ codename1.ios.release.provision= # bundled with this project, or with the cn1:android-on-device-debugging # Maven goal. See the "On-Device Debugging (Android)" chapter of the # developer guide for the wireless-debugging instructions and the full -# adb flow. +# adb flow. The checked form is @OnDeviceDebug(android = true) on the +# main class; use one form or the other, not both. #codename1.arg.android.onDeviceDebug=true codename1.j2me.nativeTheme=nbproject/nativej2me.res codename1.kotlin=false diff --git a/maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java b/maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java index a415a02bd87..44d6a1a1b39 100644 --- a/maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java +++ b/maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java @@ -4,6 +4,7 @@ package ${package}; import static com.codename1.ui.CN.*; +import com.codename1.annotations.buildhints.*; import com.codename1.system.Lifecycle; import com.codename1.ui.*; import com.codename1.ui.layouts.*; @@ -14,7 +15,19 @@ /** * This file was generated by Codename One for the purpose * of building native mobile applications using Java. + * + *

The annotations below are build hints: settings the native build reads, + * written so the compiler checks them. A misspelled name is an unknown symbol + * and an unsupported value is an unknown enum constant, rather than a line in + * codenameone_settings.properties that is silently ignored. Hints that have no + * annotation yet, and open-ended ones such as android.permission.<NAME>, + * still go in that file; setting the same hint in both places is a build + * error.

*/ +@Ios(newStorageLocation = true, themeMode = IosThemeMode.MODERN) +@Android(themeMode = AndroidThemeMode.MODERN) +@Desktop(titleBar = DesktopTitleBar.NATIVE, interactiveScrollbars = true) +@Build(nativeTheme = NativeThemeMode.MODERN) public class ${mainName} extends Lifecycle { @Override public void runApp() { diff --git a/maven/codenameone-maven-plugin/pom.xml b/maven/codenameone-maven-plugin/pom.xml index aff7d0c4312..1a048926888 100644 --- a/maven/codenameone-maven-plugin/pom.xml +++ b/maven/codenameone-maven-plugin/pom.xml @@ -39,6 +39,11 @@ codenameone-platform-feature-catalog ${project.version} + + ${project.groupId} + codenameone-build-hint-catalog + ${project.version} + org.jdom jdom2 diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index df9c6698b1e..8c01a5432f7 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -1481,6 +1481,13 @@ private void createAntProject() throws IOException, LibraryPropertiesException, try (FileInputStream fis = new FileInputStream(codenameOneSettingsCopy)) { cn1SettingsProps.load(fis); } + // Build hints declared as annotations on the main class. Merged here, before + // everything that consumes the effective configuration: the command-line + // overlay below (so -D still wins), the CN1Lib appended/required merges (so a + // library appends onto an annotation-supplied value exactly as it would onto a + // file-supplied one), the gradle sanity check, both preflights, and the copy + // that is written back out and uploaded. + mergeAnnotationBuildHints(cn1SettingsProps, cpElements); // The build request is assembled from this copy, not from the mojo's // own properties, so the command-line overlay has to be applied here // too -- otherwise a hint passed with -D is read by the mojo and still @@ -2521,4 +2528,104 @@ private SortedProperties mergeRequiredProperties(String libraryName, Properties return merged; } + + /** + * Name of the resource {@code BuildHintAnnotationProcessor} emits into + * {@code target/classes} at PROCESS_CLASSES. + */ + private static final String ANNOTATION_HINTS_RESOURCE = + "META-INF/codenameone/build-hints.properties"; + + /** + * Overlays the build hints that came from annotations onto the settings the + * build request is assembled from. + * + *

Read from the compile classpath rather than from the staged fat jar. + * {@code mergeJars} builds that jar with Ant's {@code Zip} in update mode, + * which adds and overwrites entries but never removes one, and the jar is + * reused when it is not stale — so a project that had its annotations + * deleted would keep shipping yesterday's hints. The classpath directory is + * written by the annotation processor on every build and deleted by it when + * the last annotation goes away, so it always reflects the current source.

+ */ + private void mergeAnnotationBuildHints(Properties target, List classpathElements) { + if (target == null || classpathElements == null) { + return; + } + String expectedMain = null; + if (properties != null) { + String main = properties.getProperty("codename1.mainName"); + String pkg = properties.getProperty("codename1.packageName"); + if (main != null && main.trim().length() > 0) { + expectedMain = (pkg == null || pkg.trim().length() == 0) + ? main.trim() : pkg.trim() + "." + main.trim(); + } + } + for (String element : classpathElements) { + Properties found = readAnnotationHints(new File(element)); + if (found == null) { + continue; + } + String stamped = found.getProperty("cn1.buildHints.mainClass"); + if (expectedMain != null && stamped != null && !expectedMain.equals(stamped)) { + // A cn1lib that bound the goal itself, or a stale artifact. Merging it + // would apply another project's build configuration to this one. + getLog().debug("cn1: ignoring build hints from " + element + + " -- they were generated for " + stamped); + continue; + } + int applied = 0; + for (String key : found.stringPropertyNames()) { + if (!key.startsWith("codename1.arg.")) { + continue; + } + target.setProperty(key, found.getProperty(key)); + applied++; + } + if (applied > 0) { + getLog().info("cn1: applied " + applied + " build hint(s) from annotations"); + return; + } + } + } + + /** + * Reads the emitted hints out of a classpath element, which is either the + * module's output directory or a jar. + * + * @return the properties, or null when this element carries none + */ + private Properties readAnnotationHints(File element) { + if (element == null || !element.exists()) { + return null; + } + try { + if (element.isDirectory()) { + File f = new File(element, ANNOTATION_HINTS_RESOURCE); + if (!f.isFile()) { + return null; + } + try (FileInputStream in = new FileInputStream(f)) { + Properties p = new Properties(); + p.load(in); + return p; + } + } + try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(element)) { + java.util.zip.ZipEntry entry = zip.getEntry(ANNOTATION_HINTS_RESOURCE); + if (entry == null) { + return null; + } + try (InputStream in = zip.getInputStream(entry)) { + Properties p = new Properties(); + p.load(in); + return p; + } + } + } catch (IOException ex) { + getLog().warn("cn1: could not read build hints from " + element + ": " + + ex.getMessage()); + return null; + } + } } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/LibraryHintMerger.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/LibraryHintMerger.java index afc9afa70fb..3649854a508 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/LibraryHintMerger.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/LibraryHintMerger.java @@ -22,8 +22,8 @@ */ package com.codename1.maven; -import java.util.HashMap; -import java.util.Map; +import com.codename1.build.shared.BuildHints; + import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -56,46 +56,6 @@ public class LibraryHintMerger { /** Prefix every build hint carries inside a settings/library properties file. */ private static final String ARG_PREFIX = "codename1.arg."; - /** - * Separator per hint, keyed by the name with {@link #ARG_PREFIX} stripped. - * - *

Read off how the builders themselves split or append each value, not invented here: - * {@code IPhoneBuilder} splits {@code ios.pods} and {@code ios.applicationQueriesSchemes} - * on {@code ","} and joins {@code ios.add_libs} with {@code ";"}, while every - * {@code AndroidGradleBuilder} injection into a Gradle text hint appends a newline-wrapped - * statement. A hint absent from this map keeps the historical bare concatenation, which is - * what the XML-fragment hints want.

- */ - private static final Map SEPARATORS = new HashMap(); - static { - // A Gradle dependency list. ';' rather than a newline because this is the hint - // users hand-edit most, every existing project and cn1lib already writes it that - // way, and keeping the value on one line survives any line-oriented tooling that - // rewrites codenameone_settings.properties. - SEPARATORS.put("android.gradleDep", ";"); - // Block structured Gradle text, where a statement per line is the only thing that - // reads correctly -- and what our own builder injections already append. - SEPARATORS.put("gradleDependencies", "\n"); - SEPARATORS.put("android.topDependency", "\n"); - SEPARATORS.put("android.repositories", "\n"); - SEPARATORS.put("android.xgradle", "\n"); - SEPARATORS.put("android.gradle.androidx", "\n"); - SEPARATORS.put("android.xgradle_default_config", "\n"); - SEPARATORS.put("android.gradlePlugin", "\n"); - SEPARATORS.put("android.supportv4Dep", "\n"); - // ProGuard/R8 directives are line oriented. - SEPARATORS.put("android.proguardKeep", "\n"); - // Comma-delimited lists. - SEPARATORS.put("ios.pods", ","); - SEPARATORS.put("ios.applicationQueriesSchemes", ","); - // Semicolon-delimited lists. - SEPARATORS.put("ios.add_libs", ";"); - // Attributes spliced into a single XML tag, so they abut with a space rather than - // directly -- android:allowBackup="false"android:hardwareAccelerated="true" is not - // a well formed tag. - SEPARATORS.put("android.xapplication_attr", " "); - } - private LibraryHintMerger() { } @@ -103,6 +63,10 @@ private LibraryHintMerger() { * The separator two values of this hint must be joined with, or an empty string when the * hint's values abut directly (the XML-fragment hints). * + *

The table lives in {@link BuildHints}, which is also what the build hint annotations + * are generated from. Keeping one copy is what stops a {@code String[]} attribute being + * joined with one delimiter here and split with another by the builder.

+ * * @param propertyName hint name, with or without the {@code codename1.arg.} prefix * @return the separator, never null */ @@ -113,8 +77,7 @@ public static String separatorFor(String propertyName) { String name = propertyName.startsWith(ARG_PREFIX) ? propertyName.substring(ARG_PREFIX.length()) : propertyName; - String separator = SEPARATORS.get(name); - return separator == null ? "" : separator; + return BuildHints.separatorFor(name); } /** diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java new file mode 100644 index 00000000000..a9f7ab5280e --- /dev/null +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -0,0 +1,505 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven; + +import com.codename1.build.shared.BuildHints; +import com.codename1.build.shared.HintType; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; + +/** + * Rewrites {@code codename1.arg.*} lines in {@code codenameone_settings.properties} + * as build hint annotations on the application's main class. + * + *

A build hint written as a properties line is a string nothing checks: a + * misspelled name is accepted, never read, and silently does nothing. The same + * hint written as an annotation is checked by the compiler. This goal moves the + * ones that have an annotation and leaves the rest alone.

+ * + *

Runs in place and prints what it did. Pass {@code -Dcn1.migrate.dryRun=true} + * to see the plan without touching anything.

+ */ +// Aggregator: every module of a Codename One project resolves to the same +// codenameone_settings.properties, so running per-module would try to migrate +// the same file several times and the second pass would find its own output. +@Mojo(name = "migrate-build-hints", requiresProject = true, aggregator = true, + requiresDependencyResolution = ResolutionScope.COMPILE) +public class MigrateBuildHintsMojo extends AbstractCN1Mojo { + + /** + * The whole reactor. This goal is an aggregator, so {@code project} is the + * root pom -- which carries no codenameone-core dependency of its own. The + * core has to be looked for across the modules. + */ + @Parameter(defaultValue = "${session.projects}", readonly = true, required = true) + private java.util.List reactorProjects; + + /** Report what would change without writing anything. */ + @Parameter(property = "cn1.migrate.dryRun", defaultValue = "false") + private boolean dryRun; + + /** + * Hints to leave in the properties file even though they have an annotation. + * {@code java.version} is always kept: it selects the toolchain that compiles + * the class the annotations would live on, so it has to be readable before + * any of the project's own code exists. + */ + @Parameter(property = "cn1.migrate.keep") + private String keep; + + @Override + protected void executeImpl() throws MojoExecutionException, MojoFailureException { + File projectDir = getCN1ProjectDir(); + if (projectDir == null) { + throw new MojoExecutionException("No Codename One project directory found; " + + "this goal must run in a project with a codenameone_settings.properties."); + } + File settingsFile = new File(projectDir, "codenameone_settings.properties"); + if (!settingsFile.isFile()) { + throw new MojoExecutionException("No codenameone_settings.properties in " + projectDir); + } + + // The annotations ship in codenameone-core. A project pinned to a release + // that predates them would migrate cleanly here and then fail to compile, + // so refuse rather than hand back a broken project. + if (!coreHasBuildHintAnnotations()) { + throw new MojoFailureException("This project builds against a Codename One version " + + "whose core has no com.codename1.annotations.buildhints package, so the " + + "annotations would not resolve. Update the project's cn1.version first."); + } + + Properties settings = new Properties(); + try (FileInputStream in = new FileInputStream(settingsFile)) { + settings.load(in); + } catch (IOException ex) { + throw new MojoExecutionException("Could not read " + settingsFile, ex); + } + + List kept = new ArrayList(); + kept.add("java.version"); + if (keep != null) { + for (String k : keep.split(",")) { + if (k.trim().length() > 0) { + kept.add(k.trim()); + } + } + } + + // Resolve the target language before rendering: Kotlin writes an array + // literal as [a, b] and rejects Java's {a, b}, so the same hint renders + // differently depending on which file it is going into. + String mainSourcePath = findMainClassSource(projectDir, settings); + boolean kotlinTarget = mainSourcePath != null && mainSourcePath.endsWith(".kt"); + + // annotation simple name -> attribute -> source literal + Map> plan = new TreeMap>(); + List migratedKeys = new ArrayList(); + List skipped = new ArrayList(); + + for (String key : new ArrayList(settings.stringPropertyNames())) { + if (!key.startsWith(BuildHints.ARG_PREFIX)) { + continue; + } + String name = key.substring(BuildHints.ARG_PREFIX.length()); + if (kept.contains(name)) { + skipped.add(name + " (kept by configuration)"); + continue; + } + BuildHints.Hint hint = BuildHints.byName(name); + if (hint == null || !hint.isAnnotated()) { + skipped.add(name + " (no annotation for this hint yet)"); + continue; + } + String literal = toSourceLiteral(hint, settings.getProperty(key), kotlinTarget); + if (literal == null) { + skipped.add(name + " = '" + settings.getProperty(key) + + "' (value is outside the hint's supported set)"); + continue; + } + String annotation = hint.group().annotationSimpleName(); + Map members = plan.get(annotation); + if (members == null) { + members = new TreeMap(); + plan.put(annotation, members); + } + members.put(hint.attr(), literal); + migratedKeys.add(key); + } + + if (plan.isEmpty()) { + getLog().info("cn1: nothing to migrate -- no annotated build hint is set in " + + settingsFile.getName()); + for (String s : skipped) { + getLog().debug("cn1: left in place: " + s); + } + return; + } + + String mainSource = mainSourcePath; + StringBuilder rendered = new StringBuilder(); + for (Map.Entry> e : plan.entrySet()) { + rendered.append(render(e.getKey(), e.getValue())).append('\n'); + } + + getLog().info("cn1: move these onto " + (mainSource == null ? "your main class" + : new File(mainSource).getName()) + ":"); + for (String line : rendered.toString().split("\n")) { + getLog().info("cn1: " + line); + } + for (String s : skipped) { + getLog().info("cn1: leaving " + s); + } + + if (dryRun) { + getLog().info("cn1: dry run -- nothing written"); + return; + } + if (mainSource == null) { + throw new MojoFailureException("Could not find the source of the main class named by " + + "codename1.mainName. Add the annotations above by hand, then delete the " + + "migrated lines from " + settingsFile.getName() + "."); + } + + try { + insertAnnotations(new File(mainSource), rendered.toString(), + settings.getProperty("codename1.mainName", "").trim()); + removeMigratedLines(settingsFile, migratedKeys); + } catch (IOException ex) { + throw new MojoExecutionException("Migration failed: " + ex.getMessage(), ex); + } + getLog().info("cn1: migrated " + migratedKeys.size() + " build hint(s) into " + + new File(mainSource).getName()); + } + + /** + * Whether the codenameone-core on this project's compile classpath actually + * carries the annotations. + */ + private boolean coreHasBuildHintAnnotations() { + java.util.List projects = reactorProjects; + if (projects == null || projects.isEmpty()) { + projects = java.util.Collections.singletonList(project); + } + for (org.apache.maven.project.MavenProject p : projects) { + if (carriesBuildHintAnnotations(p)) { + return true; + } + } + return false; + } + + private boolean carriesBuildHintAnnotations(org.apache.maven.project.MavenProject p) { + try { + for (Object element : p.getCompileClasspathElements()) { + File f = new File((String) element); + if (f.isDirectory()) { + if (new File(f, "com/codename1/annotations/buildhints/Ios.class").isFile()) { + return true; + } + } else if (f.isFile()) { + try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(f)) { + if (zip.getEntry("com/codename1/annotations/buildhints/Ios.class") != null) { + return true; + } + } + } + } + } catch (Exception ex) { + getLog().debug("cn1: could not inspect the compile classpath: " + ex.getMessage()); + return true; + } + return false; + } + + /** + * Renders a value as the Java literal for its attribute. + * + * @return the literal, or null when the value is outside a closed domain -- + * which is worth reporting rather than silently translating, because + * it means the properties file has been setting something the build + * never understood + */ + String toSourceLiteral(BuildHints.Hint hint, String value, boolean kotlin) { + if (value == null) { + return null; + } + String v = value.trim(); + switch (hint.type()) { + case BOOLEAN: + if ("true".equalsIgnoreCase(v)) return "true"; + if ("false".equalsIgnoreCase(v)) return "false"; + return null; + case INT: + try { + return String.valueOf(Integer.parseInt(v)); + } catch (NumberFormatException ex) { + return null; + } + case ENUM: + for (String allowed : hint.values()) { + if (allowed.equalsIgnoreCase(v)) { + return hint.enumName() + "." + enumConstant(allowed); + } + } + return null; + case STRING_LIST: { + String sep = hint.separator(); + if (sep == null || sep.length() == 0) { + return quote(v); + } + String[] parts = v.split(java.util.regex.Pattern.quote(sep), -1); + StringBuilder sb = new StringBuilder(kotlin ? "[" : "{"); + int written = 0; + for (String part : parts) { + String t = part.trim(); + if (t.length() == 0) { + continue; + } + if (written++ > 0) { + sb.append(", "); + } + sb.append(quote(t)); + } + return sb.append(kotlin ? ']' : '}').toString(); + } + default: + return quote(v); + } + } + + /** Mirrors the generator's wire-value to constant-name mapping. */ + static String enumConstant(String wire) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < wire.length(); i++) { + char c = wire.charAt(i); + if (Character.isUpperCase(c) && sb.length() > 0 + && Character.isLowerCase(wire.charAt(i - 1))) { + sb.append('_'); + } + sb.append(Character.isLetterOrDigit(c) ? Character.toUpperCase(c) : '_'); + } + String out = sb.toString(); + return out.length() > 0 && Character.isDigit(out.charAt(0)) ? "V" + out : out; + } + + private static String quote(String s) { + StringBuilder sb = new StringBuilder("\""); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '"': sb.append("\\\""); break; + case '\\': sb.append("\\\\"); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: sb.append(c); + } + } + return sb.append('"').toString(); + } + + static String render(String annotation, Map members) { + StringBuilder sb = new StringBuilder("@").append(annotation).append('('); + int i = 0; + for (Map.Entry e : members.entrySet()) { + if (i++ > 0) { + sb.append(", "); + } + sb.append(e.getKey()).append(" = ").append(e.getValue()); + } + return sb.append(')').toString(); + } + + private String findMainClassSource(File projectDir, Properties settings) { + String main = settings.getProperty("codename1.mainName"); + String pkg = settings.getProperty("codename1.packageName"); + if (main == null || main.trim().length() == 0) { + return null; + } + String path = (pkg == null ? "" : pkg.trim().replace('.', File.separatorChar) + + File.separator) + main.trim(); + String[] roots = {"src" + File.separator + "main" + File.separator + "java", + "src" + File.separator + "main" + File.separator + "kotlin", + "src"}; + String[] extensions = {".java", ".kt"}; + for (String root : roots) { + for (String ext : extensions) { + File f = new File(projectDir, root + File.separator + path + ext); + if (f.isFile()) { + return f.getAbsolutePath(); + } + } + } + return null; + } + + /** + * Splices the annotations in above the class declaration, with the import. + * + *

Textual rather than a parse: the file may be Java or Kotlin, it may use + * any formatting, and rewriting it through a parser would reformat code the + * developer did not ask to have touched.

+ */ + private void insertAnnotations(File source, String annotations, String simpleName) + throws IOException { + String text = read(source); + boolean kotlin = source.getName().endsWith(".kt"); + String importLine = kotlin + ? "import com.codename1.annotations.buildhints.*" + : "import com.codename1.annotations.buildhints.*;"; + if (text.contains("com.codename1.annotations.buildhints")) { + throw new IOException(source.getName() + " already imports the build hint " + + "annotations; migrate the remaining hints by hand so nothing is " + + "overwritten."); + } + + int declaration = classDeclarationIndex(text, kotlin, simpleName); + if (declaration < 0) { + throw new IOException("Could not find the class declaration in " + source.getName()); + } + String head = text.substring(0, declaration); + String tail = text.substring(declaration); + + int lastImport = head.lastIndexOf("\nimport "); + if (lastImport >= 0) { + int eol = head.indexOf('\n', lastImport + 1); + head = head.substring(0, eol + 1) + importLine + "\n" + head.substring(eol + 1); + } else { + int pkgEnd = head.indexOf('\n', head.indexOf("package ")); + head = head.substring(0, pkgEnd + 1) + "\n" + importLine + "\n" + + head.substring(pkgEnd + 1); + } + write(source, head + annotations + tail); + } + + /** + * Index of the start of the line declaring the top-level type. + * + *

Matched by pattern rather than against a list of prefixes: a declaration + * can carry any combination of modifiers -- {@code public final class}, + * {@code internal data class} -- and a missing combination would abort the + * migration on a perfectly ordinary file. Anchored to column zero so a + * nested type or a mention inside an indented doc comment cannot match, and + * the type named by {@code codename1.mainName} is preferred over whatever + * happens to appear first.

+ */ + static int classDeclarationIndex(String text, boolean kotlin, String simpleName) { + String modifiers = kotlin + ? "(?:public |internal |private |open |abstract |final |sealed |data |value |annotation )*" + : "(?:public |protected |private |abstract |final |static |strictfp |sealed |non-sealed )*"; + String kinds = kotlin ? "(?:class|object|interface)" : "(?:class|interface|enum|record)"; + java.util.regex.Pattern named = java.util.regex.Pattern.compile( + "(?m)^" + modifiers + kinds + "\\s+" + + java.util.regex.Pattern.quote(simpleName == null ? "" : simpleName) + + "\\b"); + java.util.regex.Matcher m = named.matcher(text); + if (simpleName != null && simpleName.length() > 0 && m.find()) { + return m.start(); + } + java.util.regex.Matcher any = java.util.regex.Pattern.compile( + "(?m)^" + modifiers + kinds + "\\s+\\w").matcher(text); + return any.find() ? any.start() : -1; + } + + /** + * Deletes the migrated lines, leaving every other line -- comments, + * ordering, unrelated settings -- byte for byte as it was. + */ + private void removeMigratedLines(File settingsFile, List keys) throws IOException { + List lines = new ArrayList(); + BufferedReader r = new BufferedReader( + new InputStreamReader(new FileInputStream(settingsFile), "ISO-8859-1")); + try { + String line; + while ((line = r.readLine()) != null) { + lines.add(line); + } + } finally { + r.close(); + } + Map wanted = new LinkedHashMap(); + for (String k : keys) { + wanted.put(k, Boolean.TRUE); + } + StringBuilder out = new StringBuilder(); + for (String line : lines) { + String t = line.trim(); + boolean drop = false; + if (t.length() > 0 && t.charAt(0) != '#' && t.charAt(0) != '!') { + int eq = t.indexOf('='); + int colon = t.indexOf(':'); + int split = eq < 0 ? colon : (colon < 0 ? eq : Math.min(eq, colon)); + if (split > 0 && wanted.containsKey(t.substring(0, split).trim())) { + drop = true; + } + } + if (!drop) { + out.append(line).append('\n'); + } + } + write(settingsFile, out.toString()); + } + + private static String read(File f) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); + try { + int c; + while ((c = r.read()) >= 0) { + sb.append((char) c); + } + } finally { + r.close(); + } + return sb.toString(); + } + + private static void write(File f, String content) throws IOException { + Writer w = new OutputStreamWriter(new FileOutputStream(f), "UTF-8"); + try { + w.write(content); + } finally { + w.close(); + } + } +} diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java index 3e8e426c683..f873958bda6 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java @@ -231,13 +231,15 @@ File extractSettingsIcon(File jar, File runtimeDir) { void writeBinding(File inputFile, File projectDir) throws MojoExecutionException { File root = multimoduleRoot(projectDir); - File buildHints = new File(root, "docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc"); + // No buildHintsDoc: the Settings tool used to scrape the developer guide's + // AsciiDoc table at runtime and guess each hint's type from its description + // prose. It now reads com.codename1.build.shared.BuildHints, the same table + // the build hint annotations are generated from. String content = "# Codename One Settings project binding\n" + "projectDir=" + projectDir.getAbsolutePath() + "\n" + "settings=" + new File(projectDir, "codenameone_settings.properties").getAbsolutePath() + "\n" + "pom=" + new File(projectDir, "pom.xml").getAbsolutePath() + "\n" - + "multimoduleRoot=" + root.getAbsolutePath() + "\n" - + (buildHints.isFile() ? "buildHintsDoc=" + buildHints.getAbsolutePath() + "\n" : ""); + + "multimoduleRoot=" + root.getAbsolutePath() + "\n"; try { FileUtils.write(inputFile, content, StandardCharsets.UTF_8); } catch (IOException ex) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java index c7829d15e5a..6d2cdb575b9 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java @@ -35,13 +35,16 @@ import org.apache.maven.plugins.annotations.Parameter; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; @@ -103,7 +106,7 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException } ProcessorContext ctx = new ProcessorContext(outputDirectory, stubSourceDirectory, - index, getLog()); + index, getLog(), getCN1ProjectDir(), rawProjectSettings(), mainClassBinaryName()); // start() for (Iterator it = processors.iterator(); it.hasNext(); ) { @@ -238,4 +241,55 @@ private List loadProcessors() { for (AnnotationProcessor p : sl) out.add(p); return Collections.unmodifiableList(out); } + + /// Loads `codenameone_settings.properties` exactly as it sits on disk. + /// + /// Deliberately not the inherited `properties` field: that one has the + /// `-D` command line overlaid on top of it, and a hint passed with `-D` is + /// the documented way to override one for a single build. A processor that + /// compared annotations against the overlaid view would report a conflict + /// for the one case that is supposed to win. + private Properties rawProjectSettings() { + File f = getProjectPropertiesFile(); + if (f == null || !f.exists()) { + return null; + } + Properties p = new Properties(); + InputStream in = null; + try { + in = new FileInputStream(f); + p.load(in); + } catch (IOException ex) { + getLog().warn("cn1: could not read " + f + ": " + ex.getMessage()); + return null; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + // nothing useful to do on close failure of a read-only stream + } + } + } + return p; + } + + /// `codename1.packageName` + `codename1.mainName`, or null when the project + /// declares no main class. + private String mainClassBinaryName() { + Properties p = rawProjectSettings(); + if (p == null) { + return null; + } + String main = p.getProperty("codename1.mainName"); + String pkg = p.getProperty("codename1.packageName"); + if (main == null || main.trim().length() == 0) { + return null; + } + main = main.trim(); + if (pkg == null || pkg.trim().length() == 0) { + return main; + } + return pkg.trim() + "." + main; + } } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ProcessorContext.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ProcessorContext.java index 7eb35bec52a..254e85743b1 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ProcessorContext.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ProcessorContext.java @@ -28,6 +28,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import org.apache.maven.plugin.logging.Log; @@ -45,6 +46,9 @@ /// - A **stub source directory** in `target/generated-sources/cn1-annotations` /// used by the GENERATE_SOURCES Mojo; the PROCESS_CLASSES path doesn't write /// to it but the directory may exist either way. +/// - The **project settings** exactly as `codenameone_settings.properties` +/// holds them, plus the main class those settings name. A processor that +/// validates against project configuration needs both. public final class ProcessorContext { private final File outputClassDir; @@ -55,17 +59,48 @@ public final class ProcessorContext { private final Map emittedClasses = new LinkedHashMap(); private final Map emittedStubSources = new LinkedHashMap(); private final Map emittedResources = new LinkedHashMap(); + private final File projectDir; + private final Properties projectSettings; + private final String mainClassBinaryName; public ProcessorContext(File outputClassDir, File stubSourceDir, Map classIndex, Log log) { + this(outputClassDir, stubSourceDir, classIndex, log, null, null, null); + } + + /// Full form, adding the project configuration. + /// + /// `projectSettings` must be the **raw** contents of + /// `codenameone_settings.properties`, without any `-D` overlay: a hint given + /// on the command line is the documented way to override one for a single + /// build, so it must never be mistaken for something the project declares. + public ProcessorContext(File outputClassDir, File stubSourceDir, + Map classIndex, Log log, + File projectDir, Properties projectSettings, + String mainClassBinaryName) { this.outputClassDir = outputClassDir; this.stubSourceDir = stubSourceDir; this.classIndex = classIndex == null ? Collections.emptyMap() : Collections.unmodifiableMap(new LinkedHashMap(classIndex)); this.log = log; + this.projectDir = projectDir; + this.projectSettings = projectSettings; + this.mainClassBinaryName = mainClassBinaryName; } + /// The Codename One project directory -- the one holding + /// `codenameone_settings.properties` -- or null when it could not be found. + public File getProjectDir() { return projectDir; } + + /// The raw `codenameone_settings.properties`, or null when absent. Never + /// carries a `-D` overlay; see the constructor. + public Properties getProjectSettings() { return projectSettings; } + + /// Fully qualified name of the class named by `codename1.mainName`, or null + /// when the project does not declare one (a cn1lib, for instance). + public String getMainClassBinaryName() { return mainClassBinaryName; } + /// `target/classes` for the project, or the equivalent output directory. public File getOutputClassDir() { return outputClassDir; } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java new file mode 100644 index 00000000000..83bf3482f59 --- /dev/null +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven.processors; + +import com.codename1.build.shared.BuildHintAnnotationBinding; +import com.codename1.build.shared.BuildHints; +import com.codename1.maven.annotations.AbstractAnnotationProcessor; +import com.codename1.maven.annotations.AnnotatedClass; +import com.codename1.maven.annotations.AnnotationValues; +import com.codename1.maven.annotations.ProcessingException; +import com.codename1.maven.annotations.ProcessorContext; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeMap; + +/// Turns the `com.codename1.annotations.buildhints` annotations into the +/// `codename1.arg.*` key/value pairs the builders already consume. +/// +/// A build hint used to be a properties line that nothing checked, so a +/// misspelled name reached the build request, was never read, and was silently +/// dropped -- a green build with the setting simply not applied. Written as an +/// annotation the compiler catches the same mistake, and this processor is what +/// turns the checked form back into the wire form. +/// +/// The result is written to `META-INF/codenameone/build-hints.properties` in +/// `target/classes`, which puts it both on the simulator's classpath and inside +/// the jar uploaded to the build server. +public class BuildHintAnnotationProcessor extends AbstractAnnotationProcessor { + + /// Where the emitted hints land. Read by `CN1BuildMojo` before it writes the + /// build request, and by `Simulator` on startup. + public static final String MANIFEST_RESOURCE = "META-INF/codenameone/build-hints.properties"; + + /// Records which annotation attribute supplied each hint, so a later stage + /// -- the conflict message, the simulator's hint editor -- can name it. + private static final String ORIGIN_PREFIX = "cn1.buildHints.origin."; + + /// Stamps the emitted file with the main class it came from, so a stale or + /// foreign copy on the classpath can be recognised rather than merged. + private static final String MAIN_CLASS_KEY = "cn1.buildHints.mainClass"; + + /// hint name to value, sorted so the emitted bytes are stable. + private final Map hints = new TreeMap(); + /// hint name to "@Ios(pods)". + private final Map origins = new TreeMap(); + /// Classes carrying a build hint annotation, in discovery order. + private final List annotated = new ArrayList(); + + @Override + public Set getAnnotationDescriptors() { + return new LinkedHashSet(BuildHintAnnotationBinding.descriptors()); + } + + @Override + public void start(ProcessorContext ctx) throws ProcessingException { + hints.clear(); + origins.clear(); + annotated.clear(); + } + + @Override + public void processClass(AnnotatedClass cls, ProcessorContext ctx) throws ProcessingException { + Set descriptors = getAnnotationDescriptors(); + + // @Target(TYPE) already rejects a method or field placement at compile + // time, but @Target is a front-end check and this reads bytecode: a + // class produced another way could still carry one, and silently + // ignoring it would be the exact failure this feature removes. + for (String d : cls.getAllAnnotationDescriptors()) { + if (descriptors.contains(d) && !cls.getClassAnnotations().containsKey(d)) { + ctx.error(cls, "@" + simpleName(d) + " is a build hint annotation and belongs on " + + "the class itself, not on one of its members."); + } + } + + boolean carriesAny = false; + for (Map.Entry e : cls.getClassAnnotations().entrySet()) { + if (descriptors.contains(e.getKey())) { + carriesAny = true; + } + } + if (!carriesAny) { + return; + } + annotated.add(cls); + + for (Map.Entry e : cls.getClassAnnotations().entrySet()) { + String descriptor = e.getKey(); + if (!descriptors.contains(descriptor)) { + continue; + } + AnnotationValues values = e.getValue(); + // Only what the developer actually wrote: javac omits a member left + // at its default from the class file, and that absence is how an + // unset attribute is distinguished from one set to the default + // value. Reading through a getXxxOrDefault here would write a hint + // for every attribute of every annotation used. + for (Map.Entry member : values.all().entrySet()) { + String hint = BuildHintAnnotationBinding.hintFor(descriptor, member.getKey()); + if (hint == null) { + ctx.error(cls, "@" + simpleName(descriptor) + "(" + member.getKey() + + ") is not a known build hint. The catalog and the annotation " + + "have drifted; regenerate with " + + "scripts/gen-build-hint-annotations.sh."); + continue; + } + String value = wireValue(cls, descriptor, member.getKey(), member.getValue(), + hint, ctx); + if (value == null) { + continue; + } + String origin = "@" + simpleName(descriptor) + "(" + member.getKey() + ")"; + String previous = hints.put(hint, value); + if (previous != null && !previous.equals(value)) { + ctx.error(cls, "Build hint " + hint + " is set twice with different values: " + + origins.get(hint) + " and " + origin + "."); + } + origins.put(hint, origin); + } + } + } + + @Override + public void finish(ProcessorContext ctx) throws ProcessingException { + if (annotated.isEmpty()) { + // The last annotation was removed. The Mojo only writes emitted + // resources, it never deletes ones a processor stopped emitting, so + // without this yesterday's hints would stay in target/classes and + // ship inside the jar. + deleteGenerated(ctx); + return; + } + checkPlacement(ctx); + checkConflicts(ctx); + if (ctx.hasErrors()) { + return; + } + ctx.emitResource(MANIFEST_RESOURCE, serialize(ctx)); + ctx.getLog().info("cn1: " + hints.size() + " build hint(s) from annotations on " + + annotated.get(0).getBinaryName()); + } + + /// Build hints configure the application, so they belong on the class the + /// project already names as its entry point. + /// + /// Accepting them anywhere would mean two classes could set the same hint + /// and the winner would depend on the order `File.listFiles` happened to + /// return -- and it would scatter the effective build configuration across + /// the source tree, which is the problem the properties file already had. + private void checkPlacement(ProcessorContext ctx) { + String main = ctx.getMainClassBinaryName(); + if (main == null) { + ctx.error(annotated.get(0), + "Build hint annotations are only supported in a Codename One application, " + + "and this module declares no codename1.mainName."); + return; + } + for (AnnotatedClass cls : annotated) { + if (!main.equals(cls.getBinaryName())) { + ctx.error(cls, "Build hint annotations belong on the application's main class, " + + main + ", but this one carries them. Move them there, or set the hint " + + "in codenameone_settings.properties."); + } + } + } + + /// A hint has one source of truth. Setting it in both places means the two + /// can disagree, and nothing would say which won. + private void checkConflicts(ProcessorContext ctx) { + Properties settings = ctx.getProjectSettings(); + if (settings == null) { + return; + } + Map lines = propertyLines(ctx); + for (Map.Entry e : hints.entrySet()) { + // An alias and its target name one setting, so declaring the alias + // in the file still collides with the annotation. + Set names = new LinkedHashSet(); + names.add(e.getKey()); + for (BuildHints.Hint h : BuildHints.entries()) { + if (e.getKey().equals(h.aliasOf()) || e.getKey().equals( + BuildHints.canonicalName(h.name()))) { + names.add(h.name()); + } + } + for (String name : names) { + String key = BuildHints.ARG_PREFIX + name; + if (settings.getProperty(key) == null) { + continue; + } + StringBuilder sb = new StringBuilder(); + sb.append(key).append(" is declared twice.\n"); + sb.append(" annotation : ").append(origins.get(e.getKey())) + .append(" on ").append(annotated.get(0).getBinaryName()).append('\n'); + sb.append(" properties : "); + File f = settingsFile(ctx); + sb.append(f == null ? "codenameone_settings.properties" : f.getPath()); + Integer line = lines.get(key); + if (line != null) { + sb.append(':').append(line); + } + sb.append('\n'); + sb.append(" ").append(key).append('=') + .append(settings.getProperty(key)).append('\n'); + sb.append(" A build hint has one source of truth. Delete the properties line " + + "and keep the annotation, or delete the annotation attribute and keep " + + "the line. (-D").append(key).append("=... overrides either and is not " + + "a conflict.)"); + ctx.error(annotated.get(0), sb.toString()); + } + } + } + + private File settingsFile(ProcessorContext ctx) { + File dir = ctx.getProjectDir(); + if (dir == null) { + return null; + } + File f = new File(dir, "codenameone_settings.properties"); + return f.exists() ? f : null; + } + + /// Best-effort key to line number, so the conflict message can point at the + /// offending line. Properties escaping means an exotic key may not match; + /// the message then names the file only rather than guessing. + private Map propertyLines(ProcessorContext ctx) { + Map out = new LinkedHashMap(); + File f = settingsFile(ctx); + if (f == null) { + return out; + } + BufferedReader r = null; + try { + r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "ISO-8859-1")); + String line; + int n = 0; + while ((line = r.readLine()) != null) { + n++; + String t = line.trim(); + if (t.length() == 0 || t.charAt(0) == '#' || t.charAt(0) == '!') { + continue; + } + int eq = t.indexOf('='); + int colon = t.indexOf(':'); + int split = eq < 0 ? colon : (colon < 0 ? eq : Math.min(eq, colon)); + if (split <= 0) { + continue; + } + String key = t.substring(0, split).trim(); + if (!out.containsKey(key)) { + out.put(key, Integer.valueOf(n)); + } + } + } catch (IOException ex) { + ctx.getLog().debug("cn1: could not read " + f + " for line numbers: " + ex.getMessage()); + } finally { + if (r != null) { + try { + r.close(); + } catch (IOException ignored) { + // read-only stream; nothing useful to do + } + } + } + return out; + } + + /// Converts one annotation member value to the string the build receives. + /// + /// Returns null when the value could not be converted, having reported it. + private String wireValue(AnnotatedClass cls, String descriptor, String member, Object raw, + String hint, ProcessorContext ctx) { + if (raw instanceof Boolean || raw instanceof Number || raw instanceof Character) { + return String.valueOf(raw); + } + if (raw instanceof String) { + return (String) raw; + } + // ASM reports an enum constant as { descriptor, CONSTANT_NAME }. The + // constant name is not the value the builder compares against, and a + // builder silently falls back to its default on a value it does not + // recognise, so guessing here would fail invisibly. + if (raw instanceof String[]) { + String[] pair = (String[]) raw; + if (pair.length == 2) { + String wire = BuildHintAnnotationBinding.wireValue(pair[0], pair[1]); + if (wire == null) { + ctx.error(cls, "@" + simpleName(descriptor) + "(" + member + ") uses the " + + "constant " + pair[1] + ", which the build hint catalog does not " + + "map to a value. Regenerate with " + + "scripts/gen-build-hint-annotations.sh."); + return null; + } + return wire; + } + } + if (raw instanceof List) { + String separator = BuildHints.separatorFor(hint); + if (separator.length() == 0) { + ctx.error(cls, "@" + simpleName(descriptor) + "(" + member + ") is a list but the " + + "catalog gives " + hint + " no separator, so its values would run " + + "together."); + return null; + } + StringBuilder sb = new StringBuilder(); + for (Object item : (List) raw) { + if (sb.length() > 0) { + sb.append(separator); + } + String itemValue = wireValue(cls, descriptor, member, item, hint, ctx); + if (itemValue == null) { + return null; + } + sb.append(itemValue); + } + return sb.toString(); + } + ctx.error(cls, "@" + simpleName(descriptor) + "(" + member + ") has a value this " + + "processor cannot convert: " + raw); + return null; + } + + /// Serializes deterministically. + /// + /// Not `Properties.store`: it writes a timestamp comment, so the bytes would + /// differ on every build. That churns the resource in every incremental + /// build and defeats the staged-jar staleness comparison in `CN1BuildMojo`. + private byte[] serialize(ProcessorContext ctx) throws ProcessingException { + StringBuilder sb = new StringBuilder(); + sb.append("# Generated from build hint annotations by the Codename One Maven plugin.\n"); + sb.append("# Edit the annotations on the main class, not this file.\n"); + String main = ctx.getMainClassBinaryName(); + if (main != null) { + sb.append(MAIN_CLASS_KEY).append('=').append(escape(main)).append('\n'); + } + for (Map.Entry e : hints.entrySet()) { + sb.append(escape(BuildHints.ARG_PREFIX + e.getKey())).append('=') + .append(escape(e.getValue())).append('\n'); + } + for (Map.Entry e : origins.entrySet()) { + sb.append(escape(ORIGIN_PREFIX + e.getKey())).append('=') + .append(escape(e.getValue())).append('\n'); + } + try { + return sb.toString().getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException ex) { + throw new ProcessingException("ISO-8859-1 is unavailable", ex); + } + } + + /// Applies the escaping `java.util.Properties` expects, so a value holding a + /// newline -- `gradleDependencies` legitimately does -- survives the round + /// trip. + static String escape(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '\\': sb.append("\\\\"); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + case '=': sb.append("\\="); break; + case ':': sb.append("\\:"); break; + case '#': sb.append("\\#"); break; + case '!': sb.append("\\!"); break; + case ' ': sb.append(i == 0 ? "\\ " : " "); break; + default: + if (c < 0x20 || c > 0x7e) { + sb.append(String.format("\\u%04x", Integer.valueOf(c))); + } else { + sb.append(c); + } + } + } + return sb.toString(); + } + + private void deleteGenerated(ProcessorContext ctx) { + File f = new File(ctx.getOutputClassDir(), MANIFEST_RESOURCE); + if (f.exists() && !f.delete()) { + ctx.getLog().warn("cn1: could not remove stale " + f + "; it would be packaged " + + "with hints the project no longer declares"); + } + } + + private static String simpleName(String descriptor) { + String s = descriptor; + if (s.startsWith("L") && s.endsWith(";")) { + s = s.substring(1, s.length() - 1); + } + int slash = s.lastIndexOf('/'); + return slash >= 0 ? s.substring(slash + 1) : s; + } +} diff --git a/maven/codenameone-maven-plugin/src/main/resources/META-INF/services/com.codename1.maven.annotations.AnnotationProcessor b/maven/codenameone-maven-plugin/src/main/resources/META-INF/services/com.codename1.maven.annotations.AnnotationProcessor index 52e2d775b5b..965c0ff6c46 100644 --- a/maven/codenameone-maven-plugin/src/main/resources/META-INF/services/com.codename1.maven.annotations.AnnotationProcessor +++ b/maven/codenameone-maven-plugin/src/main/resources/META-INF/services/com.codename1.maven.annotations.AnnotationProcessor @@ -7,3 +7,4 @@ com.codename1.maven.processors.ProtoMessageAnnotationProcessor com.codename1.maven.processors.GrpcClientAnnotationProcessor com.codename1.maven.processors.GraphQLClientAnnotationProcessor com.codename1.maven.processors.AppIntentAnnotationProcessor +com.codename1.maven.processors.BuildHintAnnotationProcessor diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java new file mode 100644 index 00000000000..b9b8e0ba559 --- /dev/null +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven.processors; + +import com.codename1.maven.annotations.AnnotatedClass; +import com.codename1.maven.annotations.ClassScanner; +import com.codename1.maven.annotations.JavaSourceCompiler; +import com.codename1.maven.annotations.ProcessorContext; + +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.ByteArrayInputStream; +import java.net.URL; +import java.util.Arrays; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/// Covers the conversion from typed annotation back to wire-format build hint. +/// +/// The cases that matter most are the silent ones: a hint written for an +/// attribute the developer never set, an enum written as its constant name +/// rather than the value the builder compares against, and a list joined with +/// the wrong delimiter. None of those fail a build -- the builder falls back to +/// a default or writes a malformed fragment -- so only a test catches them. +public class BuildHintAnnotationProcessorTest { + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private static final String MAIN = "com.example.MyApp"; + + // ------------------------------------------------------------------ + // value conversion + // ------------------------------------------------------------------ + + @Test + public void aBooleanAttributeIsWrittenAsTrueOrFalse() throws Exception { + Properties p = hintsOf("@Ios(newStorageLocation = true)"); + assertEquals("true", p.getProperty("codename1.arg.ios.newStorageLocation")); + + p = hintsOf("@Ios(newStorageLocation = false)"); + assertEquals("false", p.getProperty("codename1.arg.ios.newStorageLocation")); + } + + @Test + public void anIntAttributeIsStringified() throws Exception { + Properties p = hintsOf("@Desktop(width = 1280, height = 720)"); + assertEquals("1280", p.getProperty("codename1.arg.desktop.width")); + assertEquals("720", p.getProperty("codename1.arg.desktop.height")); + } + + @Test + public void aStringAttributeIsWrittenVerbatim() throws Exception { + Properties p = hintsOf("@Ios(teamId = \"ABCDE12345\")"); + assertEquals("ABCDE12345", p.getProperty("codename1.arg.ios.teamId")); + } + + /// The builder compares against the catalog's value, not the constant name. + /// `IOS7` happens to lowercase to `ios7`, but `INTERNAL_ONLY` does not + /// lowercase to `internalOnly`, and a builder given an unrecognized value + /// silently uses its default -- so a name-based conversion would fail with + /// no diagnostic anywhere. + @Test + public void anEnumAttributeUsesTheCatalogValueNotTheConstantName() throws Exception { + Properties p = hintsOf("@Android(installLocation = InstallLocation.INTERNAL_ONLY)"); + assertEquals("internalOnly", p.getProperty("codename1.arg.android.installLocation")); + + p = hintsOf("@Ios(themeMode = IosThemeMode.IOS7)"); + assertEquals("ios7", p.getProperty("codename1.arg.ios.themeMode")); + + p = hintsOf("@Desktop(titleBar = DesktopTitleBar.TOOLBAR)"); + assertEquals("toolbar", p.getProperty("codename1.arg.desktop.titleBar")); + } + + @Test + public void aStringArrayIsJoinedWithTheHintsOwnSeparator() throws Exception { + // ios.pods is comma delimited, ios.add_libs is semicolon delimited -- + // the same shape in Java, two different wire formats. + Properties p = hintsOf("@Ios(pods = {\"Alamofire\", \"SwiftyJSON\"}, " + + "addLibs = {\"libz.tbd\", \"libsqlite3.tbd\"})"); + assertEquals("Alamofire,SwiftyJSON", p.getProperty("codename1.arg.ios.pods")); + assertEquals("libz.tbd;libsqlite3.tbd", p.getProperty("codename1.arg.ios.add_libs")); + } + + @Test + public void aNewlineDelimitedListSurvivesThePropertiesRoundTrip() throws Exception { + Properties p = hintsOf("@Android(proguardKeep = {\"-keep class com.a.** { *; }\", " + + "\"-keep class com.b.** { *; }\"})"); + assertEquals("-keep class com.a.** { *; }\n-keep class com.b.** { *; }", + p.getProperty("codename1.arg.android.proguardKeep")); + } + + // ------------------------------------------------------------------ + // set vs unset + // ------------------------------------------------------------------ + + /// The load-bearing one. javac omits a member left at its default from the + /// class file, which is the only thing distinguishing "not set" from "set + /// to the default". Reading attributes through a getOrDefault would write + /// a hint for every attribute of every annotation the project uses. + @Test + public void anAttributeThatWasNotWrittenProducesNoHint() throws Exception { + Properties p = hintsOf("@Ios(pods = {\"Alamofire\"})"); + assertEquals("Alamofire", p.getProperty("codename1.arg.ios.pods")); + assertNull("an unset attribute must not be written at all", + p.getProperty("codename1.arg.ios.newStorageLocation")); + assertNull(p.getProperty("codename1.arg.ios.objC")); + assertNull(p.getProperty("codename1.arg.ios.teamId")); + } + + /// The other half of the same contract: a value the developer typed is + /// written even when it equals the annotation's declared default, because + /// typing it is a statement of intent. + @Test + public void anExplicitlyWrittenDefaultValueIsStillEmitted() throws Exception { + // ios.objC is declared `default true` by the generator. + Properties p = hintsOf("@Ios(objC = true)"); + assertEquals("true", p.getProperty("codename1.arg.ios.objC")); + } + + // ------------------------------------------------------------------ + // determinism and cleanup + // ------------------------------------------------------------------ + + @Test + public void theEmittedResourceIsByteStableAcrossRuns() throws Exception { + String src = "@Ios(pods = {\"A\", \"B\"}, teamId = \"T\")\n@Desktop(width = 640)"; + byte[] first = rawResource(src); + byte[] second = rawResource(src); + assertTrue("the emitted resource must not change between identical builds", + Arrays.equals(first, second)); + } + + @Test + public void removingTheLastAnnotationRemovesTheGeneratedResource() throws Exception { + File classes = compile("@Ios(teamId = \"T\")"); + ProcessorContext ctx = run(classes, settings(), MAIN, true); + File emitted = new File(classes, + BuildHintAnnotationProcessor.MANIFEST_RESOURCE); + emitted.getParentFile().mkdirs(); + FileOutputStream out = new FileOutputStream(emitted); + out.write(ctx.getEmittedResources().get( + BuildHintAnnotationProcessor.MANIFEST_RESOURCE)); + out.close(); + assertTrue(emitted.exists()); + + // Recompile with no build hint annotation at all. + File plain = compile(""); + // Point the processor at the directory still holding yesterday's file. + copyInto(plain, emitted); + run(plain, settings(), MAIN, true); + assertFalse("a stale build-hints resource would ship hints the project no longer " + + "declares", new File(plain, + BuildHintAnnotationProcessor.MANIFEST_RESOURCE).exists()); + } + + // ------------------------------------------------------------------ + // placement + // ------------------------------------------------------------------ + + @Test + public void annotationsOnANonMainClassAreRejected() throws Exception { + File classes = compile("@Ios(teamId = \"T\")"); + ProcessorContext ctx = run(classes, settings(), "com.example.SomethingElse", false); + assertErrorContaining(ctx, "belong on the application's main class"); + } + + @Test + public void aModuleWithNoMainClassIsRejected() throws Exception { + File classes = compile("@Ios(teamId = \"T\")"); + ProcessorContext ctx = run(classes, settings(), null, false); + assertErrorContaining(ctx, "declares no codename1.mainName"); + } + + /// A project that uses none of these annotations must not start failing + /// because it has no main class -- a cn1lib, for instance. + @Test + public void aModuleWithNoAnnotationsAndNoMainClassIsFine() throws Exception { + File classes = compile(""); + ProcessorContext ctx = run(classes, settings(), null, true); + assertFalse(ctx.hasErrors()); + } + + // ------------------------------------------------------------------ + // conflicts with the properties file + // ------------------------------------------------------------------ + + @Test + public void aHintSetInBothPlacesIsAnError() throws Exception { + Properties s = settings(); + s.setProperty("codename1.arg.ios.teamId", "FROMFILE"); + File classes = compile("@Ios(teamId = \"FROMANNOTATION\")"); + ProcessorContext ctx = run(classes, s, MAIN, false); + assertErrorContaining(ctx, "codename1.arg.ios.teamId is declared twice"); + assertErrorContaining(ctx, "@Ios(teamId)"); + } + + @Test + public void aHintOnlyInThePropertiesFileIsFine() throws Exception { + Properties s = settings(); + s.setProperty("codename1.arg.ios.plistInject", "X"); + File classes = compile("@Ios(teamId = \"T\")"); + ProcessorContext ctx = run(classes, s, MAIN, true); + assertFalse(ctx.hasErrors()); + } + + /// A commented-out line is not a declaration, and the archetype ships + /// several. Properties.load skips them, so this is really a guard against + /// anyone reintroducing a hand-rolled line scan. + @Test + public void aCommentedOutPropertyIsNotAConflict() throws Exception { + Properties s = new Properties(); + s.load(new ByteArrayInputStream( + ("codename1.mainName=MyApp\ncodename1.packageName=com.example\n" + + "#codename1.arg.ios.teamId=OLD\n").getBytes("ISO-8859-1"))); + File classes = compile("@Ios(teamId = \"T\")"); + ProcessorContext ctx = run(classes, s, MAIN, true); + assertFalse(ctx.hasErrors()); + } + + // ------------------------------------------------------------------ + // helpers + // ------------------------------------------------------------------ + + private static String source(String annotations) { + return "package com.example;\n" + + "import com.codename1.annotations.buildhints.*;\n" + + annotations + "\n" + + "public class MyApp {\n}\n"; + } + + private File compile(String annotations) throws Exception { + File classes = tmp.newFolder(); + JavaSourceCompiler.compile( + JavaSourceCompiler.singleSource(MAIN, source(annotations)), + classes, Arrays.asList(testClassesDir(), coreJar())); + return classes; + } + + private ProcessorContext run(File classes, Properties settings, String mainClass, + boolean expectClean) throws Exception { + Map index = ClassScanner.scan(classes); + BuildHintAnnotationProcessor proc = new BuildHintAnnotationProcessor(); + ProcessorContext ctx = new ProcessorContext(classes, tmp.newFolder(), index, + new SystemStreamLog(), tmp.newFolder(), settings, mainClass); + proc.start(ctx); + for (AnnotatedClass cls : index.values()) { + proc.processClass(cls, ctx); + } + proc.finish(ctx); + if (expectClean && ctx.hasErrors()) { + StringBuilder sb = new StringBuilder("unexpected errors:\n"); + for (ProcessorContext.ProcessingError e : ctx.getErrors()) { + sb.append(" ").append(e).append('\n'); + } + fail(sb.toString()); + } + return ctx; + } + + private byte[] rawResource(String annotations) throws Exception { + ProcessorContext ctx = run(compile(annotations), settings(), MAIN, true); + byte[] bytes = ctx.getEmittedResources() + .get(BuildHintAnnotationProcessor.MANIFEST_RESOURCE); + assertTrue("build-hints.properties must be emitted", bytes != null); + return bytes; + } + + private Properties hintsOf(String annotations) throws Exception { + Properties p = new Properties(); + p.load(new ByteArrayInputStream(rawResource(annotations))); + return p; + } + + private static Properties settings() { + Properties p = new Properties(); + p.setProperty("codename1.mainName", "MyApp"); + p.setProperty("codename1.packageName", "com.example"); + return p; + } + + private static void assertErrorContaining(ProcessorContext ctx, String fragment) { + assertTrue("expected a validation error", ctx.hasErrors()); + StringBuilder all = new StringBuilder(); + for (ProcessorContext.ProcessingError e : ctx.getErrors()) { + all.append(e).append('\n'); + } + assertTrue("expected an error containing \"" + fragment + "\" but got:\n" + all, + all.toString().contains(fragment)); + } + + private static void copyInto(File classesDir, File existing) throws Exception { + File target = new File(classesDir, BuildHintAnnotationProcessor.MANIFEST_RESOURCE); + target.getParentFile().mkdirs(); + java.nio.file.Files.copy(existing.toPath(), target.toPath(), + java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } + + private static File testClassesDir() throws Exception { + URL url = BuildHintAnnotationProcessorTest.class.getProtectionDomain() + .getCodeSource().getLocation(); + return new File(url.toURI()); + } + + /// The generated annotations live in codenameone-core, which is already a + /// dependency of the plugin, so the compiled sources can reference them. + private static File coreJar() throws Exception { + URL url = Class.forName("com.codename1.annotations.buildhints.Ios") + .getProtectionDomain().getCodeSource().getLocation(); + return new File(url.toURI()); + } +} diff --git a/maven/integration-tests/all.sh b/maven/integration-tests/all.sh index e3854a3d131..04fe56dc8d0 100644 --- a/maven/integration-tests/all.sh +++ b/maven/integration-tests/all.sh @@ -9,6 +9,7 @@ bash cssfonts.sh bash native-interfaces.sh bash initializr-roundtrip-test.sh bash cn1app-archetype-test.sh +bash build-hint-annotations-test.sh bash cn1app-desktop-build-test.sh bash bare-bones-kotlin-test.sh bash migrate-kitchensink-test.sh diff --git a/maven/integration-tests/build-hint-annotations-test.sh b/maven/integration-tests/build-hint-annotations-test.sh new file mode 100755 index 00000000000..49d14ede752 --- /dev/null +++ b/maven/integration-tests/build-hint-annotations-test.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# +# Build hints written as annotations must reach the build request, and the same +# hint set twice must fail. +# +# A build hint used to be an unchecked string: misspell it and the build stayed +# green while the setting silently did nothing. The annotations exist so the +# compiler catches that, and this test covers the part the compiler cannot -- +# that the annotation is actually converted back into the codename1.arg.* pair +# the builders read, and that it is not silently merged with a properties line +# saying something different. +SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" +set -e +source $SCRIPTPATH/inc/env.sh + +cd $SCRIPTPATH/build +rm -rf myapphints +mvn archetype:generate \ + -DarchetypeArtifactId=cn1app-archetype \ + -DarchetypeGroupId=com.codenameone \ + -DarchetypeVersion=$CN1_VERSION \ + -DartifactId=myapphints \ + -DgroupId=com.example \ + -Dversion=1.0-SNAPSHOT \ + -DmainName=MyApp \ + -DinteractiveMode=false + +cd myapphints +chmod 755 mvnw + +MAIN=common/src/main/java/com/example/MyApp.java +SETTINGS=common/codenameone_settings.properties + +echo "--- the generated project must already use annotations ---" +grep -q "com.codename1.annotations.buildhints" $MAIN \ + || { echo "FAIL: the archetype's main class does not import the build hint annotations"; exit 1; } +grep -q "^codename1.arg.ios.newStorageLocation" $SETTINGS \ + && { echo "FAIL: ios.newStorageLocation should have moved to @Ios, not stayed in $SETTINGS"; exit 1; } + +echo "--- add a hint of each shape ---" +perl -0pi -e 's/\@Ios\(/\@Ios(pods = {"Alamofire", "SwiftyJSON"}, teamId = "ABCDE12345", /' $MAIN +grep -q 'pods = {"Alamofire"' $MAIN || { echo "FAIL: could not patch $MAIN"; exit 1; } + +echo "--- process-classes must emit the hints ---" +./mvnw -B -q -pl common process-classes +EMITTED=common/target/classes/META-INF/codenameone/build-hints.properties +test -f $EMITTED || { echo "FAIL: $EMITTED was not emitted"; exit 1; } + +check() { + grep -qF "$1" $EMITTED || { echo "FAIL: expected '$1' in $EMITTED"; cat $EMITTED; exit 1; } +} +# a list joins with the hint's own separator, an enum uses the catalog's value +# rather than the constant name, and an unset attribute writes nothing at all +check "codename1.arg.ios.pods=Alamofire,SwiftyJSON" +check "codename1.arg.ios.teamId=ABCDE12345" +check "codename1.arg.ios.themeMode=modern" +check "codename1.arg.desktop.titleBar=native" +grep -q "codename1.arg.ios.objC" $EMITTED \ + && { echo "FAIL: an attribute nobody set must not be written"; exit 1; } + +echo "--- the hints must reach the build request ---" +# "Build target not supported" is thrown after the merged settings file is +# written, so this asserts the upload payload offline: no SDK, no cloud build. +set +e +./mvnw -B -q -DskipTests -Dcodename1.platform=javase \ + -Dcodename1.buildTarget=local-build-hint-probe package > /tmp/cn1-hints-build.log 2>&1 +set -e +MERGED=common/target/codenameone/antProject/codenameone_settings.properties +test -f $MERGED || MERGED=javase/target/codenameone/antProject/codenameone_settings.properties +if [ -f "$MERGED" ]; then + grep -q "codename1.arg.ios.pods=Alamofire,SwiftyJSON" $MERGED \ + || { echo "FAIL: annotation hints did not reach the build request"; cat $MERGED; exit 1; } + echo "OK: annotation hints reached $MERGED" +else + echo "NOTE: no build request was written for this target; skipping that assertion" +fi + +echo "--- declaring the same hint twice must fail ---" +echo "codename1.arg.ios.teamId=FROMFILE" >> $SETTINGS +set +e +./mvnw -B -pl common process-classes > /tmp/cn1-hints-conflict.log 2>&1 +STATUS=$? +set -e +if [ $STATUS -eq 0 ]; then + echo "FAIL: a hint set in both the annotation and $SETTINGS should fail the build" + exit 1 +fi +grep -q "codename1.arg.ios.teamId is declared twice" /tmp/cn1-hints-conflict.log \ + || { echo "FAIL: the conflict error did not name the hint"; tail -30 /tmp/cn1-hints-conflict.log; exit 1; } +grep -q "@Ios(teamId)" /tmp/cn1-hints-conflict.log \ + || { echo "FAIL: the conflict error did not name the annotation attribute"; exit 1; } + +echo "PASSED build-hint-annotations-test" diff --git a/maven/pom.xml b/maven/pom.xml index 303a83ec859..8184e0fff39 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -81,6 +81,7 @@ cn1-binaries platform-feature-catalog + build-hint-catalog java-runtime core factory diff --git a/scripts/build-hint-catalog-baseline.txt b/scripts/build-hint-catalog-baseline.txt new file mode 100644 index 00000000000..1c1820530bf --- /dev/null +++ b/scripts/build-hint-catalog-baseline.txt @@ -0,0 +1,8 @@ +# Build hints read by a builder or a mojo that the catalog does not describe, +# as of the day this gate was added. +# +# This is a ratchet, not an allow-list: new code must not add entries. Delete a +# line when the hint is added to maven/build-hint-catalog. Regenerate with +# scripts/check-build-hint-catalog.sh --write-baseline +# +# Format: |: diff --git a/scripts/build_hint_miner.py b/scripts/build_hint_miner.py new file mode 100644 index 00000000000..47498c3547f --- /dev/null +++ b/scripts/build_hint_miner.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Mines every build hint the Maven plugin and the builders read. + +Three accessor shapes reach a hint; a getArg() grep alone misses the whole +@Desktop group, which is read only by a private arg() helper in +GenerateDesktopAppWrapperMojo. + +Arguments are split with a paren/quote-balanced scan rather than a regex, +because calls nest: getArg("ios.urlSchemes", getArg("ios.urlScheme", "")). +A regex that stops at the first comma both mis-reads the outer default and +consumes the inner call, silently dropping a hint from the catalog. +""" +import re, os, sys, json, collections + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SRC = os.path.join(ROOT, "maven/codenameone-maven-plugin/src/main/java/com/codename1") + +OPENERS = [ + (re.compile(r'\bgetArg\(\s*"'), False), + (re.compile(r'(? 1 else "" + if not LITERAL_DEFAULT.fullmatch(default or "null"): + default = "" + line = text.count("\n", 0, m.start()) + 1 + hits[key].append((default or "null", rel, line)) + +if __name__ == "__main__": + print(f"distinct keys mined: {len(hits)}", file=sys.stderr) + out = sys.argv[1] if len(sys.argv) > 1 else "-" + payload = {k: v for k, v in sorted(hits.items())} + if out == "-": + json.dump(payload, sys.stdout, indent=1) + else: + json.dump(payload, open(out, "w"), indent=1) diff --git a/scripts/certificatewizard/common/codenameone_settings.properties b/scripts/certificatewizard/common/codenameone_settings.properties index 86cf88fa524..b6d065730e0 100644 --- a/scripts/certificatewizard/common/codenameone_settings.properties +++ b/scripts/certificatewizard/common/codenameone_settings.properties @@ -7,10 +7,4 @@ codename1.secondaryTitle=Certificate Wizard codename1.icon=icon.png codename1.cssTheme=true codename1.arg.java.version=17 -codename1.arg.nativeTheme=modern -codename1.arg.ios.themeMode=modern -codename1.arg.and.themeMode=modern -codename1.arg.desktop.width=1260 -codename1.arg.desktop.height=820 -codename1.arg.desktop.titleBar=native codename1.kotlin=false diff --git a/scripts/certificatewizard/common/src/main/java/com/codename1/certificatewizard/CertificateWizard.java b/scripts/certificatewizard/common/src/main/java/com/codename1/certificatewizard/CertificateWizard.java index 116a660dd0a..99254d5b752 100644 --- a/scripts/certificatewizard/common/src/main/java/com/codename1/certificatewizard/CertificateWizard.java +++ b/scripts/certificatewizard/common/src/main/java/com/codename1/certificatewizard/CertificateWizard.java @@ -67,7 +67,12 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import com.codename1.annotations.buildhints.*; +@Android(themeMode = AndroidThemeMode.MODERN) +@Build(nativeTheme = NativeThemeMode.MODERN) +@Desktop(height = 820, titleBar = DesktopTitleBar.NATIVE, width = 1260) +@Ios(themeMode = IosThemeMode.MODERN) public class CertificateWizard extends Lifecycle { public enum Section { OVERVIEW, CREDENTIAL, CERTIFICATES, BUNDLES, DEVICES, PROFILES, APNS, MAC, ANDROID, WINDOWS, MAINTENANCE } diff --git a/scripts/check-build-hint-catalog.py b/scripts/check-build-hint-catalog.py new file mode 100755 index 00000000000..eb8c4c787dc --- /dev/null +++ b/scripts/check-build-hint-catalog.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +"""Reports build hints the code reads that the catalog does not describe. + +A hint the catalog does not know about is invisible to everything downstream: +it gets no annotation, no doc row, no entry in the Settings tool, and no +value checking. That is how `android.xPermissions` shipped in our own agent +reference for a hint the builder actually spells `android.xpermissions` -- +green build, no effect, nobody noticed. + +Held against a baseline rather than failing outright: a large tail of hints +predates the catalog. The point is that *new* code cannot add another one. +""" +import fnmatch, json, os, re, subprocess, sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "scripts")) +BASELINE = os.path.join(ROOT, "scripts", "build-hint-catalog-baseline.txt") +CATALOG_CLASSES = os.path.join(ROOT, "maven/build-hint-catalog/target/classes") + + +def catalog(): + """(known names, dynamic patterns) straight out of the compiled catalog.""" + src = os.path.join(ROOT, "maven/build-hint-catalog/src/main/java/com/codename1/build/shared") + names, patterns = set(), set() + for fn in sorted(os.listdir(src)): + if not fn.startswith("BuildHints") or not fn.endswith(".java"): + continue + text = open(os.path.join(src, fn), encoding="utf-8").read() + for m in re.finditer(r'new Hint\("((?:[^"\\]|\\.)*)"\)', text): + names.add(m.group(1)) + for m in re.finditer(r'\.dynamic\("((?:[^"\\]|\\.)*)"\)', text): + patterns.add(m.group(1)) + # BuildHintsDynamic registers through a helper; take its literals too + for m in re.finditer(r'family\(h,\s*"((?:[^"\\]|\\.)*)"', text): + names.add(m.group(1)) + patterns.add(m.group(1)) + return names, patterns + + +DOC_ROOTS = [ + "scripts/initializr/common/src/main/resources/skill", + "maven/cn1app-archetype/src/main/resources/archetype-resources", +] + + +def documented_hints(): + """Every codename1.arg.* key our own docs and templates name. + + These are read by people and by coding agents, and a key that no builder + reads is silently inert -- which is exactly how android.xPermissions, + android.minSdkVersion and android.sdkVersion came to be recommended in the + agent reference for hints the builder spells differently or not at all. + """ + found = {} + for root in DOC_ROOTS: + base = os.path.join(ROOT, root) + for dirpath, _, files in os.walk(base): + for fn in files: + if not fn.endswith((".md", ".properties", ".java", ".adoc")): + continue + path = os.path.join(dirpath, fn) + try: + text = open(path, encoding="utf-8", errors="replace").read() + except OSError: + continue + for m in re.finditer(r'codename1\.arg\.([A-Za-z][A-Za-z0-9_.]*)', text): + key = m.group(1) + # "codename1.arg.var." is written with a placeholder suffix; + # keep the trailing dot so it still matches the var.* family. + if key.endswith("."): + key += "*" + found.setdefault(key, os.path.relpath(path, ROOT)) + return found + + +def main(): + write = "--write-baseline" in sys.argv + import build_hint_miner as miner + + known, patterns = catalog() + if not known: + print("check-build-hint-catalog: found no catalog entries -- is the source tree intact?", + file=sys.stderr) + return 2 + + findings = [] + for key, sites in sorted(miner.hits.items()): + if key in known: + continue + if any(fnmatch.fnmatch(key, p) for p in patterns): + continue + rel, line = sites[0][1], sites[0][2] + findings.append(f"{key}|{rel}:{line}") + + if write: + with open(BASELINE, "w") as f: + f.write(HEADER) + for line in findings: + f.write(line + "\n") + print(f"check-build-hint-catalog: wrote {len(findings)} baseline entries") + return 0 + + baseline = set() + if os.path.exists(BASELINE): + for line in open(BASELINE): + line = line.strip() + if line and not line.startswith("#"): + baseline.add(line.split("|")[0]) + + current = {f.split("|")[0]: f for f in findings} + added = sorted(set(current) - baseline) + removed = sorted(baseline - set(current)) + + if added: + print("check-build-hint-catalog: build hints read by the code that the catalog " + "does not describe:", file=sys.stderr) + for key in added: + print(" " + current[key].replace("|", " read at "), file=sys.stderr) + print("\nAdd each one to maven/build-hint-catalog/.../BuildHints*.java. A hint the " + "catalog does not know about gets no annotation, no documentation and no " + "value checking.", file=sys.stderr) + if removed: + print("\ncheck-build-hint-catalog: these baseline entries are now catalogued; " + "delete them from\n scripts/build-hint-catalog-baseline.txt", file=sys.stderr) + for key in removed: + print(" " + key, file=sys.stderr) + if added or removed: + return 1 + + # Our own docs and project templates must not name a hint that does not exist. + doc_bad = [] + for key, where in sorted(documented_hints().items()): + if key in known: + continue + if any(fnmatch.fnmatch(key, p) for p in patterns): + continue + doc_bad.append(f"{key} named in {where}") + if doc_bad: + print("check-build-hint-catalog: our own documentation names build hints that do " + "not exist:", file=sys.stderr) + for line in doc_bad: + print(" " + line, file=sys.stderr) + print("\nA hint nothing reads is silently ignored, so a reader who copies it gets a " + "green build and no effect.", file=sys.stderr) + return 1 + + print(f"check-build-hint-catalog: {len(miner.hits)} hints read, all described by the catalog" + + (f" ({len(baseline)} baselined)" if baseline else "")) + return 0 + + +HEADER = """# Build hints read by a builder or a mojo that the catalog does not describe, +# as of the day this gate was added. +# +# This is a ratchet, not an allow-list: new code must not add entries. Delete a +# line when the hint is added to maven/build-hint-catalog. Regenerate with +# scripts/check-build-hint-catalog.sh --write-baseline +# +# Format: |: +""" + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/check-build-hint-catalog.sh b/scripts/check-build-hint-catalog.sh new file mode 100755 index 00000000000..ac276d5bcf1 --- /dev/null +++ b/scripts/check-build-hint-catalog.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# +# Fails when code reads a build hint that the catalog does not describe. +# +# Build hints are string keys. Nothing checks them, so a hint that is misspelled +# where it is read -- or added to a builder and nowhere else -- simply does +# nothing: the build is green and the feature is inert. The catalog in +# maven/build-hint-catalog is what gives every hint a type, a default, a value +# domain and a doc row, and it is what the @Ios/@Android annotations are +# generated from. A hint missing from it is invisible to all of that. +# +# scripts/check-build-hint-catalog.sh [--write-baseline] +# +# The result is held against scripts/build-hint-catalog-baseline.txt, a ratchet +# of pre-existing debt rather than an allow-list. That file is currently empty: +# every hint the code reads is described. Keep it that way -- a new entry means +# a new hint went in without a catalog row. +# +# Reads source, not bytecode, so nothing has to be built first and no module can +# silently drop out of coverage. +set -euo pipefail + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +exec python3 "$SCRIPT_DIR/check-build-hint-catalog.py" "$@" diff --git a/scripts/cn1playground/common/codenameone_settings.properties b/scripts/cn1playground/common/codenameone_settings.properties index 454539b07fc..10505aa8793 100644 --- a/scripts/cn1playground/common/codenameone_settings.properties +++ b/scripts/cn1playground/common/codenameone_settings.properties @@ -2,14 +2,9 @@ codename1.android.keystore= codename1.android.keystoreAlias= codename1.android.keystorePassword= codename1.arg.block_server_registration=true -codename1.arg.ios.newStorageLocation=true -codename1.arg.ios.NSCameraUsageDescription=Some functionality of the application requires your camera # Preview the iOS Modern (liquid-glass) and Android Material 3 # themes inside the playground so users see the modern look when # they explore components. -codename1.arg.nativeTheme=modern -codename1.arg.ios.themeMode=modern -codename1.arg.and.themeMode=modern codename1.arg.java.version=17 codename1.arg.javascript.inject_proxy=false codename1.cssTheme=true diff --git a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java index f36d522986d..fe635fae6ba 100644 --- a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java +++ b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java @@ -55,7 +55,12 @@ import java.util.ArrayList; import java.util.Hashtable; import java.util.List; +import com.codename1.annotations.buildhints.*; +@Android(themeMode = AndroidThemeMode.MODERN) +@Build(nativeTheme = NativeThemeMode.MODERN) +@Ios(newStorageLocation = true, themeMode = IosThemeMode.MODERN) +@IosPrivacy(cameraUsageDescription = "Some functionality of the application requires your camera") public class CN1Playground extends Lifecycle { private static final boolean DEFAULT_DARK_MODE = true; private static final String THEME_ROLE = "playgroundThemeRole"; diff --git a/scripts/fidelity-app/common/codenameone_settings.properties b/scripts/fidelity-app/common/codenameone_settings.properties index d3f6f26ad8e..18061a29110 100644 --- a/scripts/fidelity-app/common/codenameone_settings.properties +++ b/scripts/fidelity-app/common/codenameone_settings.properties @@ -3,11 +3,7 @@ codename1.android.keystore=/Users/shai/dev/cn4/CodenameOne/scripts/fidelity-app/android/../common/androidCerts/KeyChain.ks codename1.android.keystoreAlias=androidKey codename1.android.keystorePassword=password -codename1.arg.android.gradleDep=implementation 'com.google.android.material\:material\:1.12.0' -codename1.arg.android.useAndroidX=true codename1.arg.ios.metal=true -codename1.arg.ios.newStorageLocation=true -codename1.arg.ios.uiscene=true codename1.arg.java.version=17 codename1.cssTheme=true codename1.displayName=Fidelity diff --git a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java index 739b0c98598..5fb34da91d0 100644 --- a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java +++ b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java @@ -23,6 +23,7 @@ package com.codenameone.fidelity; import com.codename1.system.Lifecycle; +import com.codename1.annotations.buildhints.*; /** * Entry point for the native-theme fidelity test app. It is not an interactive @@ -31,6 +32,8 @@ * screenshots to the host over the CN1SS WebSocket, then prints * CN1SS:SUITE:FINISHED and exits. */ +@Android(gradleDep = {"implementation 'com.google.android.material:material:1.12.0'"}, useAndroidX = true) +@Ios(newStorageLocation = true, uiscene = true) public class FidelityApp extends Lifecycle { @Override public void runApp() { diff --git a/scripts/gamebuilder/common/codenameone_settings.properties b/scripts/gamebuilder/common/codenameone_settings.properties index f38391dcdd3..40645679eea 100644 --- a/scripts/gamebuilder/common/codenameone_settings.properties +++ b/scripts/gamebuilder/common/codenameone_settings.properties @@ -7,11 +7,5 @@ codename1.secondaryTitle=Game Builder codename1.icon=icon.png codename1.cssTheme=true codename1.arg.java.version=17 -codename1.arg.nativeTheme=modern -codename1.arg.ios.themeMode=modern -codename1.arg.and.themeMode=modern -codename1.arg.desktop.width=1280 -codename1.arg.desktop.height=800 -# native window chrome: OS title bar + native macOS/Windows menu bar (File/Edit/View…) -codename1.arg.desktop.titleBar=native +# native window chrome: OS title bar + native macOS/Windows menu bar (File/Edit/View…) codename1.kotlin=false diff --git a/scripts/gamebuilder/common/src/main/java/com/codename1/gamebuilder/GameBuilder.java b/scripts/gamebuilder/common/src/main/java/com/codename1/gamebuilder/GameBuilder.java index 7dcfcd8c72e..315f0637232 100644 --- a/scripts/gamebuilder/common/src/main/java/com/codename1/gamebuilder/GameBuilder.java +++ b/scripts/gamebuilder/common/src/main/java/com/codename1/gamebuilder/GameBuilder.java @@ -75,6 +75,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import com.codename1.annotations.buildhints.*; /// The Codename One game builder: a visual level / map editor for the /// `com.codename1.gaming` engine, adapted from the "GameForge" design. @@ -82,6 +83,10 @@ /// Every control is wired; the live preview plays in-place (toggle, no navigation) so /// there is never a dead-end screen. Behavior and structure are covered by tests /// (EditorControllerTest, GameBuilderStructureHarness). +@Android(themeMode = AndroidThemeMode.MODERN) +@Build(nativeTheme = NativeThemeMode.MODERN) +@Desktop(height = 800, titleBar = DesktopTitleBar.NATIVE, width = 1280) +@Ios(themeMode = IosThemeMode.MODERN) public class GameBuilder extends Lifecycle { private EditorController controller; diff --git a/scripts/gen-build-hint-annotations.sh b/scripts/gen-build-hint-annotations.sh new file mode 100755 index 00000000000..27fb5ebb814 --- /dev/null +++ b/scripts/gen-build-hint-annotations.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# Regenerates the build hint annotations in +# CodenameOne/src/com/codename1/annotations/buildhints from the catalog in +# maven/build-hint-catalog, along with the BuildHintAnnotationBinding table the +# annotation processor reads back. +# +# scripts/gen-build-hint-annotations.sh # write into the tree +# scripts/gen-build-hint-annotations.sh --check # fail if anything changed +# +# The output is checked in. CodenameOne/src is compiled by the Maven core +# module, the Ant/NetBeans project and the IDE projects alike, and only the +# first would see sources generated into target/ -- the others would quietly +# build a codenameone-core.jar without the annotations in it. Checking the +# sources in also means @Ios( completes in the IDE, which is the point. +set -euo pipefail + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +REPO_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)" +CATALOG="$REPO_ROOT/maven/build-hint-catalog" +CLASSES="$CATALOG/target/classes" +ANN_ROOT="$REPO_ROOT/CodenameOne/src" +CATALOG_SRC="$CATALOG/src/main/java" + +check=0 +[ "${1:-}" = "--check" ] && check=1 + +if [ ! -f "$CLASSES/com/codename1/build/shared/BuildHintCodeGenerator.class" ]; then + echo "gen-build-hint-annotations: building the catalog" >&2 + (cd "$REPO_ROOT/maven" && mvn -q -B -pl build-hint-catalog package -DskipTests) +fi + +SKILL_REF="$REPO_ROOT/scripts/initializr/common/src/main/resources/skill/references/build-hints.md" +JAVASE_SRC="$REPO_ROOT/Ports/JavaSE/src" +GUIDE_TABLE="$REPO_ROOT/docs/developer-guide/_generated-build-hints.adoc" + +java -cp "$CLASSES" com.codename1.build.shared.BuildHintCodeGenerator \ + "$ANN_ROOT" "$CATALOG_SRC" "$SKILL_REF" "$JAVASE_SRC" "$GUIDE_TABLE" + +if [ "$check" -eq 1 ]; then + targets=("CodenameOne/src/com/codename1/annotations/buildhints" + "maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java" + "scripts/initializr/common/src/main/resources/skill/references/build-hints.md" + "Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java" + "docs/developer-guide/_generated-build-hints.adoc") + if ! git -C "$REPO_ROOT" diff --quiet -- "${targets[@]}" \ + || [ -n "$(git -C "$REPO_ROOT" ls-files --others --exclude-standard -- "${targets[@]}")" ]; then + echo "::error::Generated build hint annotations are out of date." >&2 + echo "Run scripts/gen-build-hint-annotations.sh and commit the result." >&2 + git -C "$REPO_ROOT" --no-pager diff -- "${targets[@]}" >&2 || true + git -C "$REPO_ROOT" ls-files --others --exclude-standard -- "${targets[@]}" >&2 || true + exit 1 + fi + echo "gen-build-hint-annotations: generated sources are up to date" +fi diff --git a/scripts/guibuilder/common/codenameone_settings.properties b/scripts/guibuilder/common/codenameone_settings.properties index 0722854a1c0..75cad311745 100644 --- a/scripts/guibuilder/common/codenameone_settings.properties +++ b/scripts/guibuilder/common/codenameone_settings.properties @@ -5,8 +5,3 @@ codename1.version=1.0 codename1.vendor=Codename One codename1.cssTheme=true codename1.arg.java.version=17 -codename1.arg.nativeTheme=modern -codename1.arg.desktop.width=1440 -codename1.arg.desktop.height=900 -codename1.arg.desktop.titleBar=native -codename1.arg.desktop.interactiveScrollbars=true diff --git a/scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/CodenameOneGUIBuilder.java b/scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/CodenameOneGUIBuilder.java index 113ff6259df..7ce5d7759bc 100644 --- a/scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/CodenameOneGUIBuilder.java +++ b/scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/CodenameOneGUIBuilder.java @@ -82,7 +82,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.codename1.annotations.buildhints.*; +@Build(nativeTheme = NativeThemeMode.MODERN) +@Desktop(height = 900, interactiveScrollbars = true, titleBar = DesktopTitleBar.NATIVE, width = 1440) public class CodenameOneGUIBuilder extends Lifecycle { private static CodenameOneGUIBuilder active; private ProjectBinding binding; diff --git a/scripts/hellocodenameone/common/codenameone_settings.properties b/scripts/hellocodenameone/common/codenameone_settings.properties index 1fbf5370aeb..aac7c5faf7b 100644 --- a/scripts/hellocodenameone/common/codenameone_settings.properties +++ b/scripts/hellocodenameone/common/codenameone_settings.properties @@ -7,15 +7,8 @@ codename1.arg.android.androidAuto.poi=true codename1.arg.android.health.privacyPolicyUrl=https\://www.codenameone.com/privacy-policy.html codename1.arg.android.health.read=steps,heart_rate codename1.arg.android.health.write=steps -codename1.arg.android.useAndroidX=true -codename1.arg.ios.applicationQueriesSchemes=cydia codename1.arg.ios.carplay.audio=true codename1.arg.ios.maps.provider=apple -codename1.arg.ios.newStorageLocation=true -codename1.arg.ios.NSCameraUsageDescription=Used by the CI smoke test to verify the com.codename1.camera native bridge compiles. The app never opens a camera session. -codename1.arg.ios.NSHealthShareUsageDescription=Used by the CI smoke test to verify the com.codename1.health native bridge compiles. The app never reads real health data. -codename1.arg.ios.NSHealthUpdateUsageDescription=Used by the CI smoke test to verify the com.codename1.health write path compiles. The app never writes real health data. -codename1.arg.ios.uiscene=true codename1.arg.java.version=17 codename1.cssTheme=true codename1.displayName=HelloCodenameOne diff --git a/scripts/hellocodenameone/common/src/main/kotlin/com/codenameone/examples/hellocodenameone/HelloCodenameOne.kt b/scripts/hellocodenameone/common/src/main/kotlin/com/codenameone/examples/hellocodenameone/HelloCodenameOne.kt index 5855a688185..fb53dc11160 100644 --- a/scripts/hellocodenameone/common/src/main/kotlin/com/codenameone/examples/hellocodenameone/HelloCodenameOne.kt +++ b/scripts/hellocodenameone/common/src/main/kotlin/com/codenameone/examples/hellocodenameone/HelloCodenameOne.kt @@ -38,7 +38,11 @@ import com.codename1.ui.Display import com.codenameone.examples.hellocodenameone.tests.Cn1ssDeviceRunner import com.codenameone.examples.hellocodenameone.tests.Cn1ssDeviceRunnerReporter import com.codenameone.examples.hellocodenameone.tests.KotlinUiTest +import com.codename1.annotations.buildhints.* +@Android(useAndroidX = true) +@Ios(applicationQueriesSchemes = ["cydia"], newStorageLocation = true, uiscene = true) +@IosPrivacy(cameraUsageDescription = "Used by the CI smoke test to verify the com.codename1.camera native bridge compiles. The app never opens a camera session.", healthShareUsageDescription = "Used by the CI smoke test to verify the com.codename1.health native bridge compiles. The app never reads real health data.", healthUpdateUsageDescription = "Used by the CI smoke test to verify the com.codename1.health write path compiles. The app never writes real health data.") open class HelloCodenameOne : Lifecycle() { override fun init(context: Any?) { super.init(context) diff --git a/scripts/initializr/common/src/main/resources/barebones-src.zip b/scripts/initializr/common/src/main/resources/barebones-src.zip index 6f000dc9a5194aabf4c97420dfbee7ffb6ec6adb..da205fa34873e76436d9de37f5f7b71a6bdf1184 100644 GIT binary patch literal 1435 zcmWIWW@h1H00H5A{}?a>O0WXyti-ZJ{Q#UwIAKbX^K)T9KGrkdul>xi~iE zxs{0p1z=4gKxqyJfh4imYYU|^f|(f@PO>pDsN&Y-Tj^L(;Fp-2st58;ZHRCFZ3}_D zzr%TIG@cw-y79ogS64b7@9@~RjalW`w}=auZYVhwa@0i#iv4|WJ>|)fo4Jdc6^ic7 zHm{NCo%(aKjlz=<&YEdcuWGDvi?WvG2}wQ4`oWQH>h_K~Tqc_i*}H@!Z4?y0Ec_xj zM9R6-^;mao$S#ekod05XDnyY)`JNWWv| z$A)=}PG8(?Ik~%=G3LOw5V>z&%2zHOX5_V!+|UrqJYC4E{rCFAMl7%Q`>2)1?pIN5 z{uArlw%K(*^LI9V^&+A9-?o~_?5+*)ylk>_`n%ZiNYAwQRGIc2?C@fXVv zZ@HxjVM=kgZcYoHdzbx9Krrv6#OXq}R{vy@b57WnzqGoS??HfA-JDHV{aPE?Y_@8w zyI;i1_etpU@@bKrGn|+1*}lT`_64~f*-31RDvd&ncdjg35^R;(rT1X5?>ggu8l`{q z8GYA8oGy@`Wx)1r`;jLC&MFzH68S;9jI-7oL@Vb-Q+Ku3xf;_xr@VXV%_T+5Tas^`;LWjBFmvXZ%oHTAwmIr19-4b@`tT z4;CdBnZ0U!`O|G((*4M}xjErCKZG6RFWM#1_i)y{@V6y`zGhOs@fp_*S-$Oj61V){ z?$Z}^E!?}lWS*;7Dzr&slkvUE52fY{pH=QUJ@pai$<3O0Y7>Fk~f`CF+NUa56BLZJC%^0mP*h+zgB?Ul|z~SVVvd z18{2RglSLC&n43cL1ZIRD-v@Ha#G1ON*tR}xs{0p1q2NQd6NSiI&8~Rj2;7hewLYm zK^3=wzLkyz1%8RSsd}K`gNI)7XN=IR4ff5yZNRhlcesF^rK4K%0Y7hPowHV3vM({j zHpsCbU6!0UN#lj`I>Rsf{rm2!9G&ng!Q)AF|Chxn6(8H*3vdPT#P2W?nwhoJ@Un{F zsR>^n*nD4Pp}?gTSll{4QXuE!iDQ?|FZ^DUJ=gu{v_*#&>8ivvxK8xuGE}YfKDfJE zz)^lepMo z-P^Ig@}9}rz$qrj_MWZd2??okhH; z?@{M9_3}KtFZ>06;NsIfM(KN^4C1EUlzeYf*;Tfp^5V^nA|Hc}7N&5_PqTdzs~%X` zUgxZ`{Z88Az8zhQH#ePT)jV3db7B0&9?@4Hu3ekpp`LD*QNRAt5fpM85-x9`v1 z9lK*~d>));(F-p#HN7cN{)Ea!tx|O!*S(ENDu%sD8rIQIUofNO;8ZP+zc@sJ%8e| Vij@s$5(6s`?gSc^0L=6Z3;^vFL8|cuy!d@9mo|?Ps)*u0qi%#`IRqZ=1PtSk3a;D|QXX3Luaz}cQlaj{$ z<*!DzHq767*1h@F4ISgN)7_cL+PBwjHQ^>{+;9nCW*=6p$XZPMy_8as^TEMnfAUxP z_uL;XwoA1K8jF&*HD!fgNgsC95X~6bB3vQoe@=)?aNs$*(e`-=2M8(fC4y=*2a{Zq zG^fj4Jxn#F@43Z$d-V><#mSK-hcmuJ+0V6VY+5$Cd`2#`%feqd<#_n$A2SVA?ELif z*`q>SXFYnGP3JW~bYGMTLoGQeh_~lTx4V&QSWv)=57x`}zU*C+iOQi5$5S+q>UjIL zopVbI)xOa-^_F&SaA|{rPxPDM_v2em9mu5bP7!pfc9jv`Bd~7Q`NX}tOl98ool+Lx z89|JR$Gf!Xy>4dT6RMr9RW27rzb&ed*Cut7ALQm8CO%hE_UK?ROLE-VLsi_R=(k}f zTb&j?(zM^?&SlOFxg?7J@NvCf#-^TDd*_j}Xdy%M89BX?MvMP*NApzf527oBJ`^na zsp;QVjrkL0l4HFM3Rf4JzYX5bt}Tw&Y?Z(}>_s1hNa0L$6%CE+++!D=@fHy#^xc2F zG@IQre67~0%4M^Ln)|%&t&SqXcbZ7oIq9vKlCs(wKrN85afg= z4XDam?&>k;v>>DV*BfVTGX6Cs!ZhKD5~X^uyKw7vTS^E`%Q-{SJx!L(?36D>M?->5 z$L6XemtJ1|BrC1DEz)Ii6KxB{+3l&HWeq)t9mkb)&jWVn!$iGnJ*mzE91(O>jyjd4 zwb);zKGJs~W$Bmbb+h}ZEl(~N{I&K%d@6}NN3|<64~#uKUyye;Y*|k!%4H-He#xdD z2pGTxzpMWI`UZ_uPTy}>r}V0{)Nv^);_1Y%HM`yQ-Gpx)v}~KOnkq_)NLe>mL)CK# zWq;0#JjL5uZbtsNX(PKKwScSQcuHoE(k(~-7#dE}l%_H6X<+Jk%V1`F>XTH!^)oUr zmoC1uJmIpuU)1#QiQ&p5TS&|(QG8{5Rxt6^94uS-{AI?ZtcoyZGhz1KKFPa$eDT}+ zdRH6Y$C+y$dFPQY=#kxwUpb4eAJ;0p<48Q7w3Pm2q3Jp7C02q3ydQz1BJTU%*tcnN zU;7JBe;b--&@k?#2tMw0Oq+CI)X<}l?$Go6Av{T7NvuzV9Bh|xU7KwpzW7V~<#;8+bl|1oH+4`7IqD7y_}8nRL?N=;gu zH{T53d~f#rk!gOSMZq9{e$0^XVO@QBD_=N}x1BO-Ub$zjxn2B8`u1Z1AKMf8_oUa< zx^f)E{Lb{&hHu%cxS63&AC#JLc&L}>K{_#iFYQpy*rAs>J_`DFfv3~p)mEp586&B> z&bxYi`fMfWSg^lZ+1L1^%&Woh<7GY4!#zXg{NS|Tw1sAA5Z!W;2_EM0_#mFM1U?CTn zqID{8go}|4tH6kIoD|gVz!c$CR{^yHqqqJCh`{C!OvoVyTOH<2^ZptO61xEz?P0;FZmLH=P>VmS3*j;XGbgCgkDgJHiSIMM^8VQmiAgT*>|HTH{Gx+Gj}jw~!Ld%T5x(xlEc43XbT80D7t5ex zACQ7?o&!}>wHylbfHtH(!))91VcX&=p+_IsB(Rc-@FWiqM2aiH-$@8{c^Esl5__C-SFm(Vk;lP5e1HckZWI>_lzy^KDg3ix@t-=xu{LFAi UTfvpbftX@l6OSNS?Epjn0rK{?1poj5 delta 2227 zcmZuy2|Scr8-M3L!_$EV& zHs)$Ykr|A2w9NgA`fl;b&6ImfSM|NP_x`@^yub6F-|so+Jm-J@&-47x5Lb!MRch2F z66CCp%I$mgjCw{%9zo7(BM1Rmfka0|S|mqC`0QMYfGzK154&MKA(@2W@W0{^1dm|t zBwZ{&^iFkl!mw&M_?lDJOTY0RN@0hb zwOBiwoiyPk-RU;AKki3WF*mn2@mPee`^)chCv9g>tzcfHZ=rt+wZMo}lRJ2z5W_W|OO$#AW>|<*${!`P;L* zE?p~sZ!}YD6@BTN=D5O&^MT3*&TN~tu0^emXDE($FV(g1*q=|niHPZwR920ideqjl zmzg#tzbPyKlM?=<bDVVf|@Fj;g#d2VB-nJHt*^?v=^8WR`lT5u*VMe;?= z$x-nn%i+=E;ydgRF22|GpMKjq4WC4gt8fgAb8K0&OIB?a*Q`9)Y>>I?)ii!^WmRr^ z{WwcZ%)frf;HsDCj>xDX#LKK>MzUPt<1HHgm+%zzz_j7J=UT$6jdhhr25F9#gO+=D zSWSP4CaX5s7+RaBXiL*;1Kd6K|HI+%LuUMYD?jfmHx2UN#Tsk>eV1C|FSV= zf7CfE{P^;UjPObOYiNH-eQIn>Olqo!1yL;eoSDG~@?aMwi>nFBTGtB+NTYi5wHl1hO8#+qd z&9wdTYrM11Ykr+pc45jv2X1hT^&RH;r*ws|w95>`I#(0#M)>ONk$^N zGun52RG%1n%6{eMTJh_lTV-*Z#u($vo%NUQ3qFk>ikz6-8$$JeB`(C zWk=_V$!6td8yq^KthPg8HfO2EQx9oG5sT^cZBp6MXUcxhnmuN1M;+=@;*=F+@}!Uv z+RVVcV#RA;p2#K+WNlQUl*AVo^Q+t1%B81Ga(umaoLDU=$nTGzoB8dQ{taM5>S$IrhMpU=n-6wn4crx`IW#fBT)noDnKxS9jd4f*<7H*t|pNMDRn?o zvmP}j3M{I@4pboKf-5K`y<*0HUiJQLm{uYYVT^=i8WeT`Q_{RWHi2RBeNa41>jo>J zT{8|3b%lV4p2R^@Ay|V};h?_|Y#?mK*vXzoc4jQL6t-rWeqV2@9L%e`I9Jeme=ai@-W! z34(^IG{E z&NVnBEGlju%34%~2YY&f6~TtLXzUjPEA#*lZWaSa^db+Q5Mu|5d9X{2Eq1Jjb7HU_ z_2R>IePA7$$cKmefR4d`JV@AGjsIX_k-XGA081`|Rj{`Yn4-H1;cOq+fHDi=+J3MJ z2}Xuu5tQ`($jsBMw(LzO*29qUZjS{H{ z87dVmB3ldk?!7ZIrf>RHbKmDV&pG$pcgAyG%jsOn?>Rv=9*pn?78D8<5fFLkaD-n# zq#F42Eg%Y@z(b}Op;XCt0Q8rRDESVpq|QlA^E-4<4V2==2%`m2C{W4_BSgLuhpsd% zFcR<;ECapYNJg+^WFtO|hmpLZMp*&J%rR`_B}MWQmVBkUO6WDhuw?-vj3`vFPqdF( zrZ77*R5bD)E+kW!jt2!6NMiV)cS6WJ5k!Gb9J!Fh#{!}-F3Ic^6%?Wh2NH&X3JkZT zFTW?9s_278b`)w-7lo3i@EaJIaoz6jk;9w$6Bf%%mV0mR%5B&<`Lxwe=hQ}np|11W z&f`13e2J0dKF_%e#d_TNW#ymv9>6O7hJsBs-fqsDXY65WWQ*SYnTbL1qn5&&dligx zh2PRMj?euVuVgqZ#S_(TWv_0wHD)8<8%rL2@!7OpEtU44c3OlO*zrEM9jWNWZL1GF z7Zqpj_>IjyzV9) z1CP>`oW_+1%D828U&cmT@@q-~e7$~;s&*dB7L?QyI;SP{eTELpDbFRftJPLW!?s&H z%g_D(#9lnBT+TJF8f!}~4y#l~TikxjshmC!mI&(`Y5beaHoa}Nx>2OO@%o9ay4&&? z6z#`7%(lHSF}TXhC!pCaPgh0XeZS>e*R*8%E$RI2y?5pGh2nh^r7Yd1mn|)-GH&Ni z@hog>^0lz6-P36xevi}Vcvjvu02Sure%R#ti0R|Ri;DDTF^QMkc884(`wMSf#+BZV zx!I|ne5p(K1TgYcZ%=Hi)wMz0^k9raXX?VV=eO9jJ@ZrBz88?b4b-)q+h-C|$j$Uh zEYjf^|F%cy3G1Cc{QFNnc5a;6F!A}&@RfyWnJkvXr^79w2T&`hjGgi>^Tzi(4;PW- zT?UqmN%{1j#MGirSqWY-+VF4(^Vu6Yd)R9mJ~)o#aT&{*T@=n$+R%^}+bJhd zBIW3gNjO_?2w<_HGMR@^UweKE9`BJ+bUIsqVFDHF};^M~K9dpm}8b_4* z^Cq5O-nY-av*x&oMj>E9)PH5f6&NRiOVd>zVqVT5{0ub1Ha&XLxy3q$cSqu-?s1P_ zVtRwqj?c;^V~tKq3GSJ0zZct4UCf{&`0NsI(p^Wp%9J;O;T%_vb7KVuC!1%T`xf)> ztE4b5mJN%LTI%fFQe46EQMBCai7>xBq%i+!owR2`#n=m@g|xz4G52=v4z*wmV->Qx=R$yG)m%>rW?RH0S*$ zirZG>X7d5rqZ8)#3c7{U5;BqfcfG0;F6R)0_n;$3EowypL+=RP$GjPCgs@wRmGtLv zXE&D9?WN!AJD=s2q$qpgbjYD0$GD_!MoVm1ZAO$izFy8!wj}W2LsbpEMmZDxC#O~d zI0~&sk{{?C?jV9iwx6-LF4>L>3&p9c$}I2)ikEG7+hluSDK!37!ss0?5kQKR%*akB zx*&5Ws!LGPwQTls>R`s0(-9|Zlt`V00B$Vgq=yMAWsvQM_pR*#y3$t{nU*}}%7X?@ zdX=2!YfRq0d$aaaskltZon6NDa?*=#K#XBxb+&4(?g^JuTRl(gPM z+~81)NT7AM(*=`glNk~3#Pr>)nm_KwAJ+OZ;?X}ma*5+>C}9z>d;G!a#p1Ph>LJyn zy*Bzi3@>%dYumAWLleq?SkR0q^BCajBFns zpwUWVHkajFB{<)*nagWcYx^%eb~LJaEPk2Ce|~SG{%@6$9dT$Ywu6b|3D=JteHp~= zZo^kyS2gc*#9^3$`G73nhf90rW}}HlF$2M*ChqD{%om?V7k81Y;b)0IE-O6<+3EXW z6HZk6^L!wI!{)l1-gHPuqa%0Ze^TG4{ES z*QBciS2V6;(1YbQl8!vvAwkKf`vzzUt}PE=xk%- zfRGK(TTQtk?gefqV0^!`%s|Vg02T8)DygqCW#<50`#5C|tahvOA@x;}S{$aG&$)oY-)TBr;88_#O9h(doTPTAj-= zYc1Pq25T43#W@!bu*PwRnA79UN=7ke1?+c*zVCH>KXp|4=9~|EUbp>{Mtgy3N%H~! zEyK58j~0lP*bxGaDt57MzHWFYwZK=Ht+FilBCfpc5_A9ChL5V88p>%O9Z$S6G}H@d z?CFs2@meZv@f7YEX9|oQ@!#yrg39n}hzXDQwba)6_?X}4*H??nO&aMVFJD-q!wDXp zhQ8}CI$x?(ilRHKE52JkG>YyCO?WlWxAgnO<*`3k6R@r|m`~>AvzL43!9yjBdUX=J z!;5lo+p`tUiVk?^9VQ^~i( z2p*04`Vp%5QMIdKT5Tiyj#f6$`)NFMK4>npgWj-V`j4xOngmbHY954v0*_aFAYz}cgH zxXSY$Lq?#YDbFdzkMXe`0jSOd2iDcr${LSr>HDk{3C1sPB^gMyE_lD()7u;v z_%v5zJ0~tI?M?6Uow?or?KRH3xj_JrIlm=3&uGhqE6+O!&q0PG^LJ}rzuT32bR{P6 zqUiOD&+QhL{J+gy^)8VT_q7nRDel{q`)hD?;>G)luQ|hd;~(hST!cvMLasG4jRu}? zfMF*t?t2|6VhPsy-F=2zB({)@(L!#a(|?E`3-j=u*(A3e{aCT9uR6Y53LnbpLU~A7dL<5=`c&5QmB)OmPsDepq!Y6h zN_mmG3d$p2r%k)kZ!?+Pt>qnQJfk6UAUZ_Z?uSuW>;*Gv*1_4u7EhUU@O41XD8~_3 z(IE312mI@IxlQJd(1?2n&mxdP}$|FkdXapA~Og2O8!)mqF?Ms#rxM)RqG{QT;cUNbw$3=%46b7>tW|;;ent4 zQu*Sxh$8VCzxKqPlXq)`3_1>m1Ru&&>2X-ux^S*MY`@|{r;OMQwRpek7>Sg4OXTM**8~xz%5mHlExP^dhXu%7VupG#RI@USCvd(6v03RWp>4 zSFnq)Wl)Pn^-Q@ymguwnuJ4=jrmP=#T$kzNj>6zi7G%df#E&h?X0km4v=iL8?`hac z6GF23?&jDi8RFtp=1NfPzsew7s9~z8_V#w(`7Xll*R|CxwJ? zXit)pflR0I&-5FjrKsOe6867oZmT*~kAB_!i#@jd+6Rw9-SqTlpTC?i?itztW-9h7 zcy_i=!~IRDZ!{nHIZ*h^EFm>S&hdU~`KJE;_0KQs*p2y0oSAl#w79x3#pjPwFU=;7 zdIO<4vjM)xtU{XIEShcJux4#4elmJ2>ZJ1TGaqlx3!OPvV)fv|i8`waKkfb)Mnb;y zTq%2w>&5bT7x6xqN!7Y>*KjxY?mUqnKiZDJFMnb`=@``BYZSyv*!Hu_ydAYNf;L_u zkDqk1lvxj&M@1cM&=_85q+~+rhT40mN~U$#FrZM&JSddj8Vldg!%JY`-N%58lJFdh z&lct-vkLsXIf+5So`%v?OTA(kLBfzp>e>=MZ7UCfxb^p%I(~3F z4}r-#lpd}?8Q)-!jK<2Kg8~MFXK~;n=r}w@*vjB`NvIB=-7-k&)`U-P#fY#v=p}{W zCoFn3Qh4vBFd_sY|1bnq2E(K=5`^jfofPzsG)9OZd{78Us1BZy!SLhtGvx{O5g?g? z$3+VfDx)(P$rXhr@DQ#f9Hoef%VPK-x(Fd7Ih7)KoxCJZ$W2Y4EGfuA1sy)^N3IXg zl*v!f&%8&G7t9tVIAlLTu8`Up!5(=G7vXr03%LpKz4H;Y^ZF=iFBPDQ_g$+;E+s)u z5rBbai{MoI4F>3;0|RK+=#aMvLcr0oCqd+HKKc2Bl3Ex>uoeq7 zmb;Rj3E2g3BnxP$gkd9u*PBy#@EV@f%uGPH{9yh|V*nMEp*!8x4N^k$gQ3v7%8;Bz ze*`5IUc-;Q<|b?y30hNQz+ZXCM|k?~I?^L@Z6H<^y3;2U>1)L?fn}=DCaU;WL>-#2 zuz_%EL6w!NkR<>RAga*;7RX~;)lYQ8Vo=02Vffh=Ayz zfFsC>4Dlxqu(*~}kZ2(SxUF3?fC`!z4&pZ{z-p~)44@|#5Fut`fz@iE_VN+WXaIfG zrC!y-2or0KfJ4-BPysx|1Pef%nwhVSVJCjJ2WHu+>KE9A;Uuc>1>RCURZ!O-;3oz{ z|4=lcrVA7MgMky&H+gO|RB}lK;6U}XpeMr!s>A`zM8`P5nF1Wwfm*bj40x;&SwSXU zD7x$n;7Tp2nhllhnGO72D~5&3KEgjBVqG)KE&__wVJigyClPxMsG%f4wjoO~)I8H- zplMBu0gTs&vN~1(oD?Q}_y*po0YoSr83owD0|rn>#A*S~)lbMMzzTjcz;J-c4}gxp zL{@OWA!Y-p-VKNm>l*-WYWB~E02@)N8E~dv^oDW~yV`(I$|96ojF{C4SW}Zcj4>OC zkft2<;-E2<99rZhHuVC`)QHO#=zb(#0OZd9r^qf z!Tt)MV5|qikkeQMlUWrL&cNSIvG`aa2N!BG<669J4k2U!h({=1RrO9nRoJvOF( zuKyk$Q-|)*MKtlyQ8GEDETv2oQjMb}49kIBLNqBakOA0ifSI(QjvW#O`woypm?qL} zCmDn+2$)F=dN|SHpDD^CZUkf#p$WD0fKeR=^6lYws0yxYLz6dI8FDHhizrQOvp@Mx zVABGo)1p8S8HN3e3U&8jqeb^Lt&9PKLUl2rP@5?2Avc~glpF?o957ytCS_0r8HMf6 zf8sQt+7SeVjSrYW3;M^ALC6FBzm3n92*VQO-$;Z=&?xVwMpx|;I73VGJ3%D^Td38x zhPbV7Yy0UnBEH;Be3cjPwVqd(xsHcH>=$@eil)4#>@^w)Te4NX@crxcj^t7iVY9YM zEC^XoJbi_V2-`SNU4|y<6fKs~WA_ z3QeU{p^!IREp{boeX%iwH694NM$kre$TP!2MjLcdr-9w{C@^ff!691M&y)iHW4=K* z4H~KctPrja*iVZn*i%$sLk`+&(gfagp}?>;|L1dV|8Z8aniYlmpov0ht^1sx-HUWj z8?@7+QOpjeD8fD-d`1hKhf`qK(Stj*X`(M4qrkAY|EHc)nO^VOA^%|v{lL>+S5MO^ zN*rwe!7ZC;WM5@cV0aJ!8);!IG}}UZQ4ciQOcOYnM+t<71F)JF4lVk-OUYxx>Q>22 zrr9bhWoXbx2crvC=+LCV-$yjC$OHkZ>C(V*O_U1YSpqDfh27c_Sn=Pf0#wqYk!t9s zNWt^PKh1T?HhOpiYHkYDT$6Rpz1xpGY&j4fK|m#a8ribf6j^u}A%AzRH<)wdXt2Tv zqePGRZ18RkO>Yd|Ljoi~37J&>sY}uv?Nz$$&A38_#JHMp`$hw$`|@m0D0smY>*2;{SOIv1+D-9 delta 13701 zcmb7~c|26@`^U#QV^4P3ce0MH>}4q>N-0!C3rVYNAu^UMNtTp@h#sOMTV-jpW+_ob z3n|o7c29~T`OTRbW1R0C(@!t2{<+@Q^|`NmIp=(CVV}8X&v8Rznt-qzBSsW+WdEuC zYPL4481PL=!r^Yhua6%)h5`G70fWKvVK5kCs3PlpsI&WV`r#liMidGO0Qf}dWfo;1 z1`017*sB2ugW{x|5_-g(U9{D1vjmB%SunaMw(i+$g{#QLVlY1J7|cpC4qep(SV`sJ z9F7zJGvcybEua|73n#b~Wh`mE41?j;CM8JV#wy?`^$+|n``p37t4muTXsXb7w!~Yx zJt@)q&|s;9*+sGA2RF$yJ$E^E%21tkQ8w^&^{#BpOHCDU?4L?}02={N697M;`=Z)2F#gmWC* z*nk7SR#|V>@@WlR@t^)WO+l+oc}63#O(!?kzt8J+cWe`nT^kbsjpe*~chq;~8|^c% zuf(5ssdI_bMAbR zvq$P!6U^0;x1M+{&$4^pTib! z@0kSu2*JbEoLvkZ`q{1?=5Hj#*|~)VOy$NZS_98Am_$UM#$&3&KM#hPF(;l_c0_imBsG|z*0w#@$afK39fe(+_*Aj#{$}YYljpM++&hx zoayxI7T#=>1{Uu&m)tj{64Cce-B_r~E}HGm2Aw@V4I{qBA5M1-P31^L*|2!Oyt4d0 zZxl02jDXO+dM)Foan1Y6^-fZ@N)748GB3Uj~c(^*7UQ=H7IYfRrdL3nZ5Fm7j4`2sSNS4LPYU$CQ@IrLFMEl){O{gRF8Ay!oZ4`VVPl_J zEjXO{6R_*Tey-j4s|wf?yfewp{$$`WU8T-XQ&ETWUn$fyRhyVUGSJ`1oh0%zXXwuL zSEGB(%Dyl&b2)5JN$11iEec(@f)n1mw@zL3VmNnT!}~N~r8Kz0`*65=j^WXo$(;N0?)5LirS4m<`($A=#8&%!nn9mI?yg|8R`A%P z?8*?iNv;juk48t_B1;|Z&gQ6RYJGh44-k3?vJS>+8lG&}{LDP0_Rc>1wE39g=`5Y$ zoVQaCU$_c6#5Z@IF;C+^?%`u!$nmqOYqWBWv8rTq|JjfGJ6A$n3h_TUcpuAOc9PFn z&hq;}?wju}wR}USPsa9_YzlZ7Y`x*3x#G-!yxi7YHatBRtYMB^8MhuNr|||!YYDsA z$@V0(O|_Zl6)A zKb!J6=JqdalyQrPU4WtUkym?cS+kj7yK0Uv(0DK4W6PXd;x#2 z1LHCIYLs1}aPmfF%%frv)!w;-EbVtg@9h*Haq>*=%zwF2{b-e-^Wz;B1x0L{8=In@ zNNkQ?Ze#LA^8C19KBqjB-Pt7Ftx-mjb#=G1duw$;L84C6{0_TyvE9a zcUSTx_Tqz)-i{j|(ogBg4|522)#68Q6m#-)UV7SkXV>@hJ()WGuSdG8w&S}Qh6nuK zyb(^Ac^UFERQXY_!nvMwE9>uexmpo&E?H@Qll^g*>cTU^M>y5oCE0|VZhUA>%NJ0Y zkjIGdJ6A?r?E(WGt^nqG+UkvcjXi85&n0Jv)Jh^{Ma(y5EH6D<@Ny$Vro})v-rL#c zQnR*$80(1~-4EFV+H*4j4n;1e-JjMw)HsDI1&eOc_7vL_ptC)@-Ze_B!pqfhShS+( z>)zuW7G0HN4L$W{7~HRP-3Z}R>s(^-@*VYlY2B?!v#R;Gl~wJx=H4DIvM#hY=t|z| z`hf4L^V;V!mxViQ6`=&43yoHB9i8}_X%%a;oK|-{)ol3kZAV1;^$WRzDn|N6V@*+g z9k&t{PJfq9J+ZQ@_1ZDqrZb5_MshnY5B`a`z#pJ+{$Bwp{(*@BT-BB;@tK`$_Ul@h zdH?jd%w_&^viT5tpZQ6lOT?xrFjvEe|h?#wyx_a-UKaMj#$-79`bOGvHPQ9x?$7F_uHBi@(#IG-3)GhJ!&NK z>3J4DBv!8(tMM+c-PB^gw9*^h;$!vphqYIIjVm4r>M@-sA4CQzE}7QbV^8e7biA)A0iTw6$S>!qfXZP6=@6>+1U#--&P ziRDZ$2leH0yOtOJ^qb1co4u%6w@QRVf7@nRiFSMd4vf`E4ZO(pAYWuK>1VRqKw5>w zx|koYe|chxti@kc*VZ@^;a9DBG*mr9;lFhnSA_@HPm!i zRaC71Pr4TWKo!i>`E$X2w7g+Z*tBL?<4}Wy%*VIoUuN2KXAVi9QRetF`>k!6s^l-< zODqX$c>jU%Q{f45H&0gdLC}bfk{^b_=Si5sqfB4Bi?^TdvKn^&^+fi#S~l}F&a6Vo zqh>#zSlI6kAD>!RIR~g*eyT0+u^^4sFLuTI^gI{uK z^F%}+>*~(UT+1qP=X_~hMu&TMmRCxX_mes?#nbBrdD9Wsvi}_J0R!|`dv3ar-{rj7u{+7=mVtY9a={~P+|BbMV;g-| zGD81M*6^KUPhZ{N^3;Ua*Iv*4&6G@qLtOXj{<^ACAMwgN_p9*E#{&I+?KlVRw)7M>U@4mM^mXqvs?ENnOf(xfAMNBIVYJa}hS=-`* z=es(0m)S2Rr2ec}lkirdB&qe8>MzFDxU7=^+!TgmI-Nd>vx2W5zkRmXeZ5p0M{>1q zT{~m8T;&Dkxt85)x0ivRd&kY!-F9{PS~t=f8)M2MGkj3e{czFEXngfm35zSOiM(z6 z-vtCXb|{89++ulL8`;DK8u#-CW;}P}OXk>U3b8^qx_+Ni%JhuG)(qn{CAE@OrJpSS zrup8pw~W7oJyTO9NO4zvuoP5Yns>?O?S;ziU2y`LwcDO>3XlKr`@MRH=efB&$!xD9 zlD&Mo7}wYDbu;#_{HooB*UBys%aS|P2h6ruzP@Cl)m!@^$%(zNJ0P+hvuS)%fGL8j z_)OH3(uRxdQ9DZhN!^yb|8}_g70qK?KTHP*Wh8ux58TMZs#T?NhqJBU+v7Iyg(a>i zC|o@MRzgmfP+ZS)}vY zO{TWyd*71^&JBJ^EE+lK%={^s&3m5CxR*o}Io#LO#BDxSgRR;0kv(Pg?dHnB7_r^4 zLJgY+cL=U9OE|5}HsoflC2}+{Y_~f1hS$rRy;F~w0|`4)HP({2w32URD-@sten zT}ip$Z>-|~8giKZP~5k~NE-~(?phn zhxYPd$scR*N0Gz-e%O=^ZvYN4k-paCq0?LDzwOY%W48b=!1-`hUaY{vCl{^kSFC~e zl<*8&fO{c)4-GLzwm=C*%rq~SDx6mK)IESHc3zAVA9lq;c3R=W`++)&m_|N&oe??+ zU@6(v__52E&c6LH@QxB5&yVF=I^6jLAWaF+_XGr&4zKeBA}Qfc0$ARq!^3@n8S+r| zmX5q10GwbX^w1*&Q2u|cHs&Mgoh2X!!^ncc7!g(*0XJSKMB@v(tT+T{fhEC(puBME zg+?p9G8~YBmjW?-pCDFzA^UzBD@P>;$b4~r2tg2XodHr08Nza?dgEfLUvlC7$!u_PXgOa#4^t}ilq>sT1d3QMG66b%6PwtQ12NU1Aqu(M9RkR0Fe|$PNG=; zB?k>Ns%4+c06z-zOqAZBGadlWi>G^y7##+#v$D*+3OG-xlP881UC>6uGJ**6CsO8E z2NY8n;;azX7S(>G)i=9FU?+Jd)u8v!sSiF{;iJ!iHn>FfOJMzyipm() zVx*L@iqI$)qm+ z!AqLhO|caq)loq!Tzdt0j?$TO87$dqg`eBB!so=mTuOMeEJ){J_D&YWvCoUase@Ec zRvO#B%uF3z4<9N-?S(oZ)sCn2Ag3YTC|z^Xxo zCg8HAyZOKboT7~PjXB7(5Ke0VgXW+;CH%ZRJu&`UL37F~9+t;aJv3;I7uZGB{8lHB z&QN=tz&3L8jTU6nN<4l9bcbaVnxeZ9N$Y?~_5j5x6WSdJQf(w!i3b9~36A+08dRX( zrnJI&&VzlFgJZKIy?v*i01Cje2^&cXs!*iY?O+HDr>rjJ40`R;&j7bkb~Ry}(mBQI zazQ)FTpAaFR1ZX2D`iI!xQCpb&YRUhIk=l5J71aJ;Tuo|&QN-oR!_Z$XzhpMdQfpb zdr}KXHAq^jxmv&q${-0xHPvH}R&w^&U=>A+xhnMp(a2u<9^`_PbAJYv7FHpRM-pW4 z8H8_!;6R8GNBw$FYt1Hp2D#uzT`wb68EXAXeP+=Jhu(h$8|IUbPk~tZt)4CiJ#p|& z809%hi~BPLij_?dfIP%!yc(gFo*MPBPJ=01Hv-y`!?hQNOe^xmICuh%B#c=Znp2}U z=78^@1vy*^B8-CSoTinXpk1C4zIBFrYtRaJngPWq;W>Zkh2Q%FHdDgkmO@1UmT}4P zLIg!>WuE}-Z-6v&YnBWz6KBGzQ%d-=((C(KR;&dG^=klvN^FFyOV5K|?mnK*2favl zm+-@n#X&aC zLP(+Tg(o3YVxcjpp)#aU*}n)?B8Nhm0RZz~#H*3w)#--9y(7q#gYqWXmm^wZe94IwR}~~&M#Dc`m=P2BfAY;WBkATEF-H&q5!GVFP(w!Z5NW2C06BRV zfuWFz79b#KYqJz0vWf|03=Bajb#UMa%07plLkb%oX2E` zoF);(8}0Q(bu_STmJEP62;bzB?!*^gX;Y^QWg-fIiny_?=qiZHU)7S35I~N`_?+?AiVGG|tQ!j%+0SF;+Vp24lQv8fT)Z za>AY-`iOc(5#4wgN5vAS0yg;mTGZUmd&)(GwIxM3CpRn-^&*ifF>C;$i)s)ijRn1I7Jen7ko%!VLuTan?Yj9 zH6#r7711$!DMYmo&G0Y{>5VWS3od@WXz3u8E6OE&NWpF+dQBB+M9tK03d>G(9qCnP z;6YS(=qF_`;sN>53(p`6+kS`T zqz8|yfr6aWZ@c#y2yf4@e+|2q^k@$q1ih1$Ov5H7J=#eRLGNQH(-cn=y=BMcu;Pn^ zhx0Nln44xxzq>BH_DBCHfYuUjeMPu280|#|-78_T7})(p*P$nm&=D#{=3pBXol8Y> zB1&Wq_Cx8lF2M=WItdLj4cnvWRozx1)S9j#az=mMut%%?w*!Q~8nR|r2X7^-h8i5yD_LTcaY!yEj9d?PWB+<$jh1PgTtj%3R20HsbQewR zni!%EJ#^{z$_VMhiDU_|Z;Q@VB00lUG6x&D=$yO?qTa)6Bo20R>CJ0#Ct_Y3Zjp<^ z#x8o%No3K_MPv?kc+oldm4aJw(JRG<3I?Kyiw+g2B1U+Vj1YZabWQ+=;J6<#EEvG( zTp*IeJtj$nO;oHjwG~WoduaG6P%kn=Ni_rHF>eRco0O1o#H2VpA=29Dl5^D%Lltcz zN+#LF=p2_if~#y^7%JTd=CT=tQ2iHV8n%zot9Bu)-fkyzu#b$+nP?!?w!U6aOEQ$v z;cH0PqKmj1)}SNonuvO@ddT%)cNv|7SGhjhqE#N<$FRs@roC`TMqGl*{A&ookt29` zEqx3`$9d_3y2}?8RO7``6{Ogiv{ox|QwlDkeR;7&3wr62+>71GCK4n;2a;J-Ns5nH zNtO<@*i;E3LqvmmNpk+Zh~x?PkvR1w5##}7$xl*9G_C27xJ6?tm?vTT8i)QG#z=VS U!e9;%{=LLuFaiREFJR370p4L*g#Z8m diff --git a/scripts/initializr/common/src/main/resources/kotlin-src.zip b/scripts/initializr/common/src/main/resources/kotlin-src.zip index 32c52cdb23f951135e2fe1da62acbf084a784591..62c4b0d91ebf286b04879864fe10fdc228030322 100644 GIT binary patch literal 1563 zcmWIWW@h1H00FH${}?a>O0Waz?EI3P%sl-7oQk*+ijwnl@hB8VRhU|lm|KvOibsPa zb`80ei3J5x)!DsdqijNQ3BT(`3GXeYCo(K%NZwqn_vya3*Qeu2lbNm_ zoYvO=l(WK)^8o-t;;7^c{{3F#Xk1;vUc}Wch*}~nw%3U zIepQt8d=%fGd6@3Z$7w4S7%>eR?n`yAf@lieqPJ)zHNSX|77p)rqe&lcLeOuJRkM!rquo?&fjgrLWY%2AACQE1b(`}W_7pscGF|GHv(Bv z-f?-eOkM9wXDnj)cfp$X*3=IUC#9BKIGmq)ak<+eDc!e@>~EZ%uV2>gm?7leDch=Ug-E`K|t>JXUt@lNG6u4MtUC2XTkH*1KPZesGXNc@vqS!MrBil=~ zVl}s)QW1~rK>-bqdi9&ftGjj{{u{jLKzWkiU!Mc{6P%hKuT$TV;QOOx;p)T7qipV- zv(t6Ic|7yHg}pmp`uYX=hIZfiZ-r^ zeJ#MAB=a)L>*R0G*FqaJD}VjcTg$=Mde!jIqPbQ3Mdq%1up{e}V9n_U`CA*!+Olt$ z@Hst}sNQn&@Z_Vviqvij{yaLj@w`Y*rD5A+)$m+9soM7kHv9a&lyv@IYwgpL0{e&B zi%TM(rR$|{yS%XBm#B&P9@EPD1Lg;`Euy{UAKd+YuT{Ik(tkpSi+AV72U~?J4&Dif z{rqC>i_Y%&53!aXqwh%{*3Yhfeej?t%W?m`>g~!WFQ0l67E|MPdN-@$-;Ao72l3Y0COKx@1rJas5C|~5bp+AyVhFb;P(cI%AWdkc06yD51rtJ}ACL*P z4OE^XiYeSSg9<7L0NGp*WMVM@R&0Tc!Ci2Hj9~=gC5_wg8G~Gm;qw|O{2>73GA9-k Ym!ZZ6E3oKfU|<8nKA;22fl3(|08`W^mH+?% literal 1507 zcmWIWW@h1H00FH${}?a>O0YA?Fl6VKK{oO5@f+C#qLdW0l{rO?G#Kiw1)-7{F+x(p*J7;FK z8hSdN+VGWKQmmn57Brd z&$D`6(~Sk+^kzSu7oq>?c<3q--Rq6jhy7O{HuF?l9LS`6Bjm{fmCg?uGH(JFi^_4| z*!Cf}j)(EPRHMny3w(b!-M@UQ`th>Kou3c=vJ=kcsWz2#Iw@Aunvj)!Bk{~()tu}} ztS@CV9BsNPGY0habwKjfMye5Zh zujfmFdBPms(p#o+wQRbUkS)8zRxDguC4?nk@m`+Q4%LbT?e2pTzh0lbRbufcwjrn} zy*!|7_JZE!?muV72dZ@`?VS*l@cgjHt25DoGqx9e{=j3=%I$pr@apopDkU5C`#hNW zH0*{%vMkT$gR?BPMfGwI$2)({zVdbDK4k-m?~Hz{=O4(iy{)tF&e<^gXWP4f7V+et zdRQOwSgX!wx7zoU4^wqa7p`k&S3h~P>D}=cDYX-yCvu1J$OW_|I(?{j`hNPum90~j z_xu*eojJr&(@k=IN@^Z3q2%WgO52P~a?H3gtOPXc3NXBN1To>+mlcwIF|sah!?+oc z4Fj15H4KuKNjFd!#lSXH0}P48DxrU|1H%XFRS94haZgerH(Hs0O5v0s`i4 ti1Db|9iLU$BTyX0Cvm{i1<5MZ6oSieRyLqB8Q6eOj){R`J` is forwarded to the build server. Note that +`java.version` stays here on purpose -- it picks the toolchain that compiles the app, +so it is resolved before any of the app's own classes exist. + +Most other build hints are better written as annotations on the main class, where the +compiler checks them: + +```java +@Ios(includePush = false, deploymentTarget = "14.0", teamId = "ABCDEF1234") +@Android(xpermissions = "...") +public class MyAppName extends Lifecycle { +} ``` -Anything prefixed `codename1.arg..` is forwarded to the build server. See [`build-hints.md`](build-hints.md) for the curated index of build hints. The complete reference is in the Codename One Developer Guide at . +Declaring the same hint in both places fails the build. See +[`build-hints.md`](build-hints.md) for which hints have an annotation and which are +still set in the properties file. ## Layout invariants diff --git a/scripts/initializr/common/src/main/resources/skill/references/build-hints.md b/scripts/initializr/common/src/main/resources/skill/references/build-hints.md index 3ed3c1b6580..136d14fa16c 100644 --- a/scripts/initializr/common/src/main/resources/skill/references/build-hints.md +++ b/scripts/initializr/common/src/main/resources/skill/references/build-hints.md @@ -1,72 +1,185 @@ # Build Hints Reference -Build hints are key/value pairs in `common/codenameone_settings.properties` that are forwarded to the Codename One build server. Every key starts with `codename1.arg.` (the build server strips that prefix). They control native platform behaviour that cannot be expressed in Java/CSS: permissions, frameworks, splash screens, signing, platform SDK versions, etc. +Build hints control native platform behaviour that cannot be expressed in Java or CSS: permissions, frameworks, plist entries, signing, SDK versions. There are two ways to set one, and you should prefer the first. -This file is a curated index of the most commonly needed hints. The complete authoritative reference is in the Codename One Developer Guide: +## 1. Annotations on the main class (preferred) -- — full guide -- — editing build hints from the simulator's *Build Hints* menu -- — variable substitution syntax for hints +Most commonly used hints have a typed annotation in `com.codename1.annotations.buildhints`. Put them on the class named by `codename1.mainName`: -When in doubt, search the developer guide for the exact key name — there are hundreds of hints and only the ones you actually need are listed here. +```java +import com.codename1.annotations.buildhints.*; + +@Ios(newStorageLocation = true, deploymentTarget = "14.0", pods = {"Firebase/Core"}) +@Android(minSdkVersion = 24, useAndroidX = true) +@Desktop(titleBar = DesktopTitleBar.NATIVE) +public class MyApplication extends Lifecycle { +} +``` + +**Use this form whenever the hint appears in the generated table below.** The compiler checks it: a misspelled name is an unknown symbol, a wrong value type is a type error, and a value outside a hint's supported set is an unknown enum constant. An attribute you do not set is not written at all, so the build's own default still applies. + +## 2. `common/codenameone_settings.properties` (everything else) + +Hints with no annotation, and open-ended families such as `android.permission.`, are set as `codename1.arg.=` lines. This form still works exactly as it always has and nothing validates it — a misspelled key is accepted, never read, and silently does nothing. + +**Setting the same hint in both places fails the build.** Move a hint rather than copying it. + +## Annotated hints + +Every attribute below is generated from the build hint catalog, so it is always in step with what the builders actually read. + + + + +### `@IosPrivacy` + +| Attribute | Type | Build hint | +| --- | --- | --- | +| `calendarsFullAccessUsageDescription` | `String` | `codename1.arg.ios.NSCalendarsFullAccessUsageDescription` | +| `calendarsUsageDescription` | `String` | `codename1.arg.ios.NSCalendarsUsageDescription` | +| `calendarsWriteOnlyAccessUsageDescription` | `String` | `codename1.arg.ios.NSCalendarsWriteOnlyAccessUsageDescription` | +| `cameraUsageDescription` | `String` | `codename1.arg.ios.NSCameraUsageDescription` | +| `healthShareUsageDescription` | `String` | `codename1.arg.ios.NSHealthShareUsageDescription` | +| `healthUpdateUsageDescription` | `String` | `codename1.arg.ios.NSHealthUpdateUsageDescription` | +| `localNetworkUsageDescription` | `String` | `codename1.arg.ios.NSLocalNetworkUsageDescription` | +| `locationAlwaysAndWhenInUseUsageDescription` | `String` | `codename1.arg.ios.NSLocationAlwaysAndWhenInUseUsageDescription` | +| `locationAlwaysUsageDescription` | `String` | `codename1.arg.ios.NSLocationAlwaysUsageDescription` | +| `locationWhenInUseUsageDescription` | `String` | `codename1.arg.ios.NSLocationWhenInUseUsageDescription` | +| `microphoneUsageDescription` | `String` | `codename1.arg.ios.NSMicrophoneUsageDescription` | +| `remindersFullAccessUsageDescription` | `String` | `codename1.arg.ios.NSRemindersFullAccessUsageDescription` | +| `remindersUsageDescription` | `String` | `codename1.arg.ios.NSRemindersUsageDescription` | + +### `@Ios` + +| Attribute | Type | Build hint | +| --- | --- | --- | +| `addLibs` | `String[]` | `codename1.arg.ios.add_libs` | +| `applicationQueriesSchemes` | `String[]` | `codename1.arg.ios.applicationQueriesSchemes` | +| `beforeFinishLaunching` | `String` | `codename1.arg.ios.beforeFinishLaunching` | +| `bundleVersion` | `String` | `codename1.arg.ios.bundleVersion` | +| `dependencyManager` | `IosDependencyManager.AUTO\|COCOAPODS\|SPM\|BOTH\|NONE` | `codename1.arg.ios.dependencyManager` | +| `deploymentTarget` | `String` | `codename1.arg.ios.deployment_target` | +| `glAppDelegateHeader` | `String` | `codename1.arg.ios.glAppDelegateHeader` | +| `includePush` | `boolean` | `codename1.arg.ios.includePush` | +| `interfaceOrientation` | `String` | `codename1.arg.ios.interface_orientation` | +| `minDeploymentTarget` | `String` | `codename1.arg.ios.minDeploymentTarget` | +| `newStorageLocation` | `boolean` | `codename1.arg.ios.newStorageLocation` | +| `objC` | `boolean` | `codename1.arg.ios.objC` | +| `plistInject` | `String` | `codename1.arg.ios.plistInject` | +| `pods` | `String[]` | `codename1.arg.ios.pods` | +| `podsPlatform` | `String` | `codename1.arg.ios.pods.platform` | +| `podsSources` | `String[]` | `codename1.arg.ios.pods.sources` | +| `prerenderedIcon` | `boolean` | `codename1.arg.ios.prerendered_icon` | +| `projectType` | `IosProjectType.IOS\|IPAD\|IPHONE` | `codename1.arg.ios.project_type` | +| `spmPackages` | `String[]` | `codename1.arg.ios.spm.packages` | +| `teamId` | `String` | `codename1.arg.ios.teamId` | +| `themeMode` | `IosThemeMode.AUTO\|MODERN\|IOS7\|LEGACY` | `codename1.arg.ios.themeMode` | +| `uiscene` | `boolean` | `codename1.arg.ios.uiscene` | +| `urlScheme` | `String` | `codename1.arg.ios.urlScheme` | + +### `@Android` + +| Attribute | Type | Build hint | +| --- | --- | --- | +| `activityLaunchMode` | `String` | `codename1.arg.android.activity.launchMode` | +| `appBundle` | `boolean` | `codename1.arg.android.appBundle` | +| `buildToolsVersion` | `String` | `codename1.arg.android.buildToolsVersion` | +| `captureRecord` | `String` | `codename1.arg.android.captureRecord` | +| `debug` | `boolean` | `codename1.arg.android.debug` | +| `disableR8` | `boolean` | `codename1.arg.android.disableR8` | +| `enableProguard` | `boolean` | `codename1.arg.android.enableProguard` | +| `gradleDep` | `String[]` | `codename1.arg.android.gradleDep` | +| `hideStatusBar` | `boolean` | `codename1.arg.android.hideStatusBar` | +| `installLocation` | `InstallLocation.AUTO\|INTERNAL_ONLY\|PREFER_EXTERNAL` | `codename1.arg.android.installLocation` | +| `licenseKey` | `String` | `codename1.arg.android.licenseKey` | +| `minSdkVersion` | `int` | `codename1.arg.android.min_sdk_version` | +| `multidex` | `boolean` | `codename1.arg.android.multidex` | +| `newFirebaseMessaging` | `boolean` | `codename1.arg.android.newFirebaseMessaging` | +| `proguardKeep` | `String[]` | `codename1.arg.android.proguardKeep` | +| `release` | `boolean` | `codename1.arg.android.release` | +| `repositories` | `String[]` | `codename1.arg.android.repositories` | +| `targetSDKVersion` | `int` | `codename1.arg.android.targetSDKVersion` | +| `themeMode` | `AndroidThemeMode.AUTO\|MODERN\|HOLOLIGHT\|LEGACY` | `codename1.arg.and.themeMode` | +| `topDependency` | `String[]` | `codename1.arg.android.topDependency` | +| `useAndroidX` | `boolean` | `codename1.arg.android.useAndroidX` | +| `xapplication` | `String` | `codename1.arg.android.xapplication` | +| `xgradle` | `String[]` | `codename1.arg.android.xgradle` | +| `xpermissions` | `String` | `codename1.arg.android.xpermissions` | + +### `@Desktop` + +| Attribute | Type | Build hint | +| --- | --- | --- | +| `adaptToRetina` | `boolean` | `codename1.arg.desktop.adaptToRetina` | +| `fullscreen` | `boolean` | `codename1.arg.desktop.fullscreen` | +| `height` | `int` | `codename1.arg.desktop.height` | +| `interactiveScrollbars` | `boolean` | `codename1.arg.desktop.interactiveScrollbars` | +| `resizable` | `boolean` | `codename1.arg.desktop.resizable` | +| `titleBar` | `DesktopTitleBar.NATIVE\|CUSTOM\|TOOLBAR` | `codename1.arg.desktop.titleBar` | +| `width` | `int` | `codename1.arg.desktop.width` | + +### `@OnDeviceDebug` + +| Attribute | Type | Build hint | +| --- | --- | --- | +| `android` | `boolean` | `codename1.arg.android.onDeviceDebug` | +| `ios` | `boolean` | `codename1.arg.ios.onDeviceDebug` | +| `iosProxyHost` | `String` | `codename1.arg.ios.onDeviceDebug.proxyHost` | +| `iosProxyPort` | `int` | `codename1.arg.ios.onDeviceDebug.proxyPort` | +| `iosWaitForAttach` | `boolean` | `codename1.arg.ios.onDeviceDebug.waitForAttach` | + +### `@Build` + +| Attribute | Type | Build hint | +| --- | --- | --- | +| `facebookAppId` | `String` | `codename1.arg.facebook.appId` | +| `gcmSenderId` | `String` | `codename1.arg.gcm.sender_id` | +| `nativeTheme` | `NativeThemeMode.MODERN\|LEGACY\|CUSTOM` | `codename1.arg.nativeTheme` | +| `noExtraResources` | `boolean` | `codename1.arg.noExtraResources` | + +### `@Hardening` + +| Attribute | Type | Build hint | +| --- | --- | --- | +| `allowUnhardenedLocalBuild` | `boolean` | `codename1.arg.harden.allowUnhardenedLocalBuild` | +| `controlFlow` | `HardenControlFlow.OFF\|ON` | `codename1.arg.harden.controlFlow` | +| `keep` | `String` | `codename1.arg.harden.keep` | +| `level` | `HardenLevel.OFF\|STANDARD\|AGGRESSIVE\|PARANOID` | `codename1.arg.harden.level` | +| `rename` | `boolean` | `codename1.arg.harden.rename` | +| `strings` | `HardenStrings.OFF\|CONSTANTS\|ALL` | `codename1.arg.harden.strings` | + + + +## Hints with no annotation yet + +These are set in `common/codenameone_settings.properties`. ## Universal | Hint | Effect | | --- | --- | | `codename1.arg.java.version=17` | **Required.** Picks the JDK 17 build server toolchain. | -| `codename1.arg.build.compile=true` | Run the ahead-of-time / bytecode-to-native compile (recommended for iOS, smaller binaries). | -| `codename1.arg.build.timeout=180` | Build server timeout in minutes. Bump for very large apps. | | `codename1.arg.var.=...` | Define a custom variable referenced as `${var.name}` elsewhere in hints. | ## iOS | Hint | Effect | | --- | --- | -| `codename1.arg.ios.deployment_target=14.0` | Minimum iOS version. Set to the lowest iOS you actually support. | -| `codename1.arg.ios.teamId=ABCDEF1234` | Apple Developer Team ID; used by `ios-source` Xcode projects for code signing. | -| `codename1.arg.ios.includePush=true` | Include APNs entitlements + frameworks for push. | -| `codename1.arg.ios.add_libs=libsqlite3.0.dylib;libxml2.dylib` | Link extra system libraries. | -| `codename1.arg.ios.pods=Firebase/Core,Firebase/Analytics` | CocoaPods to include. | -| `codename1.arg.ios.pods.platform=14.0` | Pod platform target (must be >= deployment_target). | -| `codename1.arg.ios.pods.sources=https://github.com/CocoaPods/Specs.git` | Custom Pod source repos. | -| `codename1.arg.ios.objC=true` | Allow the iOS port to use Objective-C runtime features the strict mode would block. | -| `codename1.arg.ios.NSCameraUsageDescription=...` | Camera privacy description in `Info.plist`. See *iOS privacy strings* below for the pattern. | -| `codename1.arg.ios.NSLocationWhenInUseUsageDescription=...` | Location (in-use) privacy description. | | `codename1.arg.ios.NSPhotoLibraryUsageDescription=...` | Photo library privacy description. | -| `codename1.arg.ios.NSMicrophoneUsageDescription=...` | Microphone privacy description. | -| `codename1.arg.ios.plistInject=...raw XML...` | Inject raw `……` snippets into `Info.plist` for keys that don't have a dedicated `ios.NS*` hint above. | -| `codename1.arg.ios.glAppDelegateHeader=#import "MyHeader.h"` | Prepend custom imports to the generated AppDelegate. | | `codename1.arg.ios.statusbar_hidden=true` | Hide the iOS status bar. | -| `codename1.arg.ios.beforeFinishLaunching=...` | Native code inserted before iOS's `application:didFinishLaunchingWithOptions:` returns. | -| `codename1.arg.ios.newStorageLocation=true` | Use modern iOS storage paths (recommended for new apps). | | `codename1.arg.ios.wallet.extension=true` | Generate an Apple Wallet issuer-provisioning extension (iOS 14+). See *Apple Wallet issuer provisioning* below. | ## Android | Hint | Effect | | --- | --- | -| `codename1.arg.android.googlePlayVersion=true` | Build the Google Play–compatible APK/AAB variant. | -| `codename1.arg.android.sdkVersion=34` | Compile-time Android SDK. | -| `codename1.arg.android.targetSDKVersion=34` | Target SDK in the manifest (drives Play Store acceptance). | -| `codename1.arg.android.minSdkVersion=24` | Minimum Android API level. | -| `codename1.arg.android.buildToolsVersion=34.0.0` | Android build-tools version. | -| `codename1.arg.android.xPermissions=` | Inject extra `` lines into the manifest. | -| `codename1.arg.android.xapplication=` | Inject XML inside the manifest's `` element. | -| `codename1.arg.android.activity.launchMode=singleTask` | Launch mode for the main activity. | | `codename1.arg.android.statusbar_hidden=true` | Hide the Android status bar. | -| `codename1.arg.android.debug=false` | Whether to build a debug APK in addition to release. | -| `codename1.arg.android.licenseKey=...` | Google Play licensing key. | -| `codename1.arg.android.release=true` | Treat the build as a release (R8/ProGuard on, etc.). | -| `codename1.arg.android.proguardKeep=...` | Extra ProGuard `-keep` rules. | -| `codename1.arg.android.gradleDep=implementation 'com.example:lib:1.0'` | Inject Gradle dependencies. | ## Push notifications | Hint | Effect | | --- | --- | -| `gcm.sender_id=1234567890` | Firebase/GCM sender ID for Android push. | -| `codename1.arg.ios.includePush=true` | Pair with the FCM/APNs setup on the iOS side. | ## iOS privacy strings (`Info.plist`) @@ -112,8 +225,6 @@ This is an advanced, issuer-only feature — most apps never need it. The compil | `codename1.arg.javascript.proxy.allowedTargets=...` | Restrict the generated proxy to comma-separated origins, hosts, or wildcard subdomains. | | `codename1.arg.javascript.proxy.url=...` | Use an externally hosted proxy URL. This suppresses generated packaging unless `javascript.proxy.target` is also explicit. | | `codename1.arg.javascript.inject_proxy=false` | Disable proxy generation and proxy URL injection. | -| `codename1.arg.javascript.html5=true` | Emit modern ES output. | -| `codename1.arg.javascript.bundleResources=true` | Inline `theme.res` into the bundle (faster cold start). | ## Variable substitution diff --git a/scripts/initializr/common/src/main/resources/skill/references/mobile-adaptability.md b/scripts/initializr/common/src/main/resources/skill/references/mobile-adaptability.md index 39310e63e20..219c2f8abe4 100644 --- a/scripts/initializr/common/src/main/resources/skill/references/mobile-adaptability.md +++ b/scripts/initializr/common/src/main/resources/skill/references/mobile-adaptability.md @@ -222,6 +222,6 @@ The CN1 simulator has a "Skin" menu — pick "iPhone 15 Pro", "Pixel 8", "iPad", ## What CN1 explicitly does NOT do - Dark mode is opt-in: write a `@media (prefers-color-scheme: dark) { ... }` block in `theme.css` to recolor UIIDs (see `references/css.md`). For runtime overrides — toggling dark mode in-app regardless of system preference — call `Display.getInstance().setDarkMode(Boolean)`; read the current state with `Display.getInstance().isDarkMode()`. -- Orientation lock at runtime — call `Display.getInstance().lockOrientation(boolean portrait)` to pin the orientation while the app is running; `unlockOrientation()` releases it. Check `canForceOrientation()` first (some browsers / JavaScript runtimes don't allow it outside full-screen mode). The old `codename1.arg.ios.orientation` / `codename1.arg.android.screenOrientation` build hints are **discouraged** — `lockOrientation` works portably and dynamically across all platforms. +- Orientation lock at runtime — call `Display.getInstance().lockOrientation(boolean portrait)` to pin the orientation while the app is running; `unlockOrientation()` releases it. Check `canForceOrientation()` first (some browsers / JavaScript runtimes don't allow it outside full-screen mode). The `codename1.arg.ios.interface_orientation` build hint is **discouraged** (and Android has no equivalent hint) — `lockOrientation` works portably and dynamically across all platforms. - No automatic "iPad split view" support (the macOS-style split view) — design master/detail manually with `BorderLayout`. - No `vh` / `vw` units in CSS. Use percent insets in `LayeredLayoutConstraint`. diff --git a/scripts/initializr/common/src/main/resources/skill/references/native-interfaces.md b/scripts/initializr/common/src/main/resources/skill/references/native-interfaces.md index 37edc5cfceb..d06bbc2d55a 100644 --- a/scripts/initializr/common/src/main/resources/skill/references/native-interfaces.md +++ b/scripts/initializr/common/src/main/resources/skill/references/native-interfaces.md @@ -109,18 +109,21 @@ This step matters because every platform has a different stub layout, naming con The CN1 iOS port runs **without ARC** for these `.m` files (`CLANG_ENABLE_OBJC_ARC=NO`). Don't rely on autorelease-pool magic; retain manually or use static singletons for objects whose lifetime needs to outlive a method call. (This is also true for native code authored in `Ports/iOSPort/nativeSources/`.) -iOS Info.plist privacy strings have **dedicated build hint names** — set them directly, don't fall back to `ios.plistInject`. The pattern is `ios.=`: +iOS Info.plist privacy strings have **dedicated, compiler-checked names**. Set them with `@IosPrivacy` on the main class rather than hand-writing plist XML: -```properties -codename1.arg.ios.NSCameraUsageDescription=Scan QR codes to pair the device. -codename1.arg.ios.NSLocationWhenInUseUsageDescription=Find nearby branches near your location. -codename1.arg.ios.NSPhotoLibraryUsageDescription=Attach photos to support tickets. -codename1.arg.ios.NSMicrophoneUsageDescription=Record voice notes. +```java +@IosPrivacy( + cameraUsageDescription = "Scan QR codes to pair the device.", + locationWhenInUseUsageDescription = "Find nearby branches near your location.", + microphoneUsageDescription = "Record voice notes." +) +public class MyAppName extends Lifecycle { +} ``` -App Store builds reject location, camera, microphone, photo, contacts, etc. without the appropriate descriptions. Use `ios.plistInject` only for raw XML keys that don't have a dedicated hint. +App Store builds reject location, camera, microphone, photo, contacts, etc. without the appropriate descriptions. Use `@Ios(plistInject = "...")` only for raw XML keys that have no dedicated attribute. -If you need a CocoaPod dependency, add `codename1.arg.ios.pods=PodName,...` to `codenameone_settings.properties`. +If you need a CocoaPod dependency, add it with `@Ios(pods = {"PodName"})`. ### Android (Java) @@ -147,13 +150,15 @@ public class GpsBridgeImpl { } ``` -Permissions in the Android manifest are injected via `codename1.arg.android.xPermissions`. For example: +Permissions in the Android manifest are injected with `@Android(xpermissions = ...)`: -```properties -codename1.arg.android.xPermissions= +```java +@Android(xpermissions = "") +public class MyAppName extends Lifecycle { +} ``` -Extra Gradle dependencies go in `codename1.arg.android.gradleDep`. See `references/build-hints.md`. +Extra Gradle dependencies go in `@Android(gradleDep = {"implementation 'com.example:lib:1.0'"})`. See `references/build-hints.md`. ### JavaScript (TeaVM-friendly JS) @@ -299,6 +304,6 @@ navigator.geolocation.watchPosition(function(pos) { - **Method signature mismatch between the interface and the stub** — happens after you edit the Java interface but forget to regenerate. Re-run `mvn cn1:generate-native-interfaces -Dcn1.generateNativeInterfaces.overwrite=true` and re-apply your platform code. - **Returning Java objects** — not supported by the bridge marshaler. Return primitives, `String`, `byte[]`, or `PeerComponent` only. - **`PeerComponent` on iOS without ARC** — peer-component implementations can dangle if you treat the bridge like an ARC-managed Swift method. Retain natively, or wrap returned views in a static holder. -- **Permissions / Info.plist** — the build server happily accepts a native interface that calls a privacy-protected API, but the App Store / Play Store reject it. Set `codename1.arg.ios.plistInject` and `codename1.arg.android.xPermissions` (see `references/build-hints.md`). +- **Permissions / Info.plist** — the build server happily accepts a native interface that calls a privacy-protected API, but the App Store / Play Store reject it. Set `@IosPrivacy(...)` for the plist strings and `@Android(xpermissions = ...)` for the manifest (see `references/build-hints.md`). - **Forgetting `isSupported()` return** — defaults to `false`, so the Java side thinks the bridge isn't available. Always override. - **`NativeLookup.create()` returns null in the simulator only** — usually means the `javase/` impl class is missing or in the wrong package. diff --git a/scripts/initializr/common/src/main/resources/tweet-src.zip b/scripts/initializr/common/src/main/resources/tweet-src.zip index add354048e9050391925ac291ca7400f911061c2..431c10e5a9a8da29900812fcc29903d5d2366bcc 100644 GIT binary patch delta 4024 zcmZ`+2{=@38=l3eX~tkA+gOU37P}-{NTraHsVP$SeK!gzgOt9~hbB%%^k)mzr<5go z_L8!bElIMbP)s!cIkWWP`u}&Xxvq2G`+l}}dFGzFG2!Vx;W&M9!*wti%-@OXbH?An ziK=XFisV@j&a8z6UQ(v0Mqh|>0SSs*VXzG=c#ck9KF&@~j!HpZo&}$+P;Tqu5aMfK zFn7DNcFOc-32{C&$Y;Gd_;@MDiVF)ae|HM9^YZm{0_Tw;Fc@71EehEnH?p6B56-A{ z^k6sv&Vtylg>d1#QMkq$)|?#O7R^TG08LkvL|zv<7QsQTV+naRSUn!j>lQ)DJUhVx zq>zmLhEvILrzUE^G*3vuU_?%^VmB0y)^0iO)1!%JCYMJfA~*Pdc=e?p5z^&(t^P4R zs57R5^1k>mTv1hH=lU$!t;I%h%q8b7S_z-_mztfcA2W4ym(p*-j#Q?ppS!o+XjAzVveu5a5#DYe zR7K8&Mh=AB3S)ROH|P6$znY}he>RY(eKBz|C^<|x9;Kch(J#%IGW2bXeicyt!!pby zOGPGL{kQtireta`?8vT?2z6my|HZ@uInxx}-zo0TVMyeNz{G`y6X(ef3ATuz%9@^) z{6Ms&dADGRcjvbKQ*S)tb0st@_HTS`si!Kqbmnn#Quf<3$@GryZnT!{V3I_{6Pjs4 zTdWxUAo;bu+NrpKEw??+GN%H(z1%~+o+rE>rau`h87lI*TlRImyrW#Vk0K+vSKe$Q zDNemLOD+1I_?g2=%)%r=G|p1x#)GZd=@QanH^k)T?C?~j)?4k zR1gsksCjA0e>ifjg|8xk@^Ed6ak}LM-9sH-8EKZbDY11o1-pKeMXcUt}P+^Hb83hbc7MY@UzyTNNmab^<^2&L)OQKb60(5`CfdOfxQ?;;FdHan#qtR z&^c23vFK;l%d%i*x?v>0f&*oT<;#-Vg^m#u;>;Us#lf1MP4dPbQ%h_84D)C17qp)L z-RRUa$zK>_*zrBNu8^&uN93e z&yU+b3rj-AYyV89B%&zQAM)uP z4J?=(+aq@=+c6X)X^_X;nlh^w&{HXzcB!=4x24)Z#;2dI9OFbKl&-;AQ733PMyc_< ziEq@l;pXNFooW9a;>?lwFdzB3So0-c_1`~SXX|7{nX17=2~h)LY+apMNE=PjuQi;~ zrUuBkt)R?|+PPExk?{|{@dLj-2b!orEaHJn+D?RpD8fl0B1}e5N zb~R5b_#+OPC!A}qYzj0xT@^!1iV>7BkjP^?H=cN~NU{mrNwh0@6FH~*1}4;&SsQw6 zemdkMTzFwnGVZp(b!6K7Yl`BvJ^r! zt?a_DMWiV2Am&z4#p{4l*=*W}rG=5N5rG%y-S9h3pSe{s7E{ruDSSBJrFKryh`L=z zzxmi9^st;AmA=hekMPwz8EI>(!g2C@2vv|o)UELe_;PKJY%bQ*?X(iN}JjNqy zQsu@a>!2ocMx^AvvM*HR(#z9Lp?9w2t)i_Ro6YkZlEy@p zmHL|0ssirtr#m*O@9`i$RPwn~gfVoz5t&zG;^dg6R#KOK&HA0yfqvr}N0aNt`H{D+ zj_p1gd7L@&@x#Lb4B2amoY7A^b~EUFl|p~}ZUYI`}Qdh}a9%?^^nC_<96^*(D&gI2KqN{KR z7OhfC$gXqw4?oT0{gS1}Pw)Nbu80y%cL6wJg_1zZaO8z80Zo@Da1;gImO#k6MJVCr z1+|ZzBPc^~EExnoo^^8yq_Px@G7eHaf2JTAvr-*lW8|v3Kn{;63Wlkzz87!n3Sb<9)RZ1Eq~j4bdCj}S+yg08~rWp6a_ z7uX0P)~5pco^U*ekc8yHAV9^H>h{_sJlqxVNOT+?$ zZYY8Tq-og$0zY#>=xDI4Wg(IKFM$uUOagNyFGQ5$Ury7-uw^b3LJ*X|oB@UK<3Nxg z<~28kilkqFi*n4_73D7D&_R{VN>xyn>jnn-krIMJ$N?iRe`2$ZZ8!iVq5%)rRjBd? zmKhzC)WGy=@Ub`>1O>JtJ2m$D{|}wb$^{BI8D1pCE*4VVP^K%FqLRcy!TX8kma9xy zSuXPrg}|zG1J8;|rxn?kmbD**m1hR8wERM|tYx4}&K}S@X|G_g_VuyHvK*lkZ|~Q(7&R8&8f=JVHV-0P9t(thp~1clLbox4W55ovKM8L4bZ{0bo&lK)D}EinGb=jC1~Bq3q5DX!x&! z)1R=7Ic*HcUQMXUg9ysdE=W^X8MmASDyb+U;7dhucM|YEff8MjK!+_vQUfpntE{u^ eAV3a)8s7$UQxy2?1cQ;l&rvK4cG(_kP5%Wz$mbvc delta 5747 zcma)A2{@E%6#i%ViLobJV`7k^ER8#KE2-P!B3Y)SlqNGW)~p#zQ;H&W)2QJVT}unf za*0+Vp>A1IRF))_BBZqH{{Q?lGVaWDd3bo9^PcxT=R4my=bNF98MXa0*s?ecJrKYH z-qbLv@j8kWfEoHbi2?tFhT{Mf_B{#!u*v`cumf=#bEWu}9sz!cHBqtJLzuNJ4o~8F zg>C8KRKFd*bg>K$wiqm@$OBYE;W^rLP!0bRIe4Dk>>WV?VGJ5QC|JCtLL48#evX^< z!@I}3{{&l%l_5(a?sXAEQE|FY=Q&*}-r?2hN6>Vn2@y3ZNmRP>=TMbR|#xgU@ zFw%0D#>y0YDExa>&{33Cgi15-B9qR@aW;9NWdq;9uI`m5S-FNyP9j%wS*eZOvaLjJMZ;YC4d6l2ro!Tz(~8V3!u-xFS` z9ne2RUfjf}t4ufYQrnVjfB8J`*2$C5{-RV~`&)OFbGS(pp9<3Mm^{b+BG zmTmQ$RSzv6OV=AcD{#Onc+s!M7;HW}l9|%{W+2|mwsN60-JINYPkm%oxAYL_Z=ZC} z0~!_bM~IL1+@Yn%G>Q_emSGa9$3BHvqkj9O!GbtIx3!!7 zG$!gV8j%{}n(n2U+m+Y)DXQF&EKw-c>-{OW=ud5XU{>yELgGH%2()e@ZH}@+@gcP& zYW1at7*nZ6Es~bAsoCS^3x|~BleIsx>(3o>YjskSy?l9##kzzLm2*$>+ojUYu;s+q z4|)x!l~|QUJ)1imbW|MdOI6XALw~)uXHy>gpZn)E_hK3A-fXCeFc~#?tx@K+#_^A^ zvwh^q3mYT1?(4PAd89?9Cv%cqpPM;%!?A;D&+V!odlUOUQU{Kl z&Q5!~S!Sz!SjRmB%BdxX)53RFp3HJ;Z`YBGPIX>=-hmZd?%~_q{~)cxo@_HuPk!%b zkHR}kIp6gk7#wIwD~Mrh?*N zhAk>*ef(|8Zw4-&E;LJO=f1n@FKE*)wQ^`4Dqf22M(w?&GuHKzQ!(4pHPB^u^6+l; z7Y2iKH)cI$J=xi@EUVS0c$f3eM+LV6G?WT0;*G8QnMCQ3PZU~yu=x&_rFrB-zZcX-M%df6w>G?s zZjP$DVRp=J;9`cI&OmXXRUz$r(o1PJzIIx|a>Gr=j{}ojimtcSNX%URx^k)FoI|PZ zdecAZp?xvL{Q0)%ZB<_>?G?w=eOJuOCI%S&zFXG!Igq37XIffmqMZF{bi-JFU}=EO zT$*HZ=9#;f&ZMwhqhfLr4hEiodNWh^rM1Kt#&JoX*s6T3(hYhw>uc*P&Uwb`sorIs z>Ka=%ljE{a!g9T|@2cy!Zl|{fT=CC~Y+>c#dR4-$HD3B@xyzS+lls#4;6hi~Ys`yI zzp^X(E}EgWp=tGhbzBQ$slD8l^;3yE{Gl)CKj8&zT6Yq3Ke;?o9-9hXO z$-Hxi%r^IvyythNXWYNx(ym(;T_5Ua{&~^z`0CN1zW3}qloRV4YiBfOM_Rl~FClEN zPF|Kwb6%X0qPf2P6l+dPt>c=M&Jbou9Fr}#uscDyDLf{JG{0pu+DztlqT|Tdq~h+R zuO+#98iB)wx^qYR`xfo5vK@D;xlOuy+@9@qB#gM2YCS(SftXs7S`|T+*=1O0aO{0c z%PReKJ;c7&IJ?xKt@xcqhtWU1~1)>O4QADnj}ap=wC5Si`Yt~<5XKMO6-wH|JhE@9@~ ztg^zOuby6JrT=O9oZ7|_$F;uIjTi6CYSs1p^uDaXgpU>-<;Df{4{*yvtf@;AoS@&|9m+`%cCLJRfhM z<|ifwq_Qeg#?pe7R@3ZCYI4Y=tLut}ADeGNhjfnXmgf`%c2%=}_s>4(_>d7aUuVua z!aaf{?rT(aFPKvFqB3Yxi%M&;E!wRT74L4CjEnVj|HmnAt)jl{m`7(qjkl^H+t~Iy znq4`YKZo^#=|N!(5Bg6A_xA|^+f0Ab=-eh`a69N}h z=sS55#4JCyasuW*+koJ02Hu;d0Xt)j*US8&d$SGt532?Ms?cm!@Z#?`q85u2Mx)RQ z5GaFHT{tnmg)CYyjWpyK0RBvIbmRWP`-O*SAUJP(C^?nPsE2;0Zgo$M;lqIKjx#`6 z@w?6(lj0w?I6^RuD-8DB_g~M&5~fZWh`A2p_M&hJ9266!fiZ>DK4pLl5K|F8%Q9rJ z%2}MQP?Qql*@)ohD=s@_#4ywsc<+J&)ZyIl2Ic;be;gB~fH6bTr^6xmfJ*-dRZ)TF zsnX%>_Te+ZK?}(aFA`M`%mk*>2b2-d{mj$eEo5 zSvL9dk3d9SmVGY3*7AJna3IanKNB%rgdLgyK&p8~CQ6A?3HHfEkWe0Fe&%1x4Qz(D zW&ROQ07OK<(WQd?!h{c3flnaH{tFd6#16)(A^n0L91t4xvzC=K2X6-TmI+%L-7mnC zPNx}#^XgRuSCJb-niIGk)G!7Cs8evi6o7$c@E^Pv8i>5mrrcxBkOzn-NeO_K@CF}gX(JQC?YVBT7ul<00}5Af;jmp zCqnR2+5{GQn*!N;m9Mkx;mZxf=^b$BYS!xe+@!5f8WzW%8=6dH@*;MGE0ZXl(2MZuK1 zK_A^gDK#N*6Ds6Xkz(K!6kz$)fShc@8BU`wNJ36kNbya+VsH~4|ByIvAA%mm0DzG| znxCKJ$%mH`aq`{$rX*TB??>RgYak>7&b6sH8)pzn7`W7P1U!sxw$hlEzr38awd zIpI^0uC6>Jyt(i>g(2a-Z6?jj5Yir;HiDCO67ER}LzZs?36#mDe_Q}3xXTD{#}2k2 U^g$#5%!a;u cn1-settings-common jar + + + true + cn1-settings-common + + com.codenameone + codenameone-build-hint-catalog + com.codenameone codenameone-core @@ -74,13 +82,6 @@ ${project.basedir}/src/main/resources - - ${project.basedir}/../../../docs/developer-guide - - Advanced-Topics-Under-The-Hood.asciidoc - - com/codename1/settings/hints - @@ -131,7 +132,10 @@ org.apache.maven.plugins maven-surefire-plugin - true + + ${cn1.settings.skipTests} diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 6c230eeca6d..9b2c3666391 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -80,7 +80,12 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.codename1.annotations.buildhints.*; +@Android(themeMode = AndroidThemeMode.MODERN) +@Build(nativeTheme = NativeThemeMode.MODERN) +@Desktop(height = 820, interactiveScrollbars = true, titleBar = DesktopTitleBar.NATIVE, width = 1260) +@Ios(themeMode = IosThemeMode.MODERN) public class CodenameOneSettings extends Lifecycle { public enum Section { BASIC, BUILD_HINTS, EXTENSIONS, ADVANCED } @@ -90,7 +95,7 @@ public enum Section { BASIC, BUILD_HINTS, EXTENSIONS, ADVANCED } private ProjectBinding binding; private SettingsProperties settings; - private BuildHintCatalog buildHints = BuildHintCatalog.fallback(); + private BuildHintCatalog buildHints = BuildHintCatalog.load(); private Section section = Section.BASIC; private Form form; private Container page; @@ -221,38 +226,7 @@ private void loadProject() { } catch (Exception ex) { Log.e(ex); } - buildHints = loadBuildHints(binding.buildHintsDoc()); - } - } - - private BuildHintCatalog loadBuildHints(String docPath) { - InputStream in = null; - if (docPath != null && docPath.length() > 0) { - try { - String url = ProjectIO.fsUrl(docPath); - FileSystemStorage fs = FileSystemStorage.getInstance(); - if (fs.exists(url)) { - in = fs.openInputStream(url); - return BuildHintCatalog.fromAsciiDoc(Util.readToString(in, "UTF-8")); - } - } catch (Exception ex) { - Log.e(ex); - } finally { - Util.cleanup(in); - in = null; - } - } - try { - in = getClass().getResourceAsStream("/com/codename1/settings/hints/Advanced-Topics-Under-The-Hood.asciidoc"); - if (in == null) { - return BuildHintCatalog.fallback(); - } - return BuildHintCatalog.fromAsciiDoc(Util.readToString(in, "UTF-8")); - } catch (Exception ex) { - Log.e(ex); - return BuildHintCatalog.fallback(); - } finally { - Util.cleanup(in); + buildHints = BuildHintCatalog.load(); } } @@ -817,6 +791,20 @@ private boolean isValidHintValue(BuildHintMetadata meta, String value) { return true; } String v = value.trim(); + // A closed value domain is the one case where a wrong value is certain to + // be wrong: the builder compares against these strings and silently uses + // its default when it recognises none of them. + if (!meta.values().isEmpty()) { + for (String allowed : meta.values()) { + if (allowed.equalsIgnoreCase(v)) { + return true; + } + } + return false; + } + if (meta.type() == BuildHintType.BOOLEAN) { + return "true".equalsIgnoreCase(v) || "false".equalsIgnoreCase(v); + } if (meta.type() == BuildHintType.INTEGER) { return isDigits(v); } @@ -1544,9 +1532,6 @@ private void renderAdvanced() { Container c = card("Files"); actionRow(c, "Settings file", binding.settings(), () -> Display.getInstance().execute(ProjectIO.fsUrl(binding.settings()))); actionRow(c, "Common POM", binding.pom(), () -> Display.getInstance().execute(ProjectIO.fsUrl(binding.pom()))); - if (binding.buildHintsDoc() != null && binding.buildHintsDoc().length() > 0) { - actionRow(c, "Build-hints source", binding.buildHintsDoc(), () -> Display.getInstance().execute(ProjectIO.fsUrl(binding.buildHintsDoc()))); - } page.add(c); } diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintCatalog.java b/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintCatalog.java index e75d0bc3b5b..f6191f5d820 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintCatalog.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintCatalog.java @@ -1,5 +1,8 @@ package com.codename1.settings.hints; +import com.codename1.build.shared.BuildHints; +import com.codename1.build.shared.HintType; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -7,6 +10,15 @@ import java.util.List; import java.util.Map; +/** + * The build hints the Settings tool offers for editing. + * + *

Built from {@link BuildHints}, the same table the {@code @Ios} / {@code @Android} + * annotations are generated from and the same one the builders' drift gate checks. It + * used to be scraped out of the developer guide's AsciiDoc table at runtime, with the + * type guessed by string-matching the description prose -- so a hint the guide did not + * mention was invisible here, and one whose wording changed silently changed type.

+ */ public final class BuildHintCatalog { private final Map hints = new LinkedHashMap(); @@ -38,181 +50,46 @@ public void add(BuildHintMetadata hint) { } } - public static BuildHintCatalog fromAsciiDoc(String asciidoc) { + /** + * Every hint the catalog describes, including the ones consumed only by the + * build service. Dynamic families such as {@code android.permission.} + * are left out: their names are patterns rather than keys, so there is + * nothing for the editor to set. + */ + public static BuildHintCatalog load() { BuildHintCatalog catalog = new BuildHintCatalog(); - if (asciidoc == null) { - return fallback(); - } - String[] lines = asciidoc.replace("\r\n", "\n").split("\n"); - boolean inTable = false; - boolean buildHintTable = false; - String currentName = null; - StringBuilder currentDescription = new StringBuilder(); - for (String raw : lines) { - String line = raw.trim(); - if ("|===".equals(line)) { - if (inTable) { - if (buildHintTable) { - flush(catalog, currentName, currentDescription.toString()); - break; - } - inTable = false; - } else { - inTable = true; - buildHintTable = false; - currentName = null; - currentDescription.setLength(0); - } - continue; - } - if (!inTable) { - continue; - } - if (line.startsWith("//")) { - continue; - } - if (!buildHintTable) { - String header = line.startsWith("|") ? line.substring(1).trim() : line; - if (header.startsWith("Name") && header.contains("|Description")) { - buildHintTable = true; - } + for (BuildHints.Hint h : BuildHints.entries()) { + if (h.isDynamic()) { continue; } - if (line.startsWith("|")) { - String cell = line.substring(1).trim(); - if (currentName == null) { - currentName = cell; - currentDescription.setLength(0); - } else if (currentDescription.length() == 0) { - currentDescription.append(cell); - } else { - flush(catalog, currentName, currentDescription.toString()); - currentName = cell; - currentDescription.setLength(0); - } - } else if (currentName != null && line.length() > 0) { - if (currentDescription.length() > 0) { - currentDescription.append(' '); - } - currentDescription.append(line); - } - } - if (catalog.hints.isEmpty()) { - return fallback(); + catalog.add(new BuildHintMetadata( + h.name(), + h.doc(), + toSettingsType(h.type()), + h.platform(), + h.values(), + h.def(), + annotationOf(h))); } return catalog; } - private static void flush(BuildHintCatalog catalog, String rawName, String description) { - if (rawName == null || rawName.trim().length() == 0) { - return; - } - for (String name : splitNames(rawName)) { - catalog.add(new BuildHintMetadata(name, description, inferType(name, description), inferPlatform(name))); - } - } - - private static List splitNames(String raw) { - ArrayList names = new ArrayList(); - String normalized = raw.replace("`", "").replace("(a.k.a.", "/").replace(")", ""); - String[] parts = normalized.split(","); - for (String part : parts) { - String[] slashParts = part.split("/"); - for (String slashPart : slashParts) { - String name = slashPart.trim(); - if (name.indexOf(' ') >= 0 || name.length() == 0 || name.startsWith("(")) { - continue; - } - names.add(name); - } - } - return names.isEmpty() ? Collections.singletonList(raw.trim()) : names; - } - - private static BuildHintType inferType(String name, String description) { - String n = name.toLowerCase(); - String d = description == null ? "" : description.toLowerCase(); - if ("java.version".equals(name)) { - return BuildHintType.INTEGER; - } - if ("android.targetSDKVersion".equals(name)) { - return BuildHintType.INTEGER; - } - if ("android.useAndroidX".equals(name)) { - return BuildHintType.BOOLEAN; + private static String annotationOf(BuildHints.Hint h) { + if (!h.isAnnotated()) { + return null; } - if ("build.cn1Version".equals(name) || "ios.bundleVersion".equals(name)) { - return BuildHintType.VERSION; - } - if (n.contains("password") || n.contains("secret") || n.contains("token")) { - return BuildHintType.SECRET; - } - if (n.contains("certificate") || n.contains("provision") || n.contains("sdkroot") || d.contains("path to")) { - return BuildHintType.PATH; - } - if (n.contains("url") || d.contains("https://") || d.contains("http://")) { - return BuildHintType.URL; - } - if (d.contains("true/false") || d.contains("boolean true/false") || d.contains("`true`") || d.contains("`false`")) { - return BuildHintType.BOOLEAN; - } - if (d.contains("comma") || d.contains("comma-delimited") || d.contains("comma delimited")) { - return BuildHintType.CSV; - } - if (d.contains("<") && d.contains(">") || n.contains("xml") || n.contains("plistinject") || n.contains("xpermissions")) { - return BuildHintType.XML; - } - if (n.contains("version") || d.contains("version")) { - return BuildHintType.VERSION; - } - if (d.contains("can be ") || d.contains("supported values") || d.contains("accepts ")) { - return BuildHintType.ENUM; - } - if (d.contains("integer") || d.contains("size in bytes") || n.endsWith("port")) { - return BuildHintType.INTEGER; - } - return BuildHintType.TEXT; + return "@" + h.group().annotationSimpleName() + "(" + h.attr() + ")"; } - private static String inferPlatform(String name) { - if (name.startsWith("android.") || name.startsWith("and.")) { - return "android"; + /** + * Maps the catalog's type to this tool's vocabulary. Derived rather than + * duplicated so the two cannot drift apart again. + */ + private static BuildHintType toSettingsType(HintType type) { + try { + return BuildHintType.valueOf(BuildHints.settingsType(type)); + } catch (IllegalArgumentException ex) { + return BuildHintType.TEXT; } - if (name.startsWith("ios.")) { - return "ios"; - } - if (name.startsWith("macNative.") || name.startsWith("codename1.mac.") || name.startsWith("desktop.mac.")) { - return "mac"; - } - if (name.startsWith("windows.") || name.startsWith("win.")) { - return "windows"; - } - if (name.startsWith("linux.")) { - return "linux"; - } - if (name.startsWith("javascript.")) { - return "javascript"; - } - if (name.startsWith("desktop.")) { - return "desktop"; - } - return "general"; - } - - public static BuildHintCatalog fallback() { - BuildHintCatalog catalog = new BuildHintCatalog(); - catalog.add(new BuildHintMetadata("build.cn1Version", "Pins the cloud build to a released Codename One version such as 7.0.250, or master.", BuildHintType.VERSION, "general")); - catalog.add(new BuildHintMetadata("java.version", "Build server Java version.", BuildHintType.INTEGER, "general")); - catalog.add(new BuildHintMetadata("android.debug", "Whether to include an Android debug build.", BuildHintType.BOOLEAN, "android")); - catalog.add(new BuildHintMetadata("android.release", "Whether to include an Android release build.", BuildHintType.BOOLEAN, "android")); - catalog.add(new BuildHintMetadata("android.xpermissions", "Additional Android manifest permissions XML.", BuildHintType.XML, "android")); - catalog.add(new BuildHintMetadata("ios.bundleVersion", "Version number of the generated iOS bundle.", BuildHintType.VERSION, "ios")); - catalog.add(new BuildHintMetadata("ios.deployment_target", "Minimum iOS version.", BuildHintType.VERSION, "ios")); - catalog.add(new BuildHintMetadata("ios.plistInject", "Raw XML injected into the iOS Info.plist.", BuildHintType.XML, "ios")); - catalog.add(new BuildHintMetadata("macNative.distribution", "Mac native distribution: appStore, developerID, or both.", BuildHintType.ENUM, "mac")); - catalog.add(new BuildHintMetadata("windows.signing.timestampUrl", "RFC 3161 timestamp server URL for Windows signing.", BuildHintType.URL, "windows")); - catalog.add(new BuildHintMetadata("desktop.width", "Desktop window width.", BuildHintType.INTEGER, "desktop")); - catalog.add(new BuildHintMetadata("desktop.height", "Desktop window height.", BuildHintType.INTEGER, "desktop")); - return catalog; } } diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintMetadata.java b/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintMetadata.java index e5e27a1d789..dcb1cfdc2bc 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintMetadata.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintMetadata.java @@ -1,16 +1,56 @@ package com.codename1.settings.hints; +import java.util.Collections; +import java.util.List; + public final class BuildHintMetadata { private final String name; private final String description; private final BuildHintType type; private final String platform; + private final List values; + private final String defaultValue; + private final String annotation; public BuildHintMetadata(String name, String description, BuildHintType type, String platform) { + this(name, description, type, platform, null, null, null); + } + + /** + * @param values the closed value domain, or null when the hint is free-form + * @param defaultValue the builder's own default, or null when it has none + * @param annotation the annotation attribute that sets this hint, e.g. + * {@code @Ios(pods)}, or null when it has none + */ + public BuildHintMetadata(String name, String description, BuildHintType type, String platform, + List values, String defaultValue, String annotation) { this.name = name; this.description = description == null ? "" : description.trim(); this.type = type == null ? BuildHintType.TEXT : type; this.platform = platform == null ? "general" : platform; + this.values = values == null || values.isEmpty() + ? Collections.emptyList() : Collections.unmodifiableList(values); + this.defaultValue = defaultValue; + this.annotation = annotation; + } + + /** The accepted values, or empty when the hint is free-form. */ + public List values() { + return values; + } + + /** The builder's own default, or null. */ + public String defaultValue() { + return defaultValue; + } + + /** + * The annotation attribute that sets this hint, or null when the hint has no + * checked form yet. Editing such a hint here is not wrong, but the annotation + * is the form the compiler validates. + */ + public String annotation() { + return annotation; } public String name() { diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java b/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java index ff66d7e231d..1f72dafe25f 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java @@ -5,7 +5,6 @@ public final class ProjectBinding { private String settings; private String pom; private String multimoduleRoot; - private String buildHintsDoc; public String projectDir() { return projectDir; @@ -23,10 +22,6 @@ public String multimoduleRoot() { return multimoduleRoot; } - public String buildHintsDoc() { - return buildHintsDoc; - } - public boolean isValid() { return settings != null && settings.length() > 0; } @@ -53,7 +48,6 @@ public static ProjectBinding parse(String content) { case "settings" -> b.settings = val; case "pom" -> b.pom = val; case "multimoduleRoot" -> b.multimoduleRoot = val; - case "buildHintsDoc" -> b.buildHintsDoc = val; default -> { } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index dbd47edf61f..23a7e8f2a39 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1,54 +1,91 @@ package com.codename1.settings; import com.codename1.settings.hints.BuildHintCatalog; +import com.codename1.settings.hints.BuildHintMetadata; import com.codename1.settings.hints.BuildHintType; import org.junit.jupiter.api.Test; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +/** + * The hint catalog the Settings tool offers for editing. + * + *

It used to be scraped out of the developer guide's AsciiDoc table at runtime, + * with each hint's type guessed by string-matching its description prose. It now + * comes from {@code com.codename1.build.shared.BuildHints}, the same table the + * build hint annotations are generated from and the same one the drift gate holds + * the builders against.

+ */ public class BuildHintCatalogTest { - @Test - public void parsesDeveloperGuideBuildHintTable() { - String doc = """ - Before - |=== - |Name\t|Description - - |android.debug - |true/false defaults to true - indicates whether to include debug. - - |ios.plistInject - |Injects raw XML into the plist. - - |windows.signing.timestampUrl - |RFC 3161 timestamp server URL. - |=== - After - """; - BuildHintCatalog catalog = BuildHintCatalog.fromAsciiDoc(doc); + @Test + public void carriesTheHintsTheDeveloperGuideDocuments() { + BuildHintCatalog catalog = BuildHintCatalog.load(); assertNotNull(catalog.get("android.debug")); + assertNotNull(catalog.get("ios.plistInject")); + assertNotNull(catalog.get("windows.signing.timestampUrl")); + assertTrue(catalog.all().size() > 400, + "expected the full catalog, got " + catalog.all().size()); + } + + @Test + public void knownHintsCarryTheRightType() { + BuildHintCatalog catalog = BuildHintCatalog.load(); assertEquals(BuildHintType.BOOLEAN, catalog.get("android.debug").type()); assertEquals(BuildHintType.XML, catalog.get("ios.plistInject").type()); - assertEquals(BuildHintType.URL, catalog.get("windows.signing.timestampUrl").type()); + assertEquals(BuildHintType.INTEGER, catalog.get("java.version").type()); + assertEquals(BuildHintType.INTEGER, catalog.get("android.min_sdk_version").type()); + assertEquals(BuildHintType.BOOLEAN, catalog.get("android.useAndroidX").type()); + assertEquals(BuildHintType.CSV, catalog.get("ios.pods").type()); } + /** + * The tool used to accept any string for every hint but an integer, a version + * or a URL. A hint with a closed domain is the one case where a wrong value is + * certainly wrong, because the builder compares against those strings and + * silently falls back to its default when it matches none of them. + */ @Test - public void packagedDeveloperGuideCatalogProvidesKnownHintTypes() throws Exception { - try (InputStream in = CodenameOneSettings.class.getResourceAsStream( - "/com/codename1/settings/hints/Advanced-Topics-Under-The-Hood.asciidoc")) { - assertNotNull(in, "The Settings jar should carry the developer-guide build hint table."); - String doc = new String(in.readAllBytes(), StandardCharsets.UTF_8); - BuildHintCatalog catalog = BuildHintCatalog.fromAsciiDoc(doc); - assertEquals(BuildHintType.INTEGER, catalog.get("java.version").type()); - assertEquals(BuildHintType.VERSION, catalog.get("build.cn1Version").type()); - assertEquals(BuildHintType.VERSION, catalog.get("ios.bundleVersion").type()); - assertEquals(BuildHintType.INTEGER, catalog.get("android.targetSDKVersion").type()); - assertEquals(BuildHintType.BOOLEAN, catalog.get("android.useAndroidX").type()); + public void hintsWithAClosedDomainExposeIt() { + BuildHintMetadata titleBar = BuildHintCatalog.load().get("desktop.titleBar"); + assertNotNull(titleBar); + assertEquals(BuildHintType.ENUM, titleBar.type()); + assertTrue(titleBar.values().contains("native")); + assertTrue(titleBar.values().contains("custom")); + assertTrue(titleBar.values().contains("toolbar")); + assertFalse(titleBar.values().contains("natvie")); + } + + /** A hint with a checked form should say so, so the UI can point at it. */ + @Test + public void annotatedHintsNameTheirAnnotation() { + BuildHintCatalog catalog = BuildHintCatalog.load(); + assertEquals("@Ios(pods)", catalog.get("ios.pods").annotation()); + assertEquals("@Desktop(titleBar)", catalog.get("desktop.titleBar").annotation()); + // Not every hint has one; the properties file remains the way to set those. + assertEquals(null, catalog.get("android.xmanifest").annotation()); + } + + /** + * Dynamic families such as {@code android.permission.} are patterns, not + * keys, so there is nothing for the editor to set. + */ + @Test + public void dynamicFamiliesAreNotOffered() { + BuildHintCatalog catalog = BuildHintCatalog.load(); + for (BuildHintMetadata h : catalog.all()) { + assertFalse(h.name().contains("*"), + h.name() + " is a pattern, not a hint the editor can set"); } } + + @Test + public void searchStillMatchesOnNameAndDescription() { + BuildHintCatalog catalog = BuildHintCatalog.load(); + assertFalse(catalog.search("pods").isEmpty()); + assertFalse(catalog.search("android").isEmpty()); + } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java index 44e7bd45a73..51b615d94c5 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java @@ -125,7 +125,12 @@ public void uiUsesDensityAwareThemeSizingInsteadOfFixedComponentDimensions() thr "Theme font sizes must use physical mm units so Retina density does not create miniature text."); assertTrue(source.contains("new TableLayout(1, 2)"), "The main content width should be responsive through TableLayout percentages."); - assertTrue(source.contains("new GridLayout(3, 2)"), + // Match the column count, not the row count. This asserted GridLayout(3, 2) + // and the Basic form has grown to five rows since; because this module's + // tests are skipped by default nothing reported the drift. The two columns + // are what makes the form responsive -- the row count is just how many + // fields there happen to be. + assertTrue(Pattern.compile("new GridLayout\\(\\d+, 2\\)").matcher(source).find(), "The Basic form should use a responsive two-column GridLayout."); assertTrue(source.contains("private Container configureToolbar()"), "Native desktop chrome should use a stable top-bar container, not a second Toolbar instance."); diff --git a/scripts/settings/pom.xml b/scripts/settings/pom.xml index ac7954b0357..24c27fa99d2 100644 --- a/scripts/settings/pom.xml +++ b/scripts/settings/pom.xml @@ -68,6 +68,11 @@ codenameone-javase ${cn1.version}
+ + com.codenameone + codenameone-build-hint-catalog + ${cn1.version} +
diff --git a/scripts/video-builder/common/codenameone_settings.properties b/scripts/video-builder/common/codenameone_settings.properties index b7f6775b36e..4dbecd58940 100644 --- a/scripts/video-builder/common/codenameone_settings.properties +++ b/scripts/video-builder/common/codenameone_settings.properties @@ -5,7 +5,4 @@ codename1.version=1.0 codename1.vendor=CodenameOne codename1.cssTheme=true codename1.arg.java.version=17 -codename1.arg.desktop.width=1280 -codename1.arg.desktop.height=720 -codename1.arg.desktop.titleBar=native codename1.kotlin=false diff --git a/scripts/video-builder/common/src/main/java/com/codename1/videobuilder/VideoBuilder.java b/scripts/video-builder/common/src/main/java/com/codename1/videobuilder/VideoBuilder.java index 00dc4e27788..5883947d9b8 100644 --- a/scripts/video-builder/common/src/main/java/com/codename1/videobuilder/VideoBuilder.java +++ b/scripts/video-builder/common/src/main/java/com/codename1/videobuilder/VideoBuilder.java @@ -37,8 +37,10 @@ import java.security.MessageDigest; import java.util.ArrayList; import java.util.List; +import com.codename1.annotations.buildhints.*; /** Codename One application lifecycle and CLI dispatcher. */ +@Desktop(height = 720, titleBar = DesktopTitleBar.NATIVE, width = 1280) public final class VideoBuilder { private Form current; diff --git a/tools/build-hint-bootstrap/README.md b/tools/build-hint-bootstrap/README.md new file mode 100644 index 00000000000..f27e5ef8641 --- /dev/null +++ b/tools/build-hint-bootstrap/README.md @@ -0,0 +1,23 @@ +# Build hint catalog bootstrap (one-off, archived) + +These scripts seeded `maven/build-hint-catalog` when the catalog was first +created. They mined every `getArg` call site in the builders for a hint's name +and default, imported the prose from the developer guide's hand-written build +hint table, and emitted the `BuildHints*.java` registration classes. + +**They are not part of any build and should not be re-run.** The catalog is the +source of truth now and is edited directly; `scripts/gen-build-hint-annotations.sh` +generates the annotations, the docs and the Settings schema *from* it. + +They are kept only to show where the catalog's contents came from. They read the +guide's original hand-written table from a `guide_old.asciidoc` that is +deliberately not committed — recover it from history if you ever need it: + +```bash +git show :docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc \ + > tools/build-hint-bootstrap/guide_old.asciidoc +``` + +Re-running them would overwrite hand-edits to the catalog. If you ever need to +re-derive an entry, read the miner instead: `scripts/build_hint_miner.py` is the +supported, tested version of the same extraction and is what the CI gate uses. diff --git a/tools/build-hint-bootstrap/curation.py b/tools/build-hint-bootstrap/curation.py new file mode 100644 index 00000000000..7ca8c27d14e --- /dev/null +++ b/tools/build-hint-bootstrap/curation.py @@ -0,0 +1,220 @@ +# The curated set: hints exposed as typed annotation attributes. +# +# name -> (group, attribute, enum-or-None, forced-type-or-None, forced-default-or-None) +# +# TYPE_OVERRIDES below carries the hints whose type cannot be inferred from a +# literal default -- the call site computes one -- but whose type is unambiguous +# in the code that reads it. Without them a boolean hint is exposed as a String +# attribute, which is barely better than the properties file. +# +# Every enum domain below was read off the code that consumes the hint, not +# guessed from a name or a doc sentence. Where the consumer accepts alias +# spellings (ios.themeMode takes "liquid" for "modern"), the enum exposes the +# canonical spelling only: on the annotation path an over-tight domain costs a +# fallback to the properties file, never a broken build, and the aliases stay +# reachable there. + +ENUMS = { + # HardeningPreflight.java:90 rejects anything else outright. + "HardenLevel": ["off", "standard", "aggressive", "paranoid"], + "HardenStrings": ["off", "constants", "all"], + "HardenControlFlow": ["off", "on"], + # BuildHintSchemaDefaults.registerNativeTheme + JavaSEPort.resolveAutoNativeTheme + "NativeThemeMode": ["modern", "legacy", "custom"], + "IosThemeMode": ["auto", "modern", "ios7", "legacy"], + "AndroidThemeMode": ["auto", "modern", "hololight", "legacy"], + # GenerateDesktopAppWrapperMojo.sanitizeTitleBarMode warns and silently + # falls back to "native" on anything else -- the exact silent-typo case. + "DesktopTitleBar": ["native", "custom", "toolbar"], + # IOSDependencyManager.fromHint throws on anything else. + "IosDependencyManager": ["auto", "cocoapods", "spm", "both", "none"], + # "one of ios, ipad, iphone (defaults to ios)" -- the guide states it and + # the value is passed straight through to ParparVM (IPhoneBuilder.java:4572). + "IosProjectType": ["ios", "ipad", "iphone"], + # The three values android:installLocation itself accepts. + "InstallLocation": ["auto", "internalOnly", "preferExternal"], +} + +CURATED = { + # ---- @Ios ------------------------------------------------------------- + "ios.newStorageLocation": ("IOS", "newStorageLocation", None, None, None), + "ios.deployment_target": ("IOS", "deploymentTarget", None, "VERSION", None), + "ios.minDeploymentTarget": ("IOS", "minDeploymentTarget", None, "VERSION", "6.0"), + "ios.teamId": ("IOS", "teamId", None, None, None), + "ios.includePush": ("IOS", "includePush", None, None, None), + "ios.add_libs": ("IOS", "addLibs", None, None, None), + "ios.pods": ("IOS", "pods", None, None, None), + "ios.pods.platform": ("IOS", "podsPlatform", None, "VERSION", None), + "ios.pods.sources": ("IOS", "podsSources", None, None, None), + "ios.applicationQueriesSchemes": ("IOS", "applicationQueriesSchemes", None, None, None), + "ios.objC": ("IOS", "objC", None, None, None), + "ios.plistInject": ("IOS", "plistInject", None, None, None), + "ios.glAppDelegateHeader": ("IOS", "glAppDelegateHeader", None, None, None), + "ios.beforeFinishLaunching": ("IOS", "beforeFinishLaunching", None, None, None), + "ios.themeMode": ("IOS", "themeMode", "IosThemeMode", None, None), + "ios.interface_orientation": ("IOS", "interfaceOrientation", None, None, None), + "ios.project_type": ("IOS", "projectType", "IosProjectType", None, None), + "ios.prerendered_icon": ("IOS", "prerenderedIcon", None, None, None), + "ios.uiscene": ("IOS", "uiscene", None, None, None), + "ios.urlScheme": ("IOS", "urlScheme", None, None, None), + "ios.dependencyManager": ("IOS", "dependencyManager", "IosDependencyManager", None, None), + "ios.bundleVersion": ("IOS", "bundleVersion", None, "VERSION", None), + "ios.spm.packages": ("IOS", "spmPackages", None, None, None), + # ---- @Android --------------------------------------------------------- + "android.min_sdk_version": ("ANDROID", "minSdkVersion", None, None, None), + "android.targetSDKVersion": ("ANDROID", "targetSDKVersion", None, None, None), + "android.buildToolsVersion": ("ANDROID", "buildToolsVersion", None, None, None), + "android.xpermissions": ("ANDROID", "xpermissions", None, None, None), + "android.xapplication": ("ANDROID", "xapplication", None, None, None), + "android.gradleDep": ("ANDROID", "gradleDep", None, None, None), + "android.proguardKeep": ("ANDROID", "proguardKeep", None, None, None), + "android.release": ("ANDROID", "release", None, None, None), + "android.debug": ("ANDROID", "debug", None, None, None), + "android.useAndroidX": ("ANDROID", "useAndroidX", None, None, None), + "android.licenseKey": ("ANDROID", "licenseKey", None, None, None), + "android.installLocation": ("ANDROID", "installLocation", "InstallLocation", None, None), + "android.activity.launchMode": ("ANDROID", "activityLaunchMode", None, None, None), + "and.themeMode": ("ANDROID", "themeMode", "AndroidThemeMode", None, None), + "android.appBundle": ("ANDROID", "appBundle", None, None, None), + "android.disableR8": ("ANDROID", "disableR8", None, None, None), + "android.enableProguard": ("ANDROID", "enableProguard", None, None, None), + "android.newFirebaseMessaging": ("ANDROID", "newFirebaseMessaging", None, None, None), + "android.multidex": ("ANDROID", "multidex", None, None, None), + "android.captureRecord": ("ANDROID", "captureRecord", None, None, None), + "android.hideStatusBar": ("ANDROID", "hideStatusBar", None, None, None), + "android.repositories": ("ANDROID", "repositories", None, None, None), + "android.topDependency": ("ANDROID", "topDependency", None, None, None), + "android.xgradle": ("ANDROID", "xgradle", None, None, None), + # ---- @Desktop --------------------------------------------------------- + "desktop.titleBar": ("DESKTOP", "titleBar", "DesktopTitleBar", None, None), + "desktop.interactiveScrollbars": ("DESKTOP", "interactiveScrollbars", None, None, None), + "desktop.width": ("DESKTOP", "width", None, "INT", None), + "desktop.height": ("DESKTOP", "height", None, "INT", None), + "desktop.resizable": ("DESKTOP", "resizable", None, None, None), + "desktop.fullscreen": ("DESKTOP", "fullscreen", None, None, None), + "desktop.adaptToRetina": ("DESKTOP", "adaptToRetina", None, None, None), + # ---- @Hardening ------------------------------------------------------- + "harden.level": ("HARDENING", "level", "HardenLevel", None, None), + "harden.strings": ("HARDENING", "strings", "HardenStrings", None, None), + "harden.controlFlow": ("HARDENING", "controlFlow", "HardenControlFlow", None, None), + "harden.rename": ("HARDENING", "rename", None, None, None), + "harden.keep": ("HARDENING", "keep", None, "TEXT_BLOCK", None), + "harden.allowUnhardenedLocalBuild": ("HARDENING", "allowUnhardenedLocalBuild", None, None, None), + # ---- @OnDeviceDebug --------------------------------------------------- + "ios.onDeviceDebug": ("ON_DEVICE_DEBUG", "ios", None, None, None), + "ios.onDeviceDebug.proxyHost": ("ON_DEVICE_DEBUG", "iosProxyHost", None, None, None), + "ios.onDeviceDebug.proxyPort": ("ON_DEVICE_DEBUG", "iosProxyPort", None, "INT", None), + "ios.onDeviceDebug.waitForAttach": ("ON_DEVICE_DEBUG", "iosWaitForAttach", None, None, None), + "android.onDeviceDebug": ("ON_DEVICE_DEBUG", "android", None, None, None), + # ---- @Build ----------------------------------------------------------- + "nativeTheme": ("GENERAL", "nativeTheme", "NativeThemeMode", None, None), + "gcm.sender_id": ("GENERAL", "gcmSenderId", None, None, None), + "facebook.appId": ("GENERAL", "facebookAppId", None, None, None), + "noExtraResources": ("GENERAL", "noExtraResources", None, None, None), +} + +# ios.NS*UsageDescription -> @IosPrivacy, attribute is the key minus "ios.NS" +# with a lowercased first letter; the UsageDescription suffix is kept so the +# plist key it maps to is mechanically recoverable. +PRIVACY_PREFIX = "ios.NS" + +# Hints whose default the mining cannot state in one value, resolved by reading +# the code rather than by picking whichever call site came first. +DEFAULT_NOTES = { + "android.debug": + "Defaults conditionally rather than to a fixed value: when android.release is on " + "it defaults to false, and when release is off it defaults to true, so a build " + "that selects neither still produces something installable " + "(AndroidGradleBuilder.java:447-451).", + "ios.minDeploymentTarget": + "The null and empty-string reads of this hint are presence checks; 6.0 is the " + "substantive default (IPhoneBuilder.java:4671).", +} + +# Prose for curated hints the main developer-guide table does not describe. +# Sourced from the feature chapters (App-Hardening.asciidoc) or read off the +# code that consumes the hint. The privacy strings are generated mechanically +# by BuildHintCodeGenerator and are not listed here. +DOC_OVERRIDES = { + "harden.level": + "Master switch for app hardening: off, standard, aggressive or paranoid. An " + "unrecognized value fails the build rather than being quietly treated as off.", + "harden.rename": + "Overrides symbol renaming independently of harden.level.", + "harden.strings": + "Overrides string obfuscation independently of harden.level: off, constants or all.", + "harden.controlFlow": + "Overrides control-flow obfuscation independently of harden.level.", + "harden.keep": + "Keep rules in ProGuard syntax, one per line, for classes that are resolved by " + "name at runtime and so cannot be found by the automatic analysis. Same syntax " + "as android.proguardKeep, so existing rules port directly. Rules are separated " + "by newlines only, because a semicolon is legal inside a rule body such as " + "{ *; }.", + "harden.allowUnhardenedLocalBuild": + "Permits a local or source build to run with hardening requested but not " + "applied. Without it such a build is refused, so a hardened app is never " + "shipped from a target that cannot actually harden it.", + "desktop.titleBar": + "How the desktop window is framed: native for the OS title bar and menu bar, " + "custom for an undecorated window with a Codename One drawn title bar, or " + "toolbar for the legacy in-app Toolbar. An unrecognized value falls back to " + "native with a warning.", + "desktop.interactiveScrollbars": + "Enables grab-able, click-to-page desktop scrollbars.", + "desktop.fullscreen": + "Starts the desktop build in full-screen mode.", + "ios.dependencyManager": + "Which native dependency manager to use: auto picks one from whichever of " + "ios.pods and ios.spm.packages is set, and cocoapods, spm or both require the " + "matching hint to be set. An unrecognized value fails the build.", + "ios.deployment_target": + "Minimum iOS version the build targets. Set it to the lowest iOS you actually " + "support; a higher value excludes older devices from the App Store listing.", + "ios.pods.sources": + "Extra CocoaPods spec repositories to search, in addition to the default trunk.", + "ios.spm.packages": + "Swift Package Manager packages to link, one per entry, each written as " + "identity|url|requirement.", + "android.appBundle": + "Produces an Android App Bundle (.aab) rather than an APK. Required for new " + "Play Store submissions.", + "android.buildToolsVersion": + "Android build-tools version. It also selects the compile SDK, so there is no " + "separate compile-SDK hint.", + "android.disableR8": + "Turns off R8, falling back to the older shrinker. Note that hardening requires " + "R8, so this conflicts with harden.level.", + "android.gradleDep": + "Gradle dependency statements to add to the app module, such as " + "implementation 'com.example:lib:1.0'.", + "android.topDependency": + "Statements added to the top-level Gradle build file rather than the app module.", + "android.repositories": + "Extra Gradle repositories to resolve dependencies from.", + "android.xgradle": + "Arbitrary text spliced into the generated app-module Gradle file.", + "android.hideStatusBar": + "Hides the Android status bar.", + "android.newFirebaseMessaging": + "Uses the current Firebase Cloud Messaging integration. Requires AndroidX and " + "Gradle 8.13 or newer.", +} + + +# hint -> (HintType, separator-or-None). Each verified at the call site, not guessed. +TYPE_OVERRIDES = { + # request.getArg(...).equals("true"), with a computed rather than literal default + "android.useAndroidX": ("BOOLEAN", None), # AndroidGradleBuilder.java:1169 + "android.appBundle": ("BOOLEAN", None), # AndroidGradleBuilder.java:1441 + "harden.rename": ("BOOLEAN", None), # hardenBoolArg(..., true) + # version numbers whose default is computed from the installed toolchain + "android.targetSDKVersion": ("INT", None), # AndroidGradleBuilder.java:1401 + "android.buildToolsVersion": ("VERSION", None), # AndroidGradleBuilder.java:1186 + # split by the consumer, so they are lists even though no merger entry exists + "ios.spm.packages": ("STRING_LIST", ";"), # IOSDependencyManager.java:119 split("[;]") + "ios.pods.sources": ("STRING_LIST", ","), # IPhoneBuilder.java:5159 split("[;,]") + # free text that is expected to span lines + "ios.beforeFinishLaunching": ("TEXT_BLOCK", None), + "ios.glAppDelegateHeader": ("TEXT_BLOCK", None), +} diff --git a/tools/build-hint-bootstrap/gen_catalog.py b/tools/build-hint-bootstrap/gen_catalog.py new file mode 100644 index 00000000000..e1b07755067 --- /dev/null +++ b/tools/build-hint-bootstrap/gen_catalog.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +"""One-time bootstrap: emit the BuildHints* registration classes.""" +import json, re, os, sys, collections + +ROOT = "/Users/shai/dev/cn6/CodenameOne" +SC = os.path.dirname(os.path.abspath(__file__)) +OUT = os.path.join(ROOT, "maven/build-hint-catalog/src/main/java/com/codename1/build/shared") +LICENSE = open(os.path.join(SC, "license.txt")).read() + +mined = json.load(open(os.path.join(SC, "mined.json"))) + +sys.path.insert(0, SC) +from curation import CURATED, ENUMS, PRIVACY_PREFIX, DEFAULT_NOTES, DOC_OVERRIDES, TYPE_OVERRIDES + + +def privacy_attr(name): + """ios.NSCameraUsageDescription -> cameraUsageDescription.""" + body = name[len(PRIVACY_PREFIX):] + return body[0].lower() + body[1:] + +# ---------------------------------------------------------------- doc prose +def load_docs(): + # The guide's inline table has been replaced by the generated include, so the + # prose no longer has a live source. Recover the pre-migration copy with + # git show :docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc + # This bootstrap is a one-off: the catalog is the source of truth now and is + # edited directly. + p = os.path.join(SC, "guide_old.asciidoc") + lines = open(p, encoding="utf-8").read().split("\n")[30:736] + docs, i = {}, 0 + while i < len(lines): + ln = lines[i] + m = re.fullmatch(r'\|([A-Za-z][A-Za-z0-9_.<>\-]*)', ln.strip()) + # a Vale/AsciiDoc // directive may sit between the name and its description + k = i + 1 + while k < len(lines) and lines[k].lstrip().startswith("//"): + k += 1 + if m and k < len(lines) and lines[k].startswith("|"): + body, j = [lines[k][1:]], k + 1 + while j < len(lines) and lines[j].strip() and not lines[j].startswith("|"): + body.append(lines[j]); j += 1 + docs[m.group(1)] = " ".join(x.strip() for x in body).strip() + i = j + else: + i += 1 + return docs + +DOCS = load_docs() +print(f"doc rows parsed: {len(DOCS)}", file=sys.stderr) + +def clean_doc(t): + t = re.sub(r'<<[^,>]*,\s*([^>]*)>>', r'\1', t) # <> -> text + t = re.sub(r'<<([^>]*)>>', r'\1', t) # <> -> anchor + t = re.sub(r'\s+', ' ', t).strip() + return t + +# separators are authoritative -- read off LibraryHintMerger +SEPARATORS = { + "android.gradleDep": ";", "gradleDependencies": "\n", "android.topDependency": "\n", + "android.repositories": "\n", "android.xgradle": "\n", "android.gradle.androidx": "\n", + "android.xgradle_default_config": "\n", "android.gradlePlugin": "\n", + "android.supportv4Dep": "\n", "android.proguardKeep": "\n", + "ios.pods": ",", "ios.applicationQueriesSchemes": ",", "ios.add_libs": ";", + # joins with a space, but it is an XML attribute fragment rather than a list + # the user thinks of as items, so it keeps HintType.XML -- see infer(). + "android.xapplication_attr": " ", +} +# Every entry above is a list the user edits as items; this one is not. +SEPARATOR_BUT_NOT_A_LIST = {"android.xapplication_attr"} +XML_HINTS = re.compile(r'^(android\.(xpermissions|xapplication|xmanifest|xactivity|' + r'xintent_filter|xqueries|xapplication_attr|xactivity_attr)|ios\.plistInject|' + r'ios\.entitlementsInject|.*Inject)$') + +ID_NAME = re.compile(r'(^|[._])([a-z]+_)?id$|Id$|_id$', re.I) + +def is_int_hint(name, lit): + """A digit default is only an int if it is arithmetic, not an identifier. + + facebook.appId defaults to a 15-digit Facebook app id: it overflows a Java + int and nothing ever adds to it. Such a hint is an opaque string. + """ + if ID_NAME.search(name): + return False + try: + v = int(lit) + except ValueError: + return False + return -2**31 <= v < 2**31 + + +def infer(name, defaults, doc): + if name in TYPE_OVERRIDES: + t, sep = TYPE_OVERRIDES[name] + lit = None + for x in defaults: + if x.startswith('"') and x.endswith('"'): + lit = x[1:-1]; break + if x in ("true", "false") or re.fullmatch(r'-?\d+', x): + lit = x; break + return t, lit, sep + d = [x for x in defaults if x not in ("", "null")] + lit = None + for x in d: + if x.startswith('"') and x.endswith('"'): + lit = x[1:-1]; break + if x in ("true", "false") or re.fullmatch(r'-?\d+', x): + lit = x; break + dl = doc.lower() + if name in SEPARATORS: + if name in SEPARATOR_BUT_NOT_A_LIST: + return "XML", lit, SEPARATORS[name] + return "STRING_LIST", lit, SEPARATORS[name] + if XML_HINTS.match(name): + return "XML", lit, "" + if lit in ("true", "false") or {x.strip('"') for x in d} <= {"true", "false"} and d: + return "BOOLEAN", lit, None + if lit is not None and re.fullmatch(r'-?\d+', lit) and is_int_hint(name, lit): + return "INT", lit, None + if "true/false" in dl or dl.startswith("boolean"): + return "BOOLEAN", lit, None + if lit is not None and re.fullmatch(r'\d+\.\d+(\.\d+)?', lit): + return "VERSION", lit, None + return "STRING", lit, None + +PLATFORM = [("android.", "android"), ("and.", "android"), ("ios.", "ios"), + ("macNative.", "mac"), ("desktop.mac.", "mac"), ("windows.", "windows"), + ("win.", "windows"), ("linux.", "linux"), ("javascript.", "javascript"), + ("desktop.", "desktop"), ("tvNative.", "tv"), ("watchNative.", "watch")] + +IOS_PRIVACY = re.compile(r'^ios\.NS.*UsageDescription$') +ODD = re.compile(r'^(ios\.onDeviceDebug|android\.onDeviceDebug$)') + +def group_of(name): + if IOS_PRIVACY.match(name): return "IOS_PRIVACY" + if ODD.match(name): return "ON_DEVICE_DEBUG" + for p, g in [("ios.", "IOS"), ("android.", "ANDROID"), ("and.", "ANDROID"), + ("desktop.", "DESKTOP"), ("macNative.", "MAC_NATIVE"), + ("windows.", "WINDOWS"), ("linux.", "LINUX"), + ("javascript.", "JAVASCRIPT"), ("tvNative.", "TV_NATIVE"), + ("watchNative.", "WATCH_NATIVE"), ("harden.", "HARDENING")]: + if name.startswith(p): return g + return "GENERAL" + +def platform_of(name): + for p, v in PLATFORM: + if name.startswith(p): return v + return "general" + +def jesc(s): + return (s.replace("\\", "\\\\").replace('"', '\\"') + .replace("\n", "\\n").replace("\t", "\\t").replace("\r", "")) + +def wrap(text, indent, width=88): + """Split a long Java string literal into concatenated chunks.""" + words, lines, cur = text.split(" "), [], "" + for w in words: + if len(cur) + len(w) + 1 > width and cur: + lines.append(cur); cur = w + else: + cur = (cur + " " + w).strip() + if cur: lines.append(cur) + if not lines: return '""' + if len(lines) == 1: return '"%s"' % jesc(lines[0]) + sep = "\n" + " " * indent + "+ " + return sep.join('"%s "' % jesc(l) if i < len(lines) - 1 else '"%s"' % jesc(l) + for i, l in enumerate(lines)) + +FILES = { + "BuildHintsIos": lambda n: group_of(n) in ("IOS", "IOS_PRIVACY"), + "BuildHintsAndroid": lambda n: group_of(n) == "ANDROID", + "BuildHintsApple": lambda n: group_of(n) in ("MAC_NATIVE", "TV_NATIVE", "WATCH_NATIVE"), + "BuildHintsDesktop": lambda n: group_of(n) in ("DESKTOP", "WINDOWS", "LINUX", "JAVASCRIPT"), + "BuildHintsGeneral": lambda n: group_of(n) in ("GENERAL", "HARDENING", "ON_DEVICE_DEBUG"), +} + +BLURB = { + "BuildHintsIos": "iOS build hints, including the Info.plist privacy strings.", + "BuildHintsAndroid": "Android build hints, including the {@code and.} override aliases.", + "BuildHintsApple": "macOS Catalyst, tvOS and watchOS native-slice build hints.", + "BuildHintsDesktop": "Desktop, native Windows, native Linux and JavaScript build hints.", + "BuildHintsGeneral": "Hints with no platform prefix, plus hardening and on-device debugging.", +} + +# A mined key ending in a dot is the constant half of a concatenation -- +# getArg("android.permission." + name) -- not a hint anyone can set. Cataloguing +# it would put a phantom row in the guide and a phantom entry in the Settings +# tool. Each one is covered by a dynamic family instead. +mined = {k: v for k, v in mined.items() if not k.endswith(".")} + +counts = collections.Counter() +for fname, pred in FILES.items(): + names = sorted(n for n in mined if pred(n)) + counts[fname] = len(names) + body = [] + for n in names: + defaults = [d for d, _, _ in mined[n]] + sites = sorted({os.path.basename(f)[:-5] for _, f, _ in mined[n]}) + doc = clean_doc(DOCS.get(n, "")) + if not doc and n in DOC_OVERRIDES: + doc = DOC_OVERRIDES[n] + if n in DEFAULT_NOTES: + if doc and not doc.rstrip().endswith((".", "!", "?")): + doc = doc.rstrip() + "." + doc = (doc + " " + DEFAULT_NOTES[n]).strip() + htype, lit, sep = infer(n, defaults, doc) + parts = [' h.add(new Hint("%s")' % jesc(n)] + g = group_of(n) + cur = CURATED.get(n) + enum_name = None + if g == "IOS_PRIVACY": + parts.append(' .annotatedAs(HintGroup.IOS_PRIVACY, "%s")' % privacy_attr(n)) + htype = "STRING" + elif cur: + cg, attr, enum_name, forced_type, forced_def = cur + parts.append(' .annotatedAs(HintGroup.%s, "%s")' % (cg, attr)) + if forced_type: + htype = forced_type + if forced_def is not None: + lit = forced_def + else: + parts.append(' .group(HintGroup.%s)' % g) + if enum_name: + vals = ", ".join('"%s"' % v for v in ENUMS[enum_name]) + parts.append(' .values("%s", %s)' % (enum_name, vals)) + if lit is not None and lit not in ENUMS[enum_name]: + lit = None + else: + parts.append(' .type(HintType.%s)' % htype) + if lit is not None and lit != "": + parts.append(' .def("%s")' % jesc(lit)) + if sep is not None: + parts.append(' .separator("%s")' % jesc(sep)) + parts.append(' .platform("%s")' % platform_of(n)) + parts.append(' .consumedBy(%s)' % ", ".join('"%s"' % s for s in sites)) + if doc: + parts.append(' .doc(%s)' % wrap(doc, 24)) + body.append("\n".join(parts) + ");") + + src = LICENSE + f'''package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * {BLURB[fname]} + * + *

Seeded by mining every {{@code getArg}} call site in the builders, so the + * name and the default match what the build actually reads. Curated entries + * carry an annotation attribute and, where the domain is provably closed, an + * enum; the rest are described but set through + * {{@code codenameone_settings.properties}}.

+ * + *

Split out of {{@link BuildHints}} because a single class initializer + * holding every entry would exceed the JVM's 64KB per-method limit.

+ */ +final class {fname} {{ + + private {fname}() {{ + }} + + static void register(List h) {{ +''' + "\n\n".join(body) + "\n }\n}\n" + open(os.path.join(OUT, fname + ".java"), "w").write(src) + +print("entries per file:", dict(counts), file=sys.stderr) +print("total:", sum(counts.values()), file=sys.stderr) diff --git a/tools/build-hint-bootstrap/gen_external.py b/tools/build-hint-bootstrap/gen_external.py new file mode 100644 index 00000000000..979fe6de47e --- /dev/null +++ b/tools/build-hint-bootstrap/gen_external.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Emit BuildHintsExternal: hints the developer guide documents that no code in +this repository reads. Most are consumed by build-daemon lanes whose source is +not mirrored here, so their absence is not evidence they are dead -- which is +exactly why they are recorded rather than enforced.""" +import json, sys, os, re +SC = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, SC) +import gen_catalog as G + +ROOT = "/Users/shai/dev/cn6/CodenameOne" +OUT = os.path.join(ROOT, "maven/build-hint-catalog/src/main/java/com/codename1/build/shared") +LICENSE = open(os.path.join(SC, "license.txt")).read() + +mined = set(json.load(open(SC + "/mined.json"))) +PLACEHOLDER = re.compile(r'PERMISSION_NAME|[A-Z_]{4,}$|[<>]') + +names = sorted(k for k in G.DOCS + if k not in mined and not PLACEHOLDER.search(k) and "." in k or + (k not in mined and not PLACEHOLDER.search(k) and k.islower())) +names = sorted(set(n for n in names if not PLACEHOLDER.search(n))) + +body = [] +for n in names: + doc = G.clean_doc(G.DOCS[n]) + htype, lit, sep = G.infer(n, [], doc) + parts = [' h.add(new Hint("%s")' % G.jesc(n)] + parts.append(' .group(HintGroup.%s)' % G.group_of(n)) + parts.append(' .type(HintType.%s)' % htype) + if sep is not None: + parts.append(' .separator("%s")' % G.jesc(sep)) + parts.append(' .platform("%s")' % G.platform_of(n)) + parts.append(' .external()') + if doc: + parts.append(' .doc(%s)' % G.wrap(doc, 24)) + body.append("\n".join(parts) + ");") + +src = LICENSE + '''package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * Hints the developer guide documents that nothing in this repository reads. + * + *

Most are consumed by build-daemon lanes whose source is not mirrored here, + * so having no in-repo consumer is not evidence that a hint is dead. A few are + * probably genuinely obsolete. Recording the distinction as + * {@link Hint#isExternal()} keeps both the drift gate and the Settings tool + * honest: the gate does not demand a consumer for these, and the tool still + * offers them for editing.

+ * + *

They are deliberately not annotated. Exposing a hint as a typed attribute + * is a promise that setting it does something, and for these that promise + * cannot be checked from this repository.

+ */ +final class BuildHintsExternal { + + private BuildHintsExternal() { + } + + static void register(List h) { +''' + "\n\n".join(body) + "\n }\n}\n" +open(os.path.join(OUT, "BuildHintsExternal.java"), "w").write(src) +print("external entries:", len(names), file=sys.stderr) From d24353565a1c4b770ce06caf6740aee552034d31 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 22 Aug 2026 21:41:23 +0300 Subject: [PATCH 002/115] Commit the catalog sources that .gitignore was swallowing `.gitignore` carries a repo-wide `**/build/*`. The catalog's package is `com.codename1.build.shared`, so all 13 of its sources sat under a path segment named `build` and `git add` silently skipped them. Only `pom.xml` was committed: the module built locally from the working tree and produced an empty jar in CI, which is why `codenameone-maven-plugin` then failed with `cannot find symbol` on `BuildHints` and nearly every job went red. The sibling `platform-feature-catalog` lives in the same package and is fine, because it was added before that rule existed -- tracked files stay tracked, so nothing ever pointed at the hazard. Un-ignore `build` when it is a Java package rather than a build output directory, with the rationale beside the rule so the next file added there is not lost the same way. `maven/core/build/*` and `CodenameOne/build/*` stay ignored. Also from review: - Every bare `open()` in the four Python scripts now uses a context manager, so the handle closes even if parsing or `json.dump` raises, and the writes state their encoding. - The generator no longer emits an IP literal as an annotation default. PMD reads `default "127.0.0.1"` as hardcoded configuration, and the default clause is documentation only -- the processor emits a hint solely for members the developer actually wrote -- so the value moves to the javadoc where it belongs. - Files the migration touched that never carried a copyright header now have the complete one. The archetype's `__mainName__.java` is excluded instead: it is a template for the user's own application class, and stamping a Codename One GPL header onto it would put our licence on their code. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 9 + .../annotations/buildhints/OnDeviceDebug.java | 2 +- .../codenameone/developerguide/DemoCode.java | 22 + .../shared/BuildHintAnnotationBinding.java | 216 +++ .../build/shared/BuildHintCodeGenerator.java | 746 +++++++++ .../codename1/build/shared/BuildHints.java | 386 +++++ .../build/shared/BuildHintsAndroid.java | 1445 +++++++++++++++++ .../build/shared/BuildHintsApple.java | 291 ++++ .../build/shared/BuildHintsDesktop.java | 345 ++++ .../build/shared/BuildHintsDynamic.java | 112 ++ .../build/shared/BuildHintsExternal.java | 547 +++++++ .../build/shared/BuildHintsGeneral.java | 437 +++++ .../codename1/build/shared/BuildHintsIos.java | 1203 ++++++++++++++ .../com/codename1/build/shared/HintGroup.java | 83 + .../com/codename1/build/shared/HintType.java | 57 + .../build/shared/BuildHintsTest.java | 319 ++++ scripts/build_hint_miner.py | 6 +- scripts/check-build-hint-catalog.py | 15 +- scripts/copyright-header-exclusions.txt | 1 + .../com/codenameone/fidelity/FidelityApp.java | 6 +- .../inputvalidation/InputValidationApp.java | 20 +- .../purchasetest/PurchaseTestApp.java | 22 + .../settings/hints/BuildHintCatalog.java | 22 + .../settings/hints/BuildHintMetadata.java | 22 + .../settings/project/ProjectBinding.java | 22 + .../settings/BuildHintCatalogTest.java | 22 + .../codename1/settings/SettingsThemeTest.java | 22 + tools/build-hint-bootstrap/gen_catalog.py | 12 +- tools/build-hint-bootstrap/gen_external.py | 9 +- 29 files changed, 6401 insertions(+), 20 deletions(-) create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHints.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDynamic.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsExternal.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/HintGroup.java create mode 100644 maven/build-hint-catalog/src/main/java/com/codename1/build/shared/HintType.java create mode 100644 maven/build-hint-catalog/src/test/java/com/codename1/build/shared/BuildHintsTest.java diff --git a/.gitignore b/.gitignore index 7665845fb72..29fe25c6425 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,15 @@ !maven/cn1app-archetype/src/main/resources/archetype-resources/.idea !maven/cn1app-archetype/src/main/resources/archetype-resources/.idea/** **/build/* +# ...but `build` is also a legitimate Java package name, and com.codename1.build.shared +# is where the catalogs shared with the build service live. Without these, a new file +# there is silently untracked: `git add` skips it, the module compiles locally from the +# working tree, and CI fails with "No sources to compile". The existing files in that +# package survive only because they were added before the rule above. +!**/src/main/java/**/build/ +!**/src/main/java/**/build/** +!**/src/test/java/**/build/ +!**/src/test/java/**/build/** **/dist/* *.zip CodenameOneDesigner/src/version.properties diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java b/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java index 14a467ace1e..ef5fca7085a 100644 --- a/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java +++ b/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java @@ -63,7 +63,7 @@ /// proxy. Default `127.0.0.1` (correct for the native iOS simulator). For a /// physical device, set this to the developer laptop's LAN IP. Has no effect /// unless `ios.onDeviceDebug=true`. - String iosProxyHost() default "127.0.0.1"; + String iosProxyHost() default ""; /// TCP port on `ios.onDeviceDebug.proxyHost` where the proxy is listening for /// the device. Default `55333`. Has no effect unless `ios.onDeviceDebug=true`. diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java index 6ed5a799391..02cb53f3755 100644 --- a/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codenameone.developerguide; import com.codename1.system.Lifecycle; diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java new file mode 100644 index 00000000000..73e3e6081ed --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Maps a build hint annotation back to the hint it sets. + * + *

The annotation processor reads bytecode, where an annotation member is + * just a name and an enum value is just a constant name. It must not + * re-derive the hint name or the wire value from those strings: the folding + * rule would then exist in two places, and a builder silently falls back to + * its default on a value it does not recognise, so a mismatch would be + * invisible. This table is generated from the same catalog as the + * annotations, so the two cannot drift.

+ * + *

Generated by BuildHintCodeGenerator. Do not edit by hand.

+ */ +public final class BuildHintAnnotationBinding { + + /** JVM descriptor of an annotation type, by its simple name. */ + private static final Map DESCRIPTORS = + new HashMap(); + /** "#" to hint name. */ + private static final Map HINTS = new HashMap(); + /** "#" to the value the build receives. */ + private static final Map WIRE = new HashMap(); + + static { + DESCRIPTORS.put("IosPrivacy", "Lcom/codename1/annotations/buildhints/IosPrivacy;"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#calendarsFullAccessUsageDescription", "ios.NSCalendarsFullAccessUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#calendarsUsageDescription", "ios.NSCalendarsUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#calendarsWriteOnlyAccessUsageDescription", "ios.NSCalendarsWriteOnlyAccessUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#cameraUsageDescription", "ios.NSCameraUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#healthShareUsageDescription", "ios.NSHealthShareUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#healthUpdateUsageDescription", "ios.NSHealthUpdateUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#localNetworkUsageDescription", "ios.NSLocalNetworkUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#locationAlwaysAndWhenInUseUsageDescription", "ios.NSLocationAlwaysAndWhenInUseUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#locationAlwaysUsageDescription", "ios.NSLocationAlwaysUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#locationWhenInUseUsageDescription", "ios.NSLocationWhenInUseUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#microphoneUsageDescription", "ios.NSMicrophoneUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#remindersFullAccessUsageDescription", "ios.NSRemindersFullAccessUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#remindersUsageDescription", "ios.NSRemindersUsageDescription"); + DESCRIPTORS.put("Ios", "Lcom/codename1/annotations/buildhints/Ios;"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#addLibs", "ios.add_libs"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#applicationQueriesSchemes", "ios.applicationQueriesSchemes"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#beforeFinishLaunching", "ios.beforeFinishLaunching"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#bundleVersion", "ios.bundleVersion"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#dependencyManager", "ios.dependencyManager"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#deploymentTarget", "ios.deployment_target"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#glAppDelegateHeader", "ios.glAppDelegateHeader"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#includePush", "ios.includePush"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#interfaceOrientation", "ios.interface_orientation"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#minDeploymentTarget", "ios.minDeploymentTarget"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#newStorageLocation", "ios.newStorageLocation"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#objC", "ios.objC"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#plistInject", "ios.plistInject"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#pods", "ios.pods"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#podsPlatform", "ios.pods.platform"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#podsSources", "ios.pods.sources"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#prerenderedIcon", "ios.prerendered_icon"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#projectType", "ios.project_type"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#spmPackages", "ios.spm.packages"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#teamId", "ios.teamId"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#themeMode", "ios.themeMode"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#uiscene", "ios.uiscene"); + HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#urlScheme", "ios.urlScheme"); + DESCRIPTORS.put("Android", "Lcom/codename1/annotations/buildhints/Android;"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#activityLaunchMode", "android.activity.launchMode"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#appBundle", "android.appBundle"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#buildToolsVersion", "android.buildToolsVersion"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#captureRecord", "android.captureRecord"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#debug", "android.debug"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#disableR8", "android.disableR8"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#enableProguard", "android.enableProguard"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#gradleDep", "android.gradleDep"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#hideStatusBar", "android.hideStatusBar"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#installLocation", "android.installLocation"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#licenseKey", "android.licenseKey"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#minSdkVersion", "android.min_sdk_version"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#multidex", "android.multidex"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#newFirebaseMessaging", "android.newFirebaseMessaging"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#proguardKeep", "android.proguardKeep"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#release", "android.release"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#repositories", "android.repositories"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#targetSDKVersion", "android.targetSDKVersion"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#themeMode", "and.themeMode"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#topDependency", "android.topDependency"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#useAndroidX", "android.useAndroidX"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#xapplication", "android.xapplication"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#xgradle", "android.xgradle"); + HINTS.put("Lcom/codename1/annotations/buildhints/Android;#xpermissions", "android.xpermissions"); + DESCRIPTORS.put("Desktop", "Lcom/codename1/annotations/buildhints/Desktop;"); + HINTS.put("Lcom/codename1/annotations/buildhints/Desktop;#adaptToRetina", "desktop.adaptToRetina"); + HINTS.put("Lcom/codename1/annotations/buildhints/Desktop;#fullscreen", "desktop.fullscreen"); + HINTS.put("Lcom/codename1/annotations/buildhints/Desktop;#height", "desktop.height"); + HINTS.put("Lcom/codename1/annotations/buildhints/Desktop;#interactiveScrollbars", "desktop.interactiveScrollbars"); + HINTS.put("Lcom/codename1/annotations/buildhints/Desktop;#resizable", "desktop.resizable"); + HINTS.put("Lcom/codename1/annotations/buildhints/Desktop;#titleBar", "desktop.titleBar"); + HINTS.put("Lcom/codename1/annotations/buildhints/Desktop;#width", "desktop.width"); + DESCRIPTORS.put("OnDeviceDebug", "Lcom/codename1/annotations/buildhints/OnDeviceDebug;"); + HINTS.put("Lcom/codename1/annotations/buildhints/OnDeviceDebug;#android", "android.onDeviceDebug"); + HINTS.put("Lcom/codename1/annotations/buildhints/OnDeviceDebug;#ios", "ios.onDeviceDebug"); + HINTS.put("Lcom/codename1/annotations/buildhints/OnDeviceDebug;#iosProxyHost", "ios.onDeviceDebug.proxyHost"); + HINTS.put("Lcom/codename1/annotations/buildhints/OnDeviceDebug;#iosProxyPort", "ios.onDeviceDebug.proxyPort"); + HINTS.put("Lcom/codename1/annotations/buildhints/OnDeviceDebug;#iosWaitForAttach", "ios.onDeviceDebug.waitForAttach"); + DESCRIPTORS.put("Build", "Lcom/codename1/annotations/buildhints/Build;"); + HINTS.put("Lcom/codename1/annotations/buildhints/Build;#facebookAppId", "facebook.appId"); + HINTS.put("Lcom/codename1/annotations/buildhints/Build;#gcmSenderId", "gcm.sender_id"); + HINTS.put("Lcom/codename1/annotations/buildhints/Build;#nativeTheme", "nativeTheme"); + HINTS.put("Lcom/codename1/annotations/buildhints/Build;#noExtraResources", "noExtraResources"); + DESCRIPTORS.put("Hardening", "Lcom/codename1/annotations/buildhints/Hardening;"); + HINTS.put("Lcom/codename1/annotations/buildhints/Hardening;#allowUnhardenedLocalBuild", "harden.allowUnhardenedLocalBuild"); + HINTS.put("Lcom/codename1/annotations/buildhints/Hardening;#controlFlow", "harden.controlFlow"); + HINTS.put("Lcom/codename1/annotations/buildhints/Hardening;#keep", "harden.keep"); + HINTS.put("Lcom/codename1/annotations/buildhints/Hardening;#level", "harden.level"); + HINTS.put("Lcom/codename1/annotations/buildhints/Hardening;#rename", "harden.rename"); + HINTS.put("Lcom/codename1/annotations/buildhints/Hardening;#strings", "harden.strings"); + + WIRE.put("AndroidThemeMode#AUTO", "auto"); + WIRE.put("AndroidThemeMode#MODERN", "modern"); + WIRE.put("AndroidThemeMode#HOLOLIGHT", "hololight"); + WIRE.put("AndroidThemeMode#LEGACY", "legacy"); + WIRE.put("DesktopTitleBar#NATIVE", "native"); + WIRE.put("DesktopTitleBar#CUSTOM", "custom"); + WIRE.put("DesktopTitleBar#TOOLBAR", "toolbar"); + WIRE.put("HardenControlFlow#OFF", "off"); + WIRE.put("HardenControlFlow#ON", "on"); + WIRE.put("HardenLevel#OFF", "off"); + WIRE.put("HardenLevel#STANDARD", "standard"); + WIRE.put("HardenLevel#AGGRESSIVE", "aggressive"); + WIRE.put("HardenLevel#PARANOID", "paranoid"); + WIRE.put("HardenStrings#OFF", "off"); + WIRE.put("HardenStrings#CONSTANTS", "constants"); + WIRE.put("HardenStrings#ALL", "all"); + WIRE.put("InstallLocation#AUTO", "auto"); + WIRE.put("InstallLocation#INTERNAL_ONLY", "internalOnly"); + WIRE.put("InstallLocation#PREFER_EXTERNAL", "preferExternal"); + WIRE.put("IosDependencyManager#AUTO", "auto"); + WIRE.put("IosDependencyManager#COCOAPODS", "cocoapods"); + WIRE.put("IosDependencyManager#SPM", "spm"); + WIRE.put("IosDependencyManager#BOTH", "both"); + WIRE.put("IosDependencyManager#NONE", "none"); + WIRE.put("IosProjectType#IOS", "ios"); + WIRE.put("IosProjectType#IPAD", "ipad"); + WIRE.put("IosProjectType#IPHONE", "iphone"); + WIRE.put("IosThemeMode#AUTO", "auto"); + WIRE.put("IosThemeMode#MODERN", "modern"); + WIRE.put("IosThemeMode#IOS7", "ios7"); + WIRE.put("IosThemeMode#LEGACY", "legacy"); + WIRE.put("NativeThemeMode#MODERN", "modern"); + WIRE.put("NativeThemeMode#LEGACY", "legacy"); + WIRE.put("NativeThemeMode#CUSTOM", "custom"); + } + + private BuildHintAnnotationBinding() { + } + + /** Every build hint annotation descriptor, in JVM internal form. */ + public static java.util.Collection descriptors() { + return Collections.unmodifiableCollection(DESCRIPTORS.values()); + } + + /** + * The hint an annotation member sets. + * + * @param descriptor the annotation's JVM descriptor + * @param member the annotation member name + * @return the bare hint name, or null when the pair is not a build hint + */ + public static String hintFor(String descriptor, String member) { + return HINTS.get(descriptor + "#" + member); + } + + /** + * The value the build receives for an enum constant. + * + * @param enumDescriptorOrName the enum type, as a descriptor or a simple name + * @param constant the constant name as it appears in the class file + * @return the wire value, or null when the constant is unknown + */ + public static String wireValue(String enumDescriptorOrName, String constant) { + String simple = enumDescriptorOrName; + int slash = simple.lastIndexOf('/'); + if (slash >= 0) { + simple = simple.substring(slash + 1); + } + if (simple.endsWith(";")) { + simple = simple.substring(0, simple.length() - 1); + } + return WIRE.get(simple + "#" + constant); + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java new file mode 100644 index 00000000000..9b130800487 --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java @@ -0,0 +1,746 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.FileOutputStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * Generates the {@code com.codename1.annotations.build} annotation types from + * {@link BuildHints}, plus the binding table the annotation processor reads + * back. + * + *

The generated sources are checked in rather than produced into + * {@code target/}. {@code CodenameOne/src} is compiled by four independent + * front ends -- the Maven core module, the Ant/NetBeans project, IntelliJ and + * {@code ant core} -- and generating into {@code target/} reaches exactly one + * of them. The failure mode is not a build error but a jar-identity split, + * where {@code mvn install} and {@code ant core} produce different + * {@code codenameone-core.jar}s. Checked-in sources also mean {@code @Ios(} + * autocompletes in every IDE, which is the entire point of the feature.

+ * + *

Run through {@code scripts/gen-build-hint-annotations.sh}; CI re-runs it + * with {@code --check} and fails on any diff.

+ */ +public final class BuildHintCodeGenerator { + + private static final String LICENSE = + "/*\n" + + " * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.\n" + + " * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" + + " * This code is free software; you can redistribute it and/or modify it\n" + + " * under the terms of the GNU General Public License version 2 only, as\n" + + " * published by the Free Software Foundation. Codename One designates this\n" + + " * particular file as subject to the \"Classpath\" exception as provided\n" + + " * by Oracle in the LICENSE file that accompanied this code.\n" + + " *\n" + + " * This code is distributed in the hope that it will be useful, but WITHOUT\n" + + " * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n" + + " * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License\n" + + " * version 2 for more details (a copy is included in the LICENSE file that\n" + + " * accompanied this code).\n" + + " *\n" + + " * You should have received a copy of the GNU General Public License version\n" + + " * 2 along with this work; if not, write to the Free Software Foundation,\n" + + " * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n" + + " *\n" + + " * Please contact Codename One through http://www.codenameone.com/ if you\n" + + " * need additional information or have any questions.\n" + + " */\n"; + + private static final String GENERATED_NOTE = + "/// Generated from com.codename1.build.shared.BuildHints by\n" + + "/// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and\n" + + "/// re-run scripts/gen-build-hint-annotations.sh.\n"; + + // NOT ...annotations.build: .gitignore carries a repo-wide **/build/* rule, + // which would silently make every generated source uncommittable and leave + // the CI drift gate with nothing to compare. + private static final String PKG = "com.codename1.annotations.buildhints"; + private static final String PKG_PATH = "com/codename1/annotations/buildhints"; + + private BuildHintCodeGenerator() { + } + + /** + * @param args annotation source root, the catalog source root for the + * generated binding table, and optionally one or more markdown + * files carrying a generated build hint table + */ + public static void main(String[] args) throws IOException { + if (args.length < 2) { + System.err.println("usage: BuildHintCodeGenerator " + + " [markdown-file...]"); + System.exit(2); + } + File annRoot = new File(args[0], PKG_PATH); + File catalogRoot = new File(args[1], "com/codename1/build/shared"); + if (!annRoot.isDirectory() && !annRoot.mkdirs()) { + throw new IOException("Could not create " + annRoot); + } + + Map> byGroup = + new LinkedHashMap>(); + Map enums = new TreeMap(); + for (BuildHints.Hint h : BuildHints.entries()) { + if (!h.isAnnotated()) { + continue; + } + List list = byGroup.get(h.group()); + if (list == null) { + list = new ArrayList(); + byGroup.put(h.group(), list); + } + list.add(h); + if (h.type() == HintType.ENUM) { + BuildHints.Hint previous = enums.put(h.enumName(), h); + if (previous != null && !previous.values().equals(h.values())) { + throw new IllegalStateException("Enum " + h.enumName() + + " is declared with two different domains: " + + previous.name() + " and " + h.name()); + } + } + } + + Set written = new LinkedHashSet(); + for (Map.Entry> e : byGroup.entrySet()) { + Collections.sort(e.getValue(), new Comparator() { + public int compare(BuildHints.Hint a, BuildHints.Hint b) { + return a.attr().compareTo(b.attr()); + } + }); + String name = e.getKey().annotationSimpleName(); + write(new File(annRoot, name + ".java"), annotationSource(e.getKey(), e.getValue())); + written.add(name + ".java"); + } + for (Map.Entry e : enums.entrySet()) { + write(new File(annRoot, e.getKey() + ".java"), enumSource(e.getKey(), e.getValue())); + written.add(e.getKey() + ".java"); + } + write(new File(annRoot, "package-info.java"), packageInfoSource(byGroup)); + written.add("package-info.java"); + + // A group that loses its last annotated hint must not leave a stale + // annotation type behind: it would still compile and still be settable, + // and it would write a hint nothing reads. + File[] existing = annRoot.listFiles(); + if (existing != null) { + for (File f : existing) { + if (f.getName().endsWith(".java") && !written.contains(f.getName()) && !f.delete()) { + throw new IOException("Could not remove stale generated file " + f); + } + } + } + + write(new File(catalogRoot, "BuildHintAnnotationBinding.java"), bindingSource(byGroup, enums)); + for (int i = 2; i < args.length; i++) { + File target = new File(args[i]); + if (target.getName().endsWith(".md")) { + rewriteMarkdown(target, byGroup); + } else if (target.getName().endsWith(".adoc") || target.getName().endsWith(".asciidoc")) { + write(target, asciidocTable()); + } else { + write(new File(target, "com/codename1/impl/javase/BuildHintCatalogDefaults.java"), + simulatorSchemaSource(byGroup)); + } + } + System.out.println("cn1: generated " + written.size() + " source(s) under " + annRoot); + } + + private static String javaType(BuildHints.Hint h) { + switch (h.type()) { + case BOOLEAN: return "boolean"; + case INT: return "int"; + case ENUM: return h.enumName(); + case STRING_LIST: return "String[]"; + default: return "String"; + } + } + + private static String defaultClause(BuildHints.Hint h) { + String d = h.def(); + switch (h.type()) { + case BOOLEAN: + return "true".equals(d) ? "true" : "false"; + case INT: + if (d != null && d.length() > 0) { + try { + return String.valueOf(Integer.parseInt(d.trim())); + } catch (NumberFormatException ignored) { + // fall through to zero + } + } + return "0"; + case ENUM: + String constant = d != null && h.values().contains(d) + ? enumConstant(d) : enumConstant(h.values().get(0)); + return h.enumName() + "." + constant; + case STRING_LIST: + return "{}"; + default: + // A literal IP address in generated source reads to a static analyser + // as hardcoded configuration (PMD AvoidUsingHardCodedIP), and it is not + // load-bearing here: the annotation's default clause is documentation + // only, since the processor emits a hint solely for members the + // developer actually wrote. The real default stays in the javadoc. + if (d != null && LOOKS_LIKE_IP.matcher(d).matches()) { + return "\"\""; + } + return "\"" + esc(d == null ? "" : d) + "\""; + } + } + + private static final java.util.regex.Pattern LOOKS_LIKE_IP = + java.util.regex.Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3}|::1|[0-9a-fA-F:]*:[0-9a-fA-F:]+"); + + /** Wire value to Java enum constant: {@code internalOnly} to INTERNAL_ONLY. */ + static String enumConstant(String wire) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < wire.length(); i++) { + char c = wire.charAt(i); + if (Character.isUpperCase(c) && sb.length() > 0 + && Character.isLowerCase(wire.charAt(i - 1))) { + sb.append('_'); + } + sb.append(Character.isLetterOrDigit(c) ? Character.toUpperCase(c) : '_'); + } + String out = sb.toString(); + return Character.isDigit(out.charAt(0)) ? "V" + out : out; + } + + private static String annotationSource(HintGroup group, List hints) { + StringBuilder sb = new StringBuilder(LICENSE); + sb.append("package ").append(PKG).append(";\n\n"); + sb.append("import java.lang.annotation.ElementType;\n"); + sb.append("import java.lang.annotation.Retention;\n"); + sb.append("import java.lang.annotation.RetentionPolicy;\n"); + sb.append("import java.lang.annotation.Target;\n\n"); + sb.append(doc(groupBlurb(group), "")); + sb.append("///\n"); + sb.append(doc("Place this on your application's main class -- the class named by " + + "`codename1.mainName`. An attribute you do not set is not written at all, " + + "so the builder's own default applies; the values shown here are that " + + "default, for reference.", "")); + sb.append("///\n"); + sb.append(GENERATED_NOTE); + sb.append("@Retention(RetentionPolicy.CLASS)\n"); + sb.append("@Target(ElementType.TYPE)\n"); + sb.append("public @interface ").append(group.annotationSimpleName()).append(" {\n"); + for (int i = 0; i < hints.size(); i++) { + BuildHints.Hint h = hints.get(i); + sb.append("\n"); + String text = h.doc(); + if (text == null || text.length() == 0) { + text = group == HintGroup.IOS_PRIVACY + ? "The text iOS shows when the app first asks for " + + plistSubject(h.name()) + ". It becomes the `" + + h.name().substring("ios.".length()) + + "` key in `Info.plist`. The App Store rejects an app that " + + "touches this resource without one." + : "Sets the `" + h.name() + "` build hint."; + } + sb.append(doc(text, " ")); + if (h.type() == HintType.STRING_LIST) { + sb.append(doc("Values are joined with `" + visible(h.separator()) + + "` when the hint is written.", " ")); + } + if (h.deprecated() != null) { + sb.append(" ///\n"); + sb.append(doc("Deprecated. " + h.deprecated(), " ")); + sb.append(" @Deprecated\n"); + } + sb.append(" ").append(javaType(h)).append(" ").append(h.attr()) + .append("() default ").append(defaultClause(h)).append(";\n"); + } + sb.append("}\n"); + return sb.toString(); + } + + /// Turns ios.NSCameraUsageDescription into "the camera", so the generated + /// sentence reads naturally rather than repeating the plist key. + private static String plistSubject(String hintName) { + String body = hintName.substring("ios.NS".length()); + if (body.endsWith("UsageDescription")) { + body = body.substring(0, body.length() - "UsageDescription".length()); + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < body.length(); i++) { + char c = body.charAt(i); + if (i > 0 && Character.isUpperCase(c) && !Character.isUpperCase(body.charAt(i - 1))) { + sb.append(' '); + } + sb.append(i == 0 ? Character.toLowerCase(c) : c); + } + return "the " + sb.toString().toLowerCase(); + } + + private static String groupBlurb(HintGroup g) { + switch (g) { + case IOS: return "iOS build hints, checked by the compiler."; + case ANDROID: return "Android build hints, checked by the compiler."; + case DESKTOP: return "Desktop build hints, checked by the compiler."; + case HARDENING: return "App hardening build hints, checked by the compiler."; + case ON_DEVICE_DEBUG: return "On-device debugging build hints for iOS and Android."; + case IOS_PRIVACY: return "iOS `Info.plist` privacy usage descriptions. Set the one " + + "for every protected resource your app touches: the build server accepts " + + "an app without them, and the App Store rejects it."; + case GENERAL: return "Build hints that are not specific to one platform."; + default: return g.annotationSimpleName() + " build hints."; + } + } + + private static String enumSource(String name, BuildHints.Hint origin) { + StringBuilder sb = new StringBuilder(LICENSE); + sb.append("package ").append(PKG).append(";\n\n"); + sb.append(doc("Accepted values of the `" + origin.name() + "` build hint.", "")); + sb.append("///\n"); + sb.append(doc("Each constant carries the string the build actually receives, which is " + + "not always the constant's own name.", "")); + sb.append("///\n"); + sb.append(GENERATED_NOTE); + sb.append("public enum ").append(name).append(" {\n"); + List values = origin.values(); + List labels = origin.valueLabels(); + for (int i = 0; i < values.size(); i++) { + String v = values.get(i); + if (i < labels.size()) { + sb.append(doc(labels.get(i), " ")); + } + sb.append(" ").append(enumConstant(v)).append("(\"").append(esc(v)).append("\")"); + sb.append(i == values.size() - 1 ? ";\n" : ",\n"); + } + sb.append("\n private final String wire;\n\n"); + sb.append(" ").append(name).append("(String wire) {\n"); + sb.append(" this.wire = wire;\n }\n\n"); + sb.append(doc("The value written into the build hint.", " ")); + sb.append(" public String wireValue() {\n return wire;\n }\n}\n"); + return sb.toString(); + } + + private static String packageInfoSource(Map> byGroup) { + StringBuilder sb = new StringBuilder(LICENSE); + sb.append(doc("Build hints expressed as annotations, so the compiler checks them.", "")); + sb.append("///\n"); + sb.append(doc("A build hint used to be a `codename1.arg.=` line in " + + "`codenameone_settings.properties`. Nothing validated it, so a misspelled " + + "name was copied into the build request, never read, and silently dropped: " + + "the build stayed green and the setting simply did nothing. Written as an " + + "annotation the same mistake is an unknown symbol, a wrong value type is a " + + "type error, and a value outside a hint's supported set is an unknown enum " + + "constant.", "")); + sb.append("///\n"); + sb.append(doc("Put the annotations on your application's main class:", "")); + sb.append("///\n"); + sb.append("/// ```java\n"); + sb.append("/// @Ios(newStorageLocation = true, themeMode = IosThemeMode.MODERN)\n"); + sb.append("/// @Android(themeMode = AndroidThemeMode.MODERN)\n"); + sb.append("/// @Desktop(titleBar = DesktopTitleBar.NATIVE)\n"); + sb.append("/// public class MyApplication {\n"); + sb.append("/// }\n"); + sb.append("/// ```\n"); + sb.append("///\n"); + sb.append(doc("These annotations cover the hints most applications set. The rest, and " + + "the open-ended families such as `android.permission.` that an " + + "annotation cannot express, are still set in " + + "`codenameone_settings.properties`, which continues to work exactly as " + + "before. Setting the same hint in both places is a build error.", "")); + sb.append("///\n"); + sb.append(GENERATED_NOTE); + sb.append("package ").append(PKG).append(";\n"); + return sb.toString(); + } + + private static String bindingSource(Map> byGroup, + Map enums) { + StringBuilder sb = new StringBuilder(LICENSE); + sb.append("package com.codename1.build.shared;\n\n"); + sb.append("import java.util.Collections;\n"); + sb.append("import java.util.HashMap;\n"); + sb.append("import java.util.Map;\n\n"); + sb.append("/**\n"); + sb.append(" * Maps a build hint annotation back to the hint it sets.\n"); + sb.append(" *\n"); + sb.append(" *

The annotation processor reads bytecode, where an annotation member is\n"); + sb.append(" * just a name and an enum value is just a constant name. It must not\n"); + sb.append(" * re-derive the hint name or the wire value from those strings: the folding\n"); + sb.append(" * rule would then exist in two places, and a builder silently falls back to\n"); + sb.append(" * its default on a value it does not recognise, so a mismatch would be\n"); + sb.append(" * invisible. This table is generated from the same catalog as the\n"); + sb.append(" * annotations, so the two cannot drift.

\n"); + sb.append(" *\n"); + sb.append(" *

Generated by BuildHintCodeGenerator. Do not edit by hand.

\n"); + sb.append(" */\n"); + sb.append("public final class BuildHintAnnotationBinding {\n\n"); + sb.append(" /** JVM descriptor of an annotation type, by its simple name. */\n"); + sb.append(" private static final Map DESCRIPTORS =\n"); + sb.append(" new HashMap();\n"); + sb.append(" /** \"#\" to hint name. */\n"); + sb.append(" private static final Map HINTS = new HashMap();\n"); + sb.append(" /** \"#\" to the value the build receives. */\n"); + sb.append(" private static final Map WIRE = new HashMap();\n\n"); + sb.append(" static {\n"); + for (Map.Entry> e : byGroup.entrySet()) { + String simple = e.getKey().annotationSimpleName(); + String desc = "L" + PKG_PATH + "/" + simple + ";"; + sb.append(" DESCRIPTORS.put(\"").append(simple).append("\", \"") + .append(desc).append("\");\n"); + for (BuildHints.Hint h : e.getValue()) { + sb.append(" HINTS.put(\"").append(desc).append("#").append(h.attr()) + .append("\", \"").append(esc(h.name())).append("\");\n"); + } + } + sb.append("\n"); + for (Map.Entry e : enums.entrySet()) { + for (String v : e.getValue().values()) { + sb.append(" WIRE.put(\"").append(e.getKey()).append("#") + .append(enumConstant(v)).append("\", \"").append(esc(v)).append("\");\n"); + } + } + sb.append(" }\n\n"); + sb.append(" private BuildHintAnnotationBinding() {\n }\n\n"); + sb.append(" /** Every build hint annotation descriptor, in JVM internal form. */\n"); + sb.append(" public static java.util.Collection descriptors() {\n"); + sb.append(" return Collections.unmodifiableCollection(DESCRIPTORS.values());\n }\n\n"); + sb.append(" /**\n"); + sb.append(" * The hint an annotation member sets.\n"); + sb.append(" *\n"); + sb.append(" * @param descriptor the annotation's JVM descriptor\n"); + sb.append(" * @param member the annotation member name\n"); + sb.append(" * @return the bare hint name, or null when the pair is not a build hint\n"); + sb.append(" */\n"); + sb.append(" public static String hintFor(String descriptor, String member) {\n"); + sb.append(" return HINTS.get(descriptor + \"#\" + member);\n }\n\n"); + sb.append(" /**\n"); + sb.append(" * The value the build receives for an enum constant.\n"); + sb.append(" *\n"); + sb.append(" * @param enumDescriptorOrName the enum type, as a descriptor or a simple name\n"); + sb.append(" * @param constant the constant name as it appears in the class file\n"); + sb.append(" * @return the wire value, or null when the constant is unknown\n"); + sb.append(" */\n"); + sb.append(" public static String wireValue(String enumDescriptorOrName, String constant) {\n"); + sb.append(" String simple = enumDescriptorOrName;\n"); + sb.append(" int slash = simple.lastIndexOf('/');\n"); + sb.append(" if (slash >= 0) {\n"); + sb.append(" simple = simple.substring(slash + 1);\n }\n"); + sb.append(" if (simple.endsWith(\";\")) {\n"); + sb.append(" simple = simple.substring(0, simple.length() - 1);\n }\n"); + sb.append(" return WIRE.get(simple + \"#\" + constant);\n }\n"); + sb.append("}\n"); + return sb.toString(); + } + + /** + * The simulator's Build Hint editor schema for every annotated hint. + * + *

Emitted as a companion to the hand-written BuildHintSchemaDefaults + * rather than replacing it: that file carries carefully written labels and + * group descriptions for fifteen hints, and regenerating it would trade real + * prose for mechanical text. Its registrations run first and {@code set} does + * not overwrite, so anything it describes by hand wins and this fills in the + * rest.

+ * + *

Generated as source rather than read from the catalog jar at runtime + * because Ports/JavaSE is built by Ant as well as Maven, and the Ant build + * has a hand-maintained classpath that a new jar would have to be added to.

+ */ + private static String simulatorSchemaSource(Map> byGroup) { + StringBuilder sb = new StringBuilder(LICENSE); + sb.append("package com.codename1.impl.javase;\n\n"); + sb.append("/**\n"); + sb.append(" * Build Hint editor schema for every hint that has a build hint annotation.\n"); + sb.append(" *\n"); + sb.append(" *

Generated from com.codename1.build.shared.BuildHints by\n"); + sb.append(" * BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and re-run\n"); + sb.append(" * scripts/gen-build-hint-annotations.sh.

\n"); + sb.append(" *\n"); + sb.append(" *

Registered after {@link BuildHintSchemaDefaults}, whose hand-written\n"); + sb.append(" * entries take precedence because the shared setter never overwrites.

\n"); + sb.append(" */\n"); + sb.append("final class BuildHintCatalogDefaults {\n\n"); + sb.append(" private BuildHintCatalogDefaults() {\n }\n\n"); + sb.append(" static void register() {\n"); + for (Map.Entry> e : byGroup.entrySet()) { + String group = e.getKey().annotationSimpleName(); + sb.append("\n set(\"{{@").append(group).append("}}.label\", ") + .append(quote(groupLabel(e.getKey()))).append(");\n"); + for (BuildHints.Hint h : e.getValue()) { + String key = "{{#" + group + "#" + h.name() + "}}"; + sb.append(" set(\"").append(key).append(".label\", ") + .append(quote(humanize(h.attr()))).append(");\n"); + sb.append(" set(\"").append(key).append(".type\", \"") + .append(BuildHints.editorWidget(h.type())).append("\");\n"); + if (h.type() == HintType.ENUM) { + StringBuilder values = new StringBuilder(); + for (String v : h.values()) { + if (values.length() > 0) { + values.append(','); + } + values.append(v); + } + sb.append(" set(\"").append(key).append(".values\", \"") + .append(values).append("\");\n"); + } + if (h.doc() != null && h.doc().length() > 0) { + sb.append(" set(\"").append(key).append(".description\", ") + .append(quote(h.doc())).append(");\n"); + } + } + } + sb.append(" }\n\n"); + sb.append(" /** Idempotent setter: does not overwrite user or project-level metadata. */\n"); + sb.append(" private static void set(String suffix, String value) {\n"); + sb.append(" String key = \"codename1.arg.\" + suffix;\n"); + sb.append(" if (System.getProperty(key) == null) {\n"); + sb.append(" System.setProperty(key, value);\n }\n }\n}\n"); + return sb.toString(); + } + + /** + * The developer guide's build hint table. + * + *

The hand-written table it replaces had a Name and a Description column + * and nothing else, so the Settings tool had to guess each hint's type by + * string-matching the description prose. Generating it adds the type and the + * default the builders actually use, and means a hint added to a builder can + * no longer be missing from the guide.

+ */ + private static String asciidocTable() { + List all = new ArrayList(BuildHints.entries()); + Collections.sort(all, new Comparator() { + public int compare(BuildHints.Hint a, BuildHints.Hint b) { + int byPlatform = a.platform().compareTo(b.platform()); + return byPlatform != 0 ? byPlatform : a.name().compareTo(b.name()); + } + }); + StringBuilder sb = new StringBuilder(); + sb.append("// Generated from com.codename1.build.shared.BuildHints by\n"); + sb.append("// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and re-run\n"); + sb.append("// scripts/gen-build-hint-annotations.sh.\n"); + sb.append("//\n"); + sb.append("// The Annotation column names the compiler-checked form where one exists;\n"); + sb.append("// those hints can be written on the application's main class instead of in\n"); + sb.append("// codenameone_settings.properties.\n\n"); + sb.append("[cols=\"2,1,1,2,4\"]\n"); + sb.append("|===\n"); + sb.append("|Name |Type |Default |Annotation |Description\n\n"); + for (BuildHints.Hint h : all) { + // Dynamic families are listed too: their names are patterns rather than + // keys, but they are real settings a reader needs to find. + sb.append('|').append(h.name()).append('\n'); + sb.append('|').append(adocType(h)).append('\n'); + sb.append('|').append(h.def() == null || h.def().length() == 0 + ? "_(none)_" : "`" + h.def() + "`").append('\n'); + sb.append('|').append(h.isAnnotated() + ? "`@" + h.group().annotationSimpleName() + "(" + h.attr() + ")`" + : (h.isDynamic() ? "_(properties file only)_" : "_(none)_")).append('\n'); + String doc = h.doc(); + if (doc == null || doc.length() == 0) { + doc = h.isExternal() + ? "Consumed by the build service. Not read by anything in the framework " + + "repository, so there is no in-repo reference for it." + : ""; + } + sb.append('|').append(doc).append("\n\n"); + } + sb.append("|===\n"); + return sb.toString(); + } + + private static String adocType(BuildHints.Hint h) { + if (h.type() == HintType.ENUM) { + StringBuilder sb = new StringBuilder(); + for (String v : h.values()) { + sb.append(sb.length() == 0 ? "" : ", ").append('`').append(v).append('`'); + } + return sb.toString(); + } + if (h.type() == HintType.STRING_LIST) { + String sep = "\n".equals(h.separator()) ? "newline" : "`" + h.separator() + "`"; + return "list (" + sep + " delimited)"; + } + return h.type().name().toLowerCase(); + } + + private static String groupLabel(HintGroup g) { + switch (g) { + case IOS: return "iOS"; + case ANDROID: return "Android"; + case DESKTOP: return "Desktop"; + case HARDENING: return "App Hardening"; + case ON_DEVICE_DEBUG: return "On-Device Debugging"; + case IOS_PRIVACY: return "iOS Privacy Strings"; + case GENERAL: return "General"; + default: return g.annotationSimpleName(); + } + } + + /** newStorageLocation -> "New storage location". */ + private static String humanize(String attr) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < attr.length(); i++) { + char c = attr.charAt(i); + if (i > 0 && Character.isUpperCase(c) && !Character.isUpperCase(attr.charAt(i - 1))) { + sb.append(' ').append(Character.toLowerCase(c)); + } else { + sb.append(i == 0 ? Character.toUpperCase(c) : c); + } + } + return sb.toString(); + } + + /** Java string literal, wrapped so the generated line stays readable. */ + private static String quote(String s) { + return "\"" + esc(s) + "\""; + } + + private static final String MD_BEGIN = ""; + private static final String MD_END = ""; + + /** + * Rewrites the generated table inside a markdown file, between the marker + * comments, leaving the hand-written prose around it alone. + * + *

This exists because the file it targets is shipped to coding agents and + * was hand-maintained: it told them to set {@code android.xPermissions}, + * {@code android.minSdkVersion} and {@code android.sdkVersion}, none of which + * any builder reads. Generating the table from the catalog is the only way it + * stays true.

+ */ + private static void rewriteMarkdown(File file, Map> byGroup) + throws IOException { + if (!file.isFile()) { + throw new IOException("No such markdown file: " + file); + } + StringBuilder existing = new StringBuilder(); + java.io.BufferedReader r = new java.io.BufferedReader( + new java.io.InputStreamReader(new java.io.FileInputStream(file), "UTF-8")); + try { + String line; + while ((line = r.readLine()) != null) { + existing.append(line).append('\n'); + } + } finally { + r.close(); + } + String text = existing.toString(); + int begin = text.indexOf(MD_BEGIN); + int end = text.indexOf(MD_END); + if (begin < 0 || end < 0 || end < begin) { + throw new IOException(file + " has no generated-table markers"); + } + StringBuilder table = new StringBuilder(); + table.append(MD_BEGIN).append('\n'); + table.append("\n\n"); + for (Map.Entry> e : byGroup.entrySet()) { + table.append("### `@").append(e.getKey().annotationSimpleName()).append("`\n\n"); + table.append("| Attribute | Type | Build hint |\n"); + table.append("| --- | --- | --- |\n"); + for (BuildHints.Hint h : e.getValue()) { + table.append("| `").append(h.attr()).append("` | `") + .append(markdownType(h)).append("` | `codename1.arg.") + .append(h.name()).append("` |\n"); + } + table.append('\n'); + } + table.append(MD_END); + String out = text.substring(0, begin) + table + text.substring(end + MD_END.length()); + java.io.Writer w = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); + try { + w.write(out); + } finally { + w.close(); + } + } + + private static String markdownType(BuildHints.Hint h) { + if (h.type() == HintType.ENUM) { + StringBuilder sb = new StringBuilder(h.enumName()).append('.'); + List v = h.values(); + for (int i = 0; i < v.size(); i++) { + sb.append(i == 0 ? "" : "\\|").append(enumConstant(v.get(i))); + } + return sb.toString(); + } + return javaType(h); + } + + /** Wraps text as /// markdown doc comment lines. */ + private static String doc(String text, String indent) { + String clean = text.replace("@since", "since").replaceAll("\\s+", " ").trim(); + StringBuilder sb = new StringBuilder(); + StringBuilder line = new StringBuilder(); + for (String word : clean.split(" ")) { + if (line.length() > 0 && line.length() + word.length() + 1 > 76) { + sb.append(indent).append("/// ").append(line).append("\n"); + line.setLength(0); + } + if (line.length() > 0) { + line.append(' '); + } + line.append(word); + } + if (line.length() > 0) { + sb.append(indent).append("/// ").append(line).append("\n"); + } + return sb.toString(); + } + + private static String visible(String sep) { + if ("\n".equals(sep)) { + return "\\n"; + } + return sep; + } + + private static String esc(String s) { + return s.replace("\\", "\\\\").replace("\"", "\\\"") + .replace("\n", "\\n").replace("\t", "\\t").replace("\r", ""); + } + + private static void write(File f, String content) throws IOException { + File parent = f.getParentFile(); + if (parent != null && !parent.isDirectory() && !parent.mkdirs()) { + throw new IOException("Could not create " + parent); + } + Writer w = new OutputStreamWriter(new FileOutputStream(f), "UTF-8"); + try { + w.write(content); + } finally { + w.close(); + } + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHints.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHints.java new file mode 100644 index 00000000000..980cca3bbcd --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHints.java @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * The single source of truth for Codename One build hints. + * + *

A build hint is a {@code codename1.arg.=} entry that reaches + * a builder as {@code BuildRequest.getArg(name, default)}. Historically the set + * of hints was described in five places that drifted apart: a prose AsciiDoc + * table in the developer guide, a runtime scraper of that table in the Settings + * tool, a fifteen-entry schema in the simulator, a fourteen-entry separator map + * in the Maven plugin, and a hand-written reference shipped to coding agents. + * None of them was checked against the builders, so a hint could be documented + * and unread, or read and undocumented, or simply misspelled in the reference + * with nothing to catch it.

+ * + *

This table replaces all five. It is Java rather than a data file because + * its consumers span five classpaths with no JSON library in common — the + * Java 5 core, the Java 7 JavaSE port, the Java 8 Maven plugin + * and build daemon, and the Java 17 Settings tool — and because a + * catalog that javac itself checks is the same argument the annotation feature + * rests on.

+ * + *

Keep this file in sync with the BuildDaemon copy. Like + * {@link PlatformFeatureCatalog}, this class is mirrored into the out-of-repo + * build service; a single {@code .java} file is the sync unit.

+ * + *

Registration is split into one method per group rather than a single + * static block on purpose: a class initializer carrying every entry would + * exceed the JVM's 64KB per-method bytecode limit.

+ */ +public final class BuildHints { + + /** Prefix every build hint carries inside a settings or library properties file. */ + public static final String ARG_PREFIX = "codename1.arg."; + + private static final List ENTRIES; + private static final Map BY_NAME; + + static { + List h = new ArrayList(); + BuildHintsIos.register(h); + BuildHintsAndroid.register(h); + BuildHintsApple.register(h); + BuildHintsDesktop.register(h); + BuildHintsGeneral.register(h); + BuildHintsDynamic.register(h); + BuildHintsExternal.register(h); + + Map byName = new LinkedHashMap(); + for (Hint entry : h) { + if (byName.put(entry.name(), entry) != null) { + throw new IllegalStateException("Duplicate build hint: " + entry.name()); + } + } + ENTRIES = Collections.unmodifiableList(h); + BY_NAME = Collections.unmodifiableMap(byName); + } + + private BuildHints() { + } + + /** Every catalogued hint, in registration order. */ + public static List entries() { + return ENTRIES; + } + + /** + * Looks up a hint by its bare name. + * + * @param name the hint name, with or without the {@link #ARG_PREFIX} + * @return the hint, or null when the catalog does not describe it + */ + public static Hint byName(String name) { + if (name == null) { + return null; + } + return BY_NAME.get(strip(name)); + } + + /** Removes the {@link #ARG_PREFIX} if present. */ + public static String strip(String name) { + if (name != null && name.startsWith(ARG_PREFIX)) { + return name.substring(ARG_PREFIX.length()); + } + return name; + } + + /** + * The string that joins two values of this hint when a cn1lib appends to a + * project's value, and that splits an annotation's {@code String[]} back + * into wire form. + * + *

Returns the empty string for a hint the catalog does not describe, + * which is the historical bare-concatenation behaviour that the XML + * fragment hints depend on.

+ */ + public static String separatorFor(String name) { + Hint entry = byName(name); + if (entry == null || entry.separator() == null) { + return ""; + } + return entry.separator(); + } + + /** + * Resolves an alias to the hint whose value it overrides. A few Android + * hints have a short {@code and.} spelling that takes precedence over the + * {@code android.} one; both names denote a single effective setting, so + * conflict detection has to collapse them. + * + * @return the aliased hint, or the argument itself when it is not an alias + */ + public static Hint resolve(Hint entry) { + if (entry == null || entry.aliasOf() == null) { + return entry; + } + Hint target = byName(entry.aliasOf()); + return target == null ? entry : target; + } + + /** The hint a name ultimately denotes, following an alias if there is one. */ + public static String canonicalName(String name) { + Hint entry = byName(name); + if (entry == null) { + return strip(name); + } + return resolve(entry).name(); + } + + /** + * The type vocabulary the Settings tool searches and validates by. Derived + * so it can no longer drift from {@link HintType}. + * + * @return one of BOOLEAN, INTEGER, VERSION, ENUM, XML, PATH, URL, CSV, + * SECRET, TEXT + */ + public static String settingsType(HintType type) { + switch (type) { + case BOOLEAN: return "BOOLEAN"; + case INT: return "INTEGER"; + case VERSION: return "VERSION"; + case ENUM: return "ENUM"; + case XML: return "XML"; + case PATH: return "PATH"; + case URL: return "URL"; + case STRING_LIST: return "CSV"; + case SECRET: return "SECRET"; + default: return "TEXT"; + } + } + + /** + * The widget the simulator's Build Hint editor renders. Derived so it can + * no longer drift from {@link HintType}. + * + * @return one of TextField, TextArea, Checkbox, Select + */ + public static String editorWidget(HintType type) { + switch (type) { + case BOOLEAN: return "Checkbox"; + case ENUM: return "Select"; + case TEXT_BLOCK: + case STRING_LIST: + case XML: return "TextArea"; + default: return "TextField"; + } + } + + /** + * One build hint. + * + *

Built fluently. Only {@link #name} is required; everything else + * defaults to "plain string, no default, not annotated", which is the + * correct shallow description of a hint nobody has curated yet.

+ */ + public static final class Hint { + private final String name; + private String aliasOf; + private String deprecated; + private HintGroup group = HintGroup.NONE; + private String attr; + private HintType type = HintType.STRING; + private String enumName; + private final List values = new ArrayList(); + private final List valueLabels = new ArrayList(); + private String def; + private String separator; + private String platform = "general"; + private boolean dynamic; + private String pattern; + private final List consumedBy = new ArrayList(); + private boolean external; + private boolean enterpriseOnly; + private String link; + private String doc = ""; + + Hint(String name) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException("Build hint name is required"); + } + this.name = name; + } + + /** Marks this hint as an override alias of another. */ + public Hint aliasOf(String other) { + this.aliasOf = other; + return this; + } + + /** Records that this hint is deprecated, naming what replaces it. */ + public Hint deprecated(String reason) { + this.deprecated = reason; + return this; + } + + /** Assigns the annotation type and the attribute name it is exposed as. */ + public Hint annotatedAs(HintGroup g, String attribute) { + this.group = g; + this.attr = attribute; + return this; + } + + /** Sets the group without exposing the hint as an annotation attribute. */ + public Hint group(HintGroup g) { + this.group = g; + return this; + } + + public Hint type(HintType t) { + this.type = t; + return this; + } + + /** + * Declares a closed value domain. Values are in wire form, i.e. + * exactly what the builder compares against, never the enum constant + * name. + */ + public Hint values(String enumTypeName, String... wireValues) { + this.type = HintType.ENUM; + this.enumName = enumTypeName; + this.values.clear(); + for (String v : wireValues) { + if (v.indexOf(',') >= 0) { + throw new IllegalArgumentException( + "Build hint " + name + " value '" + v + "' contains a comma, which the " + + "simulator's Build Hint editor uses to delimit its value list"); + } + this.values.add(v); + } + return this; + } + + /** Optional human labels for the value domain, parallel to the values. */ + public Hint valueLabels(String... labels) { + this.valueLabels.clear(); + Collections.addAll(this.valueLabels, labels); + return this; + } + + /** + * The builder's own default, i.e. the second argument of the + * {@code getArg} call that reads this hint. + */ + public Hint def(String value) { + this.def = value; + return this; + } + + /** + * The string that joins appended values. Empty string means the values + * abut directly, which is what the XML-fragment hints want. + */ + public Hint separator(String sep) { + this.separator = sep; + return this; + } + + public Hint platform(String p) { + this.platform = p; + return this; + } + + /** Declares an open-ended family of hints matching a name pattern. */ + public Hint dynamic(String namePattern) { + this.dynamic = true; + this.pattern = namePattern; + return this; + } + + /** Names the builders or mojos that read this hint. */ + public Hint consumedBy(String... classSimpleNames) { + Collections.addAll(this.consumedBy, classSimpleNames); + return this; + } + + /** + * Marks a hint that is read outside this repository, by a build-daemon + * lane whose source is not mirrored here. Such a hint has no in-repo + * consumer and that is not evidence it is dead. + */ + public Hint external() { + this.external = true; + return this; + } + + public Hint enterpriseOnly() { + this.enterpriseOnly = true; + return this; + } + + public Hint link(String url) { + this.link = url; + return this; + } + + /** One paragraph, reused verbatim by the docs, the javadoc and the UI. */ + public Hint doc(String text) { + this.doc = text == null ? "" : text; + return this; + } + + public String name() { return name; } + public String aliasOf() { return aliasOf; } + public String deprecated() { return deprecated; } + public HintGroup group() { return group; } + public String attr() { return attr; } + public HintType type() { return type; } + public String enumName() { return enumName; } + public List values() { return Collections.unmodifiableList(values); } + public List valueLabels() { return Collections.unmodifiableList(valueLabels); } + public String def() { return def; } + public String separator() { return separator; } + public String platform() { return platform; } + public boolean isDynamic() { return dynamic; } + public String pattern() { return pattern; } + public List consumedBy() { return Collections.unmodifiableList(consumedBy); } + public boolean isExternal() { return external; } + public boolean isEnterpriseOnly() { return enterpriseOnly; } + public String link() { return link; } + public String doc() { return doc; } + + /** Whether this hint is exposed as an annotation attribute. */ + public boolean isAnnotated() { + return attr != null && group.isAnnotated(); + } + + /** The full settings-file key, including the {@link #ARG_PREFIX}. */ + public String propertyKey() { + return ARG_PREFIX + name; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java new file mode 100644 index 00000000000..240be102a79 --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java @@ -0,0 +1,1445 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * Android build hints, including the {@code and.} override aliases. + * + *

Seeded by mining every {@code getArg} call site in the builders, so the + * name and the default match what the build actually reads. Curated entries + * carry an annotation attribute and, where the domain is provably closed, an + * enum; the rest are described but set through + * {@code codenameone_settings.properties}.

+ * + *

Split out of {@link BuildHints} because a single class initializer + * holding every entry would exceed the JVM's 64KB per-method limit.

+ */ +final class BuildHintsAndroid { + + private BuildHintsAndroid() { + } + + static void register(List h) { + h.add(new Hint("and.captureRecord") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("and.facebook_permissions") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder")); + + h.add(new Hint("and.themeMode") + .annotatedAs(HintGroup.ANDROID, "themeMode") + .values("AndroidThemeMode", "auto", "modern", "hololight", "legacy") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("`auto`, `modern` / `material`, `hololight` (default for existing apps), `legacy`. `auto` " + + "and `modern` / `material` opt in to the CSS-generated Android Material 3 theme from " + + "`native-themes/android-material/theme.css`. `hololight` is Android Holo Light (what the " + + "framework shipped on API 14+ before this refactor). `legacy` loads the pre-Holo Android " + + "theme. The legacy alias `cn1.androidTheme` is still accepted, and `and.hololight=true` " + + "still maps to `hololight`. The default stays on `hololight` for existing apps until you " + + "flip in a future release.")); + + h.add(new Hint("android.NotificationChannel.description") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("Remote notifications") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.NotificationChannel.enableLights") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.NotificationChannel.enableVibration") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.NotificationChannel.id") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("cn1-channel") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.NotificationChannel.importance") + .group(HintGroup.ANDROID) + .type(HintType.INT) + .def("2") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.NotificationChannel.lightColor") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.NotificationChannel.name") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("Notifications") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.NotificationChannel.vibrationPattern") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.accessibilityGuard") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.accessibilityGuard.allow") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.accessibilityGuard.mode") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("exit") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.activity.launchMode") + .annotatedAs(HintGroup.ANDROID, "activityLaunchMode") + .type(HintType.STRING) + .def("singleTop") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows explicitly setting the `android:launchMode` attribute of the main activity in " + + "android. Default is \"singleTop,\" but for some applications you may need to change this " + + "behaviour. In particular, apps that are meant to open a file type will need to set this " + + "to \"singleTask.\" See " + + "https://developer.android.com/guide/topics/manifest/activity-element.html[Android docs " + + "for the activity element] for more information about the `android:launchMode` attribute.")); + + h.add(new Hint("android.activityClassBody") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.activityClassImports") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.adaptiveIconBackground") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("#ffffff") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Background color to use for adaptive icons when `android.enableAdaptiveIcons=true` and " + + "no background image is supplied. Defaults to `#ffffff` and is written as " + + "`@color/ic_launcher_background`.")); + + h.add(new Hint("android.adaptiveIconBackgroundImage") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Optional path (relative to the root of the native Android project) to an image file to " + + "use as the adaptive icon background when `android.enableAdaptiveIcons=true`. If this " + + "property is set, it overrides `android.adaptiveIconBackground`.")); + + h.add(new Hint("android.allowBackup") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.androidAuto.messaging") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.androidAuto.minCarApiLevel") + .group(HintGroup.ANDROID) + .type(HintType.INT) + .def("1") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.androidAuto.navigation") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.androidAuto.poi") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.anyDensity") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.apacheLegacy") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.appBundle") + .annotatedAs(HintGroup.ANDROID, "appBundle") + .type(HintType.BOOLEAN) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Produces an Android App Bundle (.aab) rather than an APK. Required for new Play Store " + + "submissions.")); + + h.add(new Hint("android.appReview.version") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("2.0.1") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.ar.required") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.arrcompile") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.arrimplementation") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.asyncPaint") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to true. Toggles the Android pipeline between the legacy " + + "pipeline (false) and new pipeline (true)")); + + h.add(new Hint("android.background_push_handling") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.billingclient.version") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("4.0.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.blockExternalStoragePermission") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Disables the external storage (SD card) permission")); + + h.add(new Hint("android.blockReadMediaPermissions") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false, defaults to the value of `android.blockExternalStoragePermission`. " + + "Suppresses the `READ_MEDIA_VIDEO` and `READ_MEDIA_AUDIO` permissions that playing a URI " + + "adds on API 33 and above")); + + h.add(new Hint("android.bluetooth.neverForLocation") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.bluetooth.required") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.buildToolsVersion") + .annotatedAs(HintGroup.ANDROID, "buildToolsVersion") + .type(HintType.VERSION) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Android build-tools version. It also selects the compile SDK, so there is no separate " + + "compile-SDK hint.")); + + h.add(new Hint("android.captureRecord") + .annotatedAs(HintGroup.ANDROID, "captureRecord") + .type(HintType.STRING) + .def("enabled") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Indicates whether the `RECORD_AUDIO` permission should be requested. Can be `enabled` or " + + "any other value to disable this option")); + + h.add(new Hint("android.carAppVersion") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("1.4.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.credentialsPlayServicesVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.credentialsVersion") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("1.3.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.cusom_layout") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.cusom_layout1") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Applies to any number of layouts as long as they're in sequence (for example, " + + "android.cusom_layout2, android.cusom_layout3 etc.). Will write the content of the " + + "argument as a layout XML file and give it the name `cusom_layout1.xml` onwards. This can " + + "be used by native code to work with XML files")); + + h.add(new Hint("android.customActivity") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("CodenameOneActivity") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.customTabsVersion") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("1.8.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.debug") + .annotatedAs(HintGroup.ANDROID, "debug") + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("true/false defaults to true - indicates whether to include the debug version in the " + + "build. Defaults conditionally rather than to a fixed value: when android.release is on " + + "it defaults to false, and when release is off it defaults to true, so a build that " + + "selects neither still produces something installable " + + "(AndroidGradleBuilder.java:447-451).")); + + h.add(new Hint("android.decouplePlayServiceVersions") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.delayPushCompletion") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.disableR8") + .annotatedAs(HintGroup.ANDROID, "disableR8") + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Turns off R8, falling back to the older shrinker. Note that hardening requires R8, so " + + "this conflicts with harden.level.")); + + h.add(new Hint("android.disableR8FullMode") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.disableScreenshots") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.enableAdaptiveIcons") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder", "CN1BuildMojo") + .doc("Boolean true/false defaults to false. Enables Android adaptive icon generation in " + + "Android Gradle builds. When enabled, Codename One generates `mipmap` launcher resources " + + "(`ic_launcher`, `ic_launcher_foreground`, and adaptive XML in `mipmap-anydpi-v26`) and " + + "uses them in the application manifest (`android:icon` and `android:roundIcon`).")); + + h.add(new Hint("android.enableProguard") + .annotatedAs(HintGroup.ANDROID, "enableProguard") + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to true. Allows disabling the proguard obfuscation even on " + + "release builds, notice that this isn't recommended")); + + h.add(new Hint("android.excludeBolts") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.extendAppCompatActivity") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.facebookSdkVersion") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("16.2.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.facebook_permissions") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("\\\"public_profile\\\",\\\"email\\\",\\\"user_friends\\\"") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Permissions for Facebook used in the Android build target, applicable only if Facebook " + + "native integration is used.")); + + h.add(new Hint("android.file_paths") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def(" ") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.firebaseAnalytics") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.firebaseAnalyticsVersion") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("21.5.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.firebaseCoreVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.firebaseMessagingVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.foldableSupport") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.forceJava8Builder") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.foregroundServiceType") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("dataSync") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.fridaDetection") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Indicates whether the app should check for the " + + "presence of the https://www.frida.re/[Frida] dynamic instrumentation toolkit on the " + + "device. If Frida is detected, the app will exit. This uses the " + + "[frida-blocker](https://github.com/shannah/frida-blocker) library to perform the frida " + + "detection.")); + + h.add(new Hint("android.fullScreenIntent") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.googleAdUnitId") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows integrating admob/google play ads, this is effectively identical to " + + "google.adUnitId but only applies to Android")); + + h.add(new Hint("android.googleAdUnitTestDevice") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("C6783E2486F0931D9D09FABC65094FDF") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Device key used to mark a specific Android device as a test device for Google Play ads " + + "defaults to C6783E2486F0931D9D09FABC65094FDF")); + + h.add(new Hint("android.gpsPermission") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Indicates whether the GPS permission should be requested, it's autodetected by default " + + "if you use the location API. But, some code might want to explicitly define it")); + + h.add(new Hint("android.gradle.androidx") + .group(HintGroup.ANDROID) + .type(HintType.STRING_LIST) + .separator("\n") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.gradleDep") + .annotatedAs(HintGroup.ANDROID, "gradleDep") + .type(HintType.STRING_LIST) + .separator(";") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Gradle dependency statements to add to the app module, such as implementation " + + "'com.example:lib:1.0'.")); + + h.add(new Hint("android.gradlePlugin") + .group(HintGroup.ANDROID) + .type(HintType.STRING_LIST) + .separator("\n") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.hce") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.hceAids") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("F0010203040506") + .platform("android") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder")); + + h.add(new Hint("android.hceCategory") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("other") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.hceDescription") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.hceRequireUnlock") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.headphoneCallback") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. When set to true it assumes the main class has two " + + "methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately " + + "as needed")); + + h.add(new Hint("android.health.background") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.health.connectVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("1.1.0-alpha07") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.health.history") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.health.privacyPolicyUrl") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.health.read") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.health.write") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.hideOverlayWindows") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Declares the " + + "`android.permission.HIDE_OVERLAY_WINDOWS` permission needed by " + + "`DeviceIntegrity.setHideOverlayWindows()` on Android 12+, for apps that call the runtime " + + "API without enabling `android.tapjackingGuard`. A normal install-time permission, so the " + + "user sees no prompt.")); + + h.add(new Hint("android.hideStatusBar") + .annotatedAs(HintGroup.ANDROID, "hideStatusBar") + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Hides the Android status bar.")); + + h.add(new Hint("android.hms.pushVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("6.3.0.302") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.home.playServicesVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("16.0.0-beta1") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.includeGPlayServices") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("*Deprecated, please android.playService.+++*+++!* Indicates whether Google Play Services " + + "should be included into the build, defaults to false but that might change based on the " + + "functionality of the application and other build hints. Adding Google Play Services " + + "support allows you to use a more refined location implementation and invoke some Google " + + "specific functionality from native code.")); + + h.add(new Hint("android.includeMavenCentral") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.installLocation") + .annotatedAs(HintGroup.ANDROID, "installLocation") + .values("InstallLocation", "auto", "internalOnly", "preferExternal") + .def("auto") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Maps to android:installLocation manifest entry defaults to auto. Can also be set to " + + "internalOnly or preferExternal.")); + + h.add(new Hint("android.java8") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.keyboardOpen") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to true. Toggles the new async keyboard mode that leaves the " + + "keyboard open while you move between text components")); + + h.add(new Hint("android.largeScreens") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.licenseKey") + .annotatedAs(HintGroup.ANDROID, "licenseKey") + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("The license key for the Android app, this is required if you use in-app purchase on " + + "Android")); + + h.add(new Hint("android.locales") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.manifest.queries") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Embeds XML content into the section of the Android manifest file. This is " + + "https://developer.android.com/training/package-visibility[required in Android 11 for " + + "package visibility]. See " + + "https://developer.android.com/guide/topics/manifest/queries-element[queries element " + + "Android documentation].")); + + h.add(new Hint("android.messagingService") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.migrateToAndroidX") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.min_sdk_version") + .annotatedAs(HintGroup.ANDROID, "minSdkVersion") + .type(HintType.INT) + .def("19") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("The least SDK required to run this app, the default value changes based on functionality " + + "but can be as low as 7. This corresponds to the XML attribute `android:minSdkVersion`.")); + + h.add(new Hint("android.mockLocation") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to true. Toggles the mock location permission which is on by " + + "default, this allows easier debugging of Android device location based services")); + + h.add(new Hint("android.mopubId") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.multidex") + .annotatedAs(HintGroup.ANDROID, "multidex") + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Multidex allows Android binaries to reference more " + + "than 65536 methods. This slows builds a bit so you have it off by default but if you get " + + "a build error mentioning this limit you should turn this on.")); + + h.add(new Hint("android.newFirebaseMessaging") + .annotatedAs(HintGroup.ANDROID, "newFirebaseMessaging") + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Uses the current Firebase Cloud Messaging integration. Requires AndroidX and Gradle 8.13 " + + "or newer.")); + + h.add(new Hint("android.nonconsumable") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Comma delimited string of items that are non-consumable in the in-app purchase API")); + + h.add(new Hint("android.normalScreens") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.onCreate") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playIntegrity") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playIntegrity.verifyUrl") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playIntegrityVersion") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("1.4.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.ads") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.analytics") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.appInvite") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.auth") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.base") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.cast") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.drive") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.fitness") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.games") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.gcm") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.identity") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.indexing") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.location") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.maps") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.nearby") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.panorama") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.plus") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.safetynet") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.vision") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.wallet") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playService.wearable") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.playServicesVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("The version number of play services to build against. Experimental. **Use with caution** " + + "as building against versions other than the server default may introduce " + + "incompatibilities with some Codename One APIs.")); + + h.add(new Hint("android.proguardKeep") + .annotatedAs(HintGroup.ANDROID, "proguardKeep") + .type(HintType.STRING_LIST) + .separator("\n") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Arguments for the keep option in proguard allowing you to keep a pattern of files for " + + "example, `-keep class com.mypackage.ProblemClass { *; }`")); + + h.add(new Hint("android.proguardKeepOverride") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("Exceptions, InnerClasses, Signature, Deprecated, SourceFile, LineNumberTable, *Annotation*, EnclosingMethod") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.pushSound") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.pushVibratePattern") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Comma delimited long values to describe the push pattern of vibrate used for the " + + "`setVibrate` native method")); + + h.add(new Hint("android.release") + .annotatedAs(HintGroup.ANDROID, "release") + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("true/false defaults to true - indicates whether to include the release version in the " + + "build")); + + h.add(new Hint("android.removeBasePermissions") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Disables the built-in permissions specifically " + + "`INTERNET` permission (that is, no networking...)")); + + h.add(new Hint("android.repositories") + .annotatedAs(HintGroup.ANDROID, "repositories") + .type(HintType.STRING_LIST) + .separator("\n") + .platform("android") + .consumedBy("AndroidGradleBuilder", "MapsProviderInjector") + .doc("Extra Gradle repositories to resolve dependencies from.")); + + h.add(new Hint("android.requestReadMediaPermissions") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Declares `READ_MEDIA_IMAGES`, `READ_MEDIA_VIDEO` " + + "and `READ_MEDIA_AUDIO` on API 33 and above even when the build detected no media " + + "playback. `READ_MEDIA_IMAGES` is only ever added by this hint")); + + h.add(new Hint("android.rootCheck") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Indicates whether the app should check for root " + + "access on the device. If root access is detected, the app will exit.")); + + h.add(new Hint("android.rootbeerVersion") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("0.1.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.shareFilter") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.sharedUserId") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows adding a manifest attribute for the sharedUserId option")); + + h.add(new Hint("android.sharedUserLabel") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows adding a manifest attribute for the sharedUserLabel option")); + + h.add(new Hint("android.shrinkResources") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Used only in conjunction with " + + "android.enableProguard. Strips out unused resources to reduce apk size. Since 7.0")); + + h.add(new Hint("android.smallScreens") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to true. Corresponds to the `android:smallScreens` XML " + + "attribute and allows disabling the support for small phones")); + + h.add(new Hint("android.stack_size") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Size in bytes for the Android stack thread")); + + h.add(new Hint("android.statusbar_hidden") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("true/false defaults to false. When set to true hides the status bar on Android devices.")); + + h.add(new Hint("android.store_ids") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.streamMode") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("The mode in which the volume key should behave, defaults to OS default. Allows setting " + + "it to `music` for music playback apps")); + + h.add(new Hint("android.stringsXml") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows injecting more entries into the strings.xml file using a value that includes " + + "something like this `value1value2`")); + + h.add(new Hint("android.style") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows injecting more data into the `styles.xml` file right before the closing resources " + + "tag")); + + h.add(new Hint("android.supportScreens") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.supportv4Dep") + .group(HintGroup.ANDROID) + .type(HintType.STRING_LIST) + .separator("\n") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.surfaces.exactAlarms") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.tapjackingGuard") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Switches on tapjacking / screen-overlay protection " + + "at launch, so touches that arrive while another app's window covers this one are " + + "detected and dropped. See the security chapter.")); + + h.add(new Hint("android.tapjackingGuard.hideOverlays") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to true. Also asks Android 12+ to hide overlay windows drawn " + + "over the app, which is the only mitigation that covers native peer components, and " + + "declares the `HIDE_OVERLAY_WINDOWS` permission it requires. Only relevant if " + + "`android.tapjackingGuard=true`.")); + + h.add(new Hint("android.tapjackingGuard.mode") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("block") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("`block` (default), `strict`, `report` or `off`. `block` drops gestures that start on a " + + "fully obscured window, `report` only observes, `strict` also drops touches where only " + + "part of the window is covered (which benign system UI can trigger). Only relevant if " + + "`android.tapjackingGuard=true`.")); + + h.add(new Hint("android.targetSDKVersion") + .annotatedAs(HintGroup.ANDROID, "targetSDKVersion") + .type(HintType.INT) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Indicates the Android SDK used to compile the Android build defaults to 21. Notice that " + + "not all targets will work since the source might have some limitations and not all SDK " + + "targets are installed on the build servers.")); + + h.add(new Hint("android.textureView") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.theme") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("Light") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Light or Dark defaults to Light. On Android 4+ the default Holo theme is used to render " + + "the native widgets sometimes and this indicates whether holo light or holo dark is used. " + + "This doesn't affect the Codename One theme but that might change in the future.")); + + h.add(new Hint("android.topDependency") + .annotatedAs(HintGroup.ANDROID, "topDependency") + .type(HintType.STRING_LIST) + .separator("\n") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Statements added to the top-level Gradle build file rather than the app module.")); + + h.add(new Hint("android.tv") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("true/false (defaults to false). Marks the build as an Android TV / Google TV app. Adds " + + "the `LEANBACK_LAUNCHER` intent category to the launcher activity (so the app appears on " + + "the TV home screen), declares the `android.software.leanback` feature, makes " + + "`android.hardware.touchscreen` optional (so it installs on touchless TVs), and generates " + + "a 320×180 launcher banner (`@drawable/tv_banner`) from the app icon. The same APK still " + + "installs and runs on phones and tablets, and `CN.isTV()` returns true at runtime on a " + + "TV.")); + + h.add(new Hint("android.useAndroidX") + .annotatedAs(HintGroup.ANDROID, "useAndroidX") + .type(HintType.BOOLEAN) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Use Android X instead of support libraries. This will also run a find/replace on all " + + "source files to replace support libraries and artifacts with AndroidX equivalents.")); + + h.add(new Hint("android.useGradle8") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.versionCode") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows overriding the auto generated version number with a custom internal version " + + "number specifically used for the XML attribute `android:versionCode`")); + + h.add(new Hint("android.wear") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.wear.standalone") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.web_loading_hidden") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("true/false defaults to false - set to true to hide the progress indicator that appears " + + "when loading a web page on Android.")); + + h.add(new Hint("android.windowVersion") + .group(HintGroup.ANDROID) + .type(HintType.VERSION) + .def("1.3.0") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.xactivity") + .group(HintGroup.ANDROID) + .type(HintType.XML) + .separator("") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows injecting more attributes into the `activity` tag in the Android XML")); + + h.add(new Hint("android.xapplication") + .annotatedAs(HintGroup.ANDROID, "xapplication") + .type(HintType.XML) + .separator("") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("defaults to an empty string. Allows developers of native Android code to add text within " + + "the application block to define things such as widgets, services etc.")); + + h.add(new Hint("android.xapplication_attr") + .group(HintGroup.ANDROID) + .type(HintType.XML) + .separator(" ") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows injecting more attributes into the `application`` tag in the Android XML")); + + h.add(new Hint("android.xgradle") + .annotatedAs(HintGroup.ANDROID, "xgradle") + .type(HintType.STRING_LIST) + .separator("\n") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Arbitrary text spliced into the generated app-module Gradle file.")); + + h.add(new Hint("android.xgradle_default_config") + .group(HintGroup.ANDROID) + .type(HintType.STRING_LIST) + .separator("\n") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.xintent_filter") + .group(HintGroup.ANDROID) + .type(HintType.XML) + .separator("") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Allows adding an intent filter to the main android activity")); + + h.add(new Hint("android.xlargeScreens") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.xlayout_attr") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.xmanifest") + .group(HintGroup.ANDROID) + .type(HintType.XML) + .separator("") + .platform("android") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.xpermissions") + .annotatedAs(HintGroup.ANDROID, "xpermissions") + .type(HintType.XML) + .separator("") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("more permissions for the Android manifest")); + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java new file mode 100644 index 00000000000..d606f485d0a --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * macOS Catalyst, tvOS and watchOS native-slice build hints. + * + *

Seeded by mining every {@code getArg} call site in the builders, so the + * name and the default match what the build actually reads. Curated entries + * carry an annotation attribute and, where the domain is provably closed, an + * enum; the rest are described but set through + * {@code codenameone_settings.properties}.

+ * + *

Split out of {@link BuildHints} because a single class initializer + * holding every entry would exceed the JVM's 64KB per-method limit.

+ */ +final class BuildHintsApple { + + private BuildHintsApple() { + } + + static void register(List h) { + h.add(new Hint("macNative.appCategory") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .def("public.app-category.utilities") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. `LSApplicationCategoryType` in the generated Info.plist. Default " + + "`public.app-category.utilities`. See " + + "https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype[Apple's " + + "category list].")); + + h.add(new Hint("macNative.bundleId") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. Used only when `macNative.deriveBundleId=false`. Default: " + + "`.mac`.")); + + h.add(new Hint("macNative.copyright") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. `NSHumanReadableCopyright` in the Info.plist. Defaults to " + + "`Copyright (c) `.")); + + h.add(new Hint("macNative.deriveBundleId") + .group(HintGroup.MAC_NATIVE) + .type(HintType.BOOLEAN) + .def("true") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. `true` (default) maps to Xcode's " + + "`DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER=YES` (Xcode appends `.maccatalyst` to the " + + "iOS bundle ID). Set to `false` to take the bundle ID verbatim from `macNative.bundleId`.")); + + h.add(new Hint("macNative.distribution") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .def("appStore") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. `appStore` (default), `developerID`, or `both`. Selects which " + + "entitlements + ExportOptions plist + signing certificate to emit. `both` emits parallel " + + "`*-AppStore.entitlements` / `*-DeveloperID.entitlements` and matching " + + "`ExportOptions-*-Mac.plist` files so a single project can be archived to either channel.")); + + h.add(new Hint("macNative.enabled") + .group(HintGroup.MAC_NATIVE) + .type(HintType.BOOLEAN) + .def("false") + .platform("mac") + .consumedBy("CN1BuildMojo", "IPhoneBuilder", "MacNativeBuilder")); + + h.add(new Hint("macNative.entitlements.extra") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. Free-form XML inserted verbatim inside the `…` of " + + "the generated entitlements plist. Use for entitlements Codename One doesn't expose " + + "individually.")); + + h.add(new Hint("macNative.entitlements.files.userSelected") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .def("readwrite") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. `readwrite` (default), `readonly`, or `none`. Sets the matching " + + "`com.apple.security.files.user-selected.*` entitlement.")); + + h.add(new Hint("macNative.fixedWindowSize") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. Opt-in. Format `x` — for example `1024x685`. When " + + "set, the Catalyst window's `UISceneSession.sizeRestrictions` minimum and maximum are " + + "pinned to the requested size so every launch produces a byte-identical window. Default " + + "unset, in which case the window is resizable. The CI screenshot pipeline turns this on " + + "to keep the strict-pixel golden comparison stable; production apps should leave it off.")); + + h.add(new Hint("macNative.iosMinDeploymentTarget") + .group(HintGroup.MAC_NATIVE) + .type(HintType.VERSION) + .def("13.1") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. iOS deployment-target floor for the Catalyst slice " + + "(`IPHONEOS_DEPLOYMENT_TARGET`). Default `13.1`. The plugin coerces the iOS slice's " + + "minimum upward when set.")); + + h.add(new Hint("macNative.minDeploymentTarget") + .group(HintGroup.MAC_NATIVE) + .type(HintType.VERSION) + .def("10.15") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. Minimum macOS version (`MACOSX_DEPLOYMENT_TARGET`). Default " + + "`10.15` — earlier versions don't support Mac Catalyst.")); + + h.add(new Hint("macNative.notarize") + .group(HintGroup.MAC_NATIVE) + .type(HintType.BOOLEAN) + .def("false") + .platform("mac") + .consumedBy("MacNativeBuilder")); + + h.add(new Hint("macNative.notarize.appleId") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder")); + + h.add(new Hint("macNative.notarize.keychainProfile") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder")); + + h.add(new Hint("macNative.notarize.password") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder")); + + h.add(new Hint("macNative.notarize.teamId") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder")); + + h.add(new Hint("macNative.signing.style") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .def("automatic") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. `automatic` (default) lets Xcode pick the signing certificate; " + + "`manual` forces the certificate identity hints below to be respected verbatim.")); + + h.add(new Hint("macNative.signingIdentity.appStore") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .def("Apple Distribution") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. Signing certificate identity for the App Store channel. Default " + + "`Apple Distribution`.")); + + h.add(new Hint("macNative.signingIdentity.developerID") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .def("Developer ID Application") + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. Signing certificate identity for the Developer ID channel. " + + "Default `Developer ID Application`.")); + + h.add(new Hint("macNative.teamId") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Mac Native builds only. Apple Developer Team ID (alphanumeric). Falls back to " + + "`ios.release.teamId` → `ios.teamId` → `ios.debug.teamId` since most apps share a single " + + "Apple Developer Team for iOS and Mac.")); + + h.add(new Hint("tvNative.bundleId") + .group(HintGroup.TV_NATIVE) + .type(HintType.STRING) + .platform("tv") + .consumedBy("TvNativeBuilder") + .doc("Bundle identifier of the tvOS app. Defaults to `.tvos`.")); + + h.add(new Hint("tvNative.displayName") + .group(HintGroup.TV_NATIVE) + .type(HintType.STRING) + .platform("tv") + .consumedBy("TvNativeBuilder") + .doc("The tvOS app name shown on Apple TV. Defaults to the app's display name.")); + + h.add(new Hint("tvNative.enabled") + .group(HintGroup.TV_NATIVE) + .type(HintType.BOOLEAN) + .def("false") + .platform("tv") + .consumedBy("IPhoneBuilder", "TvNativeBuilder") + .doc("true/false (defaults to false). Adds an Apple TV (tvOS) application target to the iOS " + + "build. The tvOS app is a separate `appletvos` target built from the same Java/Kotlin " + + "sources through ParparVM (UIKit + Metal; tvOS has no OpenGL ES). Enabling it doesn't " + + "change the iOS app -- in particular it doesn't override the iOS app's `ios.metal` " + + "setting. Also turned on implicitly by `codename1.tvMain`.")); + + h.add(new Hint("tvNative.mainClass") + .group(HintGroup.TV_NATIVE) + .type(HintType.STRING) + .platform("tv") + .consumedBy("IPhoneBuilder", "TvNativeBuilder")); + + h.add(new Hint("tvNative.minDeploymentTarget") + .group(HintGroup.TV_NATIVE) + .type(HintType.VERSION) + .def("13.0") + .platform("tv") + .consumedBy("TvNativeBuilder") + .doc("`TVOS_DEPLOYMENT_TARGET` for the tvOS target. Defaults to `13.0`.")); + + h.add(new Hint("tvNative.teamId") + .group(HintGroup.TV_NATIVE) + .type(HintType.STRING) + .platform("tv") + .consumedBy("TvNativeBuilder") + .doc("Apple Developer Team ID used to sign the tvOS target. Falls back to the iOS team id " + + "(`ios.release.teamId` / `ios.teamId` / `ios.debug.teamId`).")); + + h.add(new Hint("watchNative.enabled") + .group(HintGroup.WATCH_NATIVE) + .type(HintType.BOOLEAN) + .def("false") + .platform("watch") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("watchNative.health") + .group(HintGroup.WATCH_NATIVE) + .type(HintType.STRING) + .platform("watch") + .consumedBy("WatchNativeBuilder")); + + h.add(new Hint("watchNative.health.workoutProcessing") + .group(HintGroup.WATCH_NATIVE) + .type(HintType.BOOLEAN) + .def("false") + .platform("watch") + .consumedBy("WatchNativeBuilder")); + + h.add(new Hint("watchNative.mainClass") + .group(HintGroup.WATCH_NATIVE) + .type(HintType.STRING) + .platform("watch") + .consumedBy("IPhoneBuilder")); + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java new file mode 100644 index 00000000000..aee3bf9532d --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * Desktop, native Windows, native Linux and JavaScript build hints. + * + *

Seeded by mining every {@code getArg} call site in the builders, so the + * name and the default match what the build actually reads. Curated entries + * carry an annotation attribute and, where the domain is provably closed, an + * enum; the rest are described but set through + * {@code codenameone_settings.properties}.

+ * + *

Split out of {@link BuildHints} because a single class initializer + * holding every entry would exceed the JVM's 64KB per-method limit.

+ */ +final class BuildHintsDesktop { + + private BuildHintsDesktop() { + } + + static void register(List h) { + h.add(new Hint("desktop.adaptToRetina") + .annotatedAs(HintGroup.DESKTOP, "adaptToRetina") + .type(HintType.BOOLEAN) + .def("true") + .platform("desktop") + .consumedBy("GenerateDesktopAppWrapperMojo") + .doc("Boolean true/false defaults to true. When set to true some values will ve implicitly " + + "doubled to deal with retina displays and icons etc. Will use higher DPI's")); + + h.add(new Hint("desktop.fullscreen") + .annotatedAs(HintGroup.DESKTOP, "fullscreen") + .type(HintType.BOOLEAN) + .def("false") + .platform("desktop") + .consumedBy("GenerateDesktopAppWrapperMojo") + .doc("Starts the desktop build in full-screen mode.")); + + h.add(new Hint("desktop.height") + .annotatedAs(HintGroup.DESKTOP, "height") + .type(HintType.INT) + .def("600") + .platform("desktop") + .consumedBy("GenerateDesktopAppWrapperMojo") + .doc("Height in pixels for the form in desktop builds, will be doubled for retina grade " + + "displays. Defaults to 600.")); + + h.add(new Hint("desktop.interactiveScrollbars") + .annotatedAs(HintGroup.DESKTOP, "interactiveScrollbars") + .type(HintType.BOOLEAN) + .def("true") + .platform("desktop") + .consumedBy("GenerateDesktopAppWrapperMojo") + .doc("Enables grab-able, click-to-page desktop scrollbars.")); + + h.add(new Hint("desktop.resizable") + .annotatedAs(HintGroup.DESKTOP, "resizable") + .type(HintType.BOOLEAN) + .def("true") + .platform("desktop") + .consumedBy("GenerateDesktopAppWrapperMojo") + .doc("Boolean true/false defaults to true. Indicates whether the UI in the desktop build is " + + "resizable")); + + h.add(new Hint("desktop.title") + .group(HintGroup.DESKTOP) + .type(HintType.STRING) + .platform("desktop") + .consumedBy("GenerateDesktopAppWrapperMojo")); + + h.add(new Hint("desktop.titleBar") + .annotatedAs(HintGroup.DESKTOP, "titleBar") + .values("DesktopTitleBar", "native", "custom", "toolbar") + .def("native") + .platform("desktop") + .consumedBy("GenerateDesktopAppWrapperMojo") + .doc("How the desktop window is framed: native for the OS title bar and menu bar, custom for " + + "an undecorated window with a Codename One drawn title bar, or toolbar for the legacy " + + "in-app Toolbar. An unrecognized value falls back to native with a warning.")); + + h.add(new Hint("desktop.width") + .annotatedAs(HintGroup.DESKTOP, "width") + .type(HintType.INT) + .def("800") + .platform("desktop") + .consumedBy("GenerateDesktopAppWrapperMojo") + .doc("Width in pixels for the form in desktop builds, will be doubled for retina grade " + + "displays. Defaults to 800.")); + + h.add(new Hint("javascript.includeVideoJS") + .group(HintGroup.JAVASCRIPT) + .type(HintType.BOOLEAN) + .def("false") + .platform("javascript") + .consumedBy("JavaScriptBuilder")); + + h.add(new Hint("javascript.inject_proxy") + .group(HintGroup.JAVASCRIPT) + .type(HintType.BOOLEAN) + .def("true") + .platform("javascript") + .consumedBy("JavaScriptProxyPackager") + .doc("true/false (defaults to `true`). The ParparVM builder generates a same-origin proxy " + + "bundle and configures the app to use it. Setting this to `false` disables both proxy " + + "generation and proxy URL injection.")); + + h.add(new Hint("javascript.portSources") + .group(HintGroup.JAVASCRIPT) + .type(HintType.STRING) + .platform("javascript") + .consumedBy("JavaScriptBuilder")); + + h.add(new Hint("javascript.proxy.allowedTargets") + .group(HintGroup.JAVASCRIPT) + .type(HintType.STRING) + .platform("javascript") + .consumedBy("JavaScriptProxyPackager") + .doc("Comma-separated target origins, host names, or wildcard subdomains that a generated " + + "proxy may access, for example `https://api.example.com,*.services.example.org`. If " + + "omitted, the proxy accepts any HTTP or HTTPS target and the build emits a warning.")); + + h.add(new Hint("javascript.proxy.target") + .group(HintGroup.JAVASCRIPT) + .type(HintType.STRING) + .def("jakarta-servlet") + .platform("javascript") + .consumedBy("CN1BuildMojo", "JavaScriptProxyPackager") + .doc("The generated ParparVM proxy deployment platform. Supported values are `jakarta-servlet` " + + "(default), `javax-servlet`, `node`, `php`, `aws-lambda`, `google-cloud-functions`, " + + "`cloudflare-workers`, and `none`.")); + + h.add(new Hint("javascript.proxy.url") + .group(HintGroup.JAVASCRIPT) + .type(HintType.STRING) + .platform("javascript") + .consumedBy("JavaScriptProxyPackager") + .doc("The URL of an existing proxy to use for network requests. Setting it suppresses " + + "generated proxy packaging unless `javascript.proxy.target` is also set. If " + + "`javascript.inject_proxy` is `false`, this build hint is ignored.")); + + h.add(new Hint("linux.arch") + .group(HintGroup.LINUX) + .type(HintType.STRING) + .platform("linux") + .consumedBy("LinuxNativeBuilder")); + + h.add(new Hint("linux.cc") + .group(HintGroup.LINUX) + .type(HintType.STRING) + .platform("linux") + .consumedBy("LinuxNativeBuilder")); + + h.add(new Hint("linux.debug") + .group(HintGroup.LINUX) + .type(HintType.BOOLEAN) + .def("false") + .platform("linux") + .consumedBy("LinuxNativeBuilder")); + + h.add(new Hint("linux.libc") + .group(HintGroup.LINUX) + .type(HintType.STRING) + .def("glibc") + .platform("linux") + .consumedBy("LinuxNativeBuilder")); + + h.add(new Hint("linux.musl") + .group(HintGroup.LINUX) + .type(HintType.BOOLEAN) + .def("false") + .platform("linux") + .consumedBy("LinuxNativeBuilder")); + + h.add(new Hint("linux.muslNativeCc") + .group(HintGroup.LINUX) + .type(HintType.BOOLEAN) + .def("false") + .platform("linux") + .consumedBy("LinuxNativeBuilder")); + + h.add(new Hint("linux.toolchain") + .group(HintGroup.LINUX) + .type(HintType.STRING) + .platform("linux") + .consumedBy("LinuxNativeBuilder")); + + h.add(new Hint("windows.arch") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder") + .doc("Native Windows port only (the `windows-native` build target -- not the JVM `win.*` " + + "desktop hints above). Target CPU architecture for the standalone `.exe`: `x64` (the " + + "default) or `arm64`. Accepts the usual synonyms (`x86_64`/`amd64`, `aarch64`). clang-cl " + + "cross-compiles to the chosen architecture from either host. See the " + + "link:#_working_with_the_native_windows_port[Working with the native Windows port " + + "chapter].")); + + h.add(new Hint("windows.calendar.restrictedCapability") + .group(HintGroup.WINDOWS) + .type(HintType.BOOLEAN) + .def("false") + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.debug") + .group(HintGroup.WINDOWS) + .type(HintType.BOOLEAN) + .def("false") + .platform("windows") + .consumedBy("WindowsNativeBuilder") + .doc("Native Windows port only. true/false (defaults to false). When `false` the `.exe` is " + + "built optimized and *stripped* -- no PDB, dead-stripped unreferenced code (`/OPT:REF`) " + + "and folded identical functions (`/OPT:ICF`) -- which is the shipping default. Set `true` " + + "to keep debug symbols (a `.pdb` next to the exe, via `RelWithDebInfo` / clang-cl `/Zi` + " + + "linker `/DEBUG`) so a native crash address can be symbolized during development. " + + "Optimizations stay on in both cases.")); + + h.add(new Hint("windows.msix") + .group(HintGroup.WINDOWS) + .type(HintType.BOOLEAN) + .def("false") + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.msix.identityName") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.msix.password") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.msix.pfx") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.msix.publisher") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.msix.version") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.sdkRoot") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder") + .doc("Native Windows port only; used when building on a *non-Windows* host (for example a " + + "Linux build server). Path to a Windows SDK laid out by " + + "https://github.com/Jake-Shadle/xwin[`xwin splat`] (a directory containing `crt/include` " + + "and `sdk/include/um`), used to cross-compile the `.exe` with clang-cl + lld-link instead " + + "of a Visual Studio environment. If unset, the `CN1_XWIN_SYSROOT` environment variable is " + + "used. Ignored on Windows hosts, which build through Visual Studio. The same SDK serves " + + "both `windows.arch` targets (its `x86_64` / `aarch64` lib subdirs).")); + + h.add(new Hint("windows.signing") + .group(HintGroup.WINDOWS) + .type(HintType.BOOLEAN) + .def("true") + .platform("windows") + .consumedBy("WindowsNativeBuilder") + .doc("Native Windows port only. `true`/`false` (default `true`). Set `false` to force an " + + "unsigned build even when a certificate is available.")); + + h.add(new Hint("windows.signing.digest") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .def("sha256") + .platform("windows") + .consumedBy("WindowsNativeBuilder") + .doc("Native Windows port only. Signature digest algorithm. Default `sha256`.")); + + h.add(new Hint("windows.signing.name") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.signing.password") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.signing.pkcs12") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + + h.add(new Hint("windows.signing.timestampUrl") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .def("http://timestamp.digicert.com") + .platform("windows") + .consumedBy("WindowsNativeBuilder") + .doc("Native Windows port only. RFC 3161 timestamp server used when signing, so the " + + "signature stays valid after the certificate expires. Default " + + "`http://timestamp.digicert.com`; set empty to disable timestamping.")); + + h.add(new Hint("windows.signing.url") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder")); + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDynamic.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDynamic.java new file mode 100644 index 00000000000..f82955cf1a4 --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDynamic.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * Open-ended hint families whose names are built by concatenation, so the set + * of valid keys is unbounded. + * + *

These are deliberately not exposed as annotation attributes. Each + * one is really a map — permission name to setting, entitlement key to + * value — and a Java annotation member cannot express a + * {@code Map}. The shapes that could work (a nested + * {@code @Permission[]}, or a {@code String[]} of {@code "KEY=VALUE"} pairs) + * are a materially different design; until one is chosen these are set through + * {@code codenameone_settings.properties}.

+ * + *

They are catalogued anyway because the drift gate needs them: a mined key + * that matches one of these patterns is accounted for rather than reported as + * an unknown hint.

+ */ +final class BuildHintsDynamic { + + private BuildHintsDynamic() { + } + + static void register(List h) { + family(h, "android.permission.*", "android", "AndroidGradleBuilder", + "true/false. Whether to include a particular permission. Preferred over " + + "android.xpermissions because it avoids conflicts with libraries. See " + + "Android's Manifest.permission documentation for the full list. The " + + "optional .maxSdkVersion suffix becomes the maxSdkVersion attribute of " + + "the generated tag, and .required marks the " + + "permission required."); + family(h, "android.uses_feature.*", "android", "AndroidGradleBuilder", + "Adds a element named by the suffix."); + family(h, "android.uses_permission.*", "android", "AndroidGradleBuilder", + "Adds a element named by the suffix."); + family(h, "android.playService.*", "android", "AndroidGradleBuilder", + "Opts a single Google Play service in or out. The sibling " + + ".minPlayServicesVersion pins its version."); + family(h, "android.cusom_layout*", "android", "AndroidGradleBuilder", + "Numbered custom layout resources: android.cusom_layout1, 2, and so on. " + + "The misspelling is load-bearing -- it is the key the builder " + + "actually reads, so correcting it would silently drop the layout."); + family(h, "ios.NS*UsageDescription", "ios", "IPhoneBuilder", + "Info.plist privacy strings. The commonly used keys are catalogued " + + "individually and exposed through @IosPrivacy; this entry covers the " + + "open tail that the builder sweeps by prefix."); + family(h, "ios.entitlements.*", "ios", "IPhoneBuilder", + "Adds an arbitrary entitlement key to the generated entitlements file."); + family(h, "ios.spm.products.*", "ios", "IPhoneBuilder", + "Selects which products of a Swift Package Manager package to link, keyed " + + "by package identity."); + family(h, "ios.pods.build.*", "ios", "IPhoneBuilder", + "Overrides an Xcode build setting for the generated CocoaPods project."); + family(h, "ios.home.commissioning.buildSettings.*", "ios", "IPhoneBuilder", + "Overrides an Xcode build setting for the Matter commissioning extension."); + family(h, "ios.surfaces.buildSettings.*", "ios", "IPhoneBuilder", + "Overrides an Xcode build setting for the external-surfaces extension."); + family(h, "ios.*.appext.*", "ios", "IPhoneBuilder", + "Per-app-extension signing. ios.debug.appext..* and " + + "ios.release.appext..* are collapsed to unqualified keys before " + + "the request is sent."); + family(h, "harden.*.enabled", "general", "Executor", + "Enables or disables hardening for one platform slice."); + family(h, "harden.*", "general", "Executor", + "The whole hardening namespace is swept into the hardening engine's " + + "configuration, so a hint added there reaches it without a dedicated " + + "reader."); + family(h, "macNative.provisioningProfile.*", "mac", "MacNativeBuilder", + "Per-profile provisioning data for a native macOS build, keyed by profile " + + "name."); + family(h, "var.*", "general", "BuildRequest", + "Defines a variable that any other hint can interpolate as ${var.name}, " + + "with ${var.name:default} for a fallback."); + } + + private static void family(List h, String pattern, String platform, + String consumer, String doc) { + h.add(new Hint(pattern) + .group(HintGroup.NONE) + .type(HintType.STRING) + .dynamic(pattern) + .platform(platform) + .consumedBy(consumer) + .doc(doc)); + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsExternal.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsExternal.java new file mode 100644 index 00000000000..30c02b89a6e --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsExternal.java @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * Hints the developer guide documents that nothing in this repository reads. + * + *

Most are consumed by build-daemon lanes whose source is not mirrored here, + * so having no in-repo consumer is not evidence that a hint is dead. A few are + * probably genuinely obsolete. Recording the distinction as + * {@link Hint#isExternal()} keeps both the drift gate and the Settings tool + * honest: the gate does not demand a consumer for these, and the tool still + * offers them for editing.

+ * + *

They are deliberately not annotated. Exposing a hint as a typed attribute + * is a promise that setting it does something, and for these that promise + * cannot be checked from this repository.

+ */ +final class BuildHintsExternal { + + private BuildHintsExternal() { + } + + static void register(List h) { + h.add(new Hint("android.fridaDebugLogging") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .platform("android") + .external() + .doc("Boolean true/false defaults to false. If true, it will add verbose debug logs during " + + "frida detection to show which check if fails on.")); + + h.add(new Hint("android.fridaVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .external() + .doc("x.y.z The version of [frida-blocker](https://github.com/shannah/frida-blocker) to use to " + + "perform frida detection. This is only relevant if `android.fridaDetection=true`. If " + + "omitted, it will use the latest tested version in the build server.")); + + h.add(new Hint("android.signingV1") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .platform("android") + .external() + .doc("true/false Default true. See " + + "https://source.android.com/docs/security/features/apksigning")); + + h.add(new Hint("android.signingV2") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .platform("android") + .external() + .doc("true/false Default true. See " + + "https://source.android.com/docs/security/features/apksigning")); + + h.add(new Hint("android.signingV3") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .platform("android") + .external() + .doc("true/false Default true. See " + + "https://source.android.com/docs/security/features/apksigning")); + + h.add(new Hint("android.signingV4") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .platform("android") + .external() + .doc("true/false Default true. See " + + "https://source.android.com/docs/security/features/apksigning")); + + h.add(new Hint("android.supportV4") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .platform("android") + .external() + .doc("Boolean true/false defaults to false but that can change based on usage (for example, " + + "push implicitly activates this). Indicates whether the android support v4 library should " + + "be included in the build")); + + h.add(new Hint("block_server_registration") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .platform("general") + .external() + .doc("true/false flag defaults to false. By default Codename One applications register with " + + "the Codename One server. Setting this to true blocks them from sending information to " + + "the Codename One cloud, which is kept for statistical purposes and may be used to " + + "provide more installation stats in the future.")); + + h.add(new Hint("build.cn1Version") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .external() + .doc("Pro/Enterprise only. Pins the cloud build to a specific released Codename One version " + + "using the Maven release scheme (for example `7.0.182`), or to `master` to build against " + + "the current development head. The build server fetches that version's framework " + + "artifacts. Pro accounts can target versions published within the last two months; " + + "Enterprise within the last six months. Requesting an older version, a version that was " + + "never published, or using this hint without a Pro/Enterprise subscription fails the " + + "build with an explanatory error. See Versioned builds.")); + + h.add(new Hint("codename1.mac.appid") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .external() + .doc("Mac Native cloud builds only. The Mac bundle identifier registered in App Store Connect " + + "/ Apple Developer. Distinct from `codename1.ios.appid` because Apple treats the iOS and " + + "Mac App Store records as separate products. Required for cloud Mac builds.")); + + h.add(new Hint("codename1.mac.certificate") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .external() + .doc("Mac Native cloud builds only. Path to the `.p12` file containing the Mac signing " + + "certificate(s) — _Mac App Distribution_ (3rd Party Mac Developer Application) for App " + + "Store builds, _Developer ID Application_ for Developer ID builds, or both bundled into " + + "the same P12 when `macNative.distribution=both`. Not interchangeable with the iOS " + + "distribution certificate. Required for cloud Mac builds.")); + + h.add(new Hint("codename1.mac.certificatePassword") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .external() + .doc("Mac Native cloud builds only. Password to unlock the P12 referenced by " + + "`codename1.mac.certificate`. Required for cloud Mac builds.")); + + h.add(new Hint("codename1.mac.provision") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .external() + .doc("Mac Native cloud builds only. Path to the Mac provisioning profile " + + "(`.provisionprofile`). Apple issues distinct provisioning profiles for Mac App Store and " + + "Developer ID distribution — pass the one that matches the chosen channel.")); + + h.add(new Hint("desktop.fontSizes") + .group(HintGroup.DESKTOP) + .type(HintType.STRING) + .platform("desktop") + .external() + .doc("Indicates the sizes in pixels for the system fonts as a comma delimited string " + + "containing 3 numbers for small,medium,large fonts.")); + + h.add(new Hint("desktop.mac.cef") + .group(HintGroup.DESKTOP) + .type(HintType.BOOLEAN) + .platform("mac") + .external() + .doc("Whetherto use CEF for media or BrowserComponent instead of JavaFX in Mac desktop builds. " + + "true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a " + + "future version.")); + + h.add(new Hint("desktop.theme") + .group(HintGroup.DESKTOP) + .type(HintType.STRING) + .platform("desktop") + .external() + .doc("Name of the theme res file (without the \".res\" extension) to use as the \"native\" theme. " + + "By default this is native indicating iOS theme on Mac and Windows Metro on Windows. If " + + "its something else then the app will try to load the file /themeName.res (placed in " + + "native/Java SE directory).")); + + h.add(new Hint("desktop.themeMac") + .group(HintGroup.DESKTOP) + .type(HintType.STRING) + .platform("desktop") + .external() + .doc("Same as `desktop.theme` but specific to macOS")); + + h.add(new Hint("desktop.themeWin") + .group(HintGroup.DESKTOP) + .type(HintType.STRING) + .platform("desktop") + .external() + .doc("Same as `desktop.theme` but specific to Windows")); + + h.add(new Hint("desktop.win.cef") + .group(HintGroup.DESKTOP) + .type(HintType.BOOLEAN) + .platform("desktop") + .external() + .doc("Whether to use CEF for media and BrowserComponent instead of JavaFX in windows desktop " + + "builds. true/false. Default value is `false` (Jan 2021), but this will be changed to " + + "`true` in a future version.")); + + h.add(new Hint("desktop.windowsOutput") + .group(HintGroup.DESKTOP) + .type(HintType.STRING) + .platform("desktop") + .external() + .doc("Can be exe or msi depending on desired results")); + + h.add(new Hint("ios.NSXXXUsageDescription") + .group(HintGroup.IOS_PRIVACY) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("iOS privacy flags for using certain APIs. Starting with Xcode 8, you're required to add " + + "usage description strings for certain APIs. Find a full list of the available keys in " + + "https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html[Apple's " + + "docs]. Some relevant ones include `ios.NSCameraUsageDescription`, " + + "`ios.NSContactsUsageDescription`, `ios.NSLocationAlwaysUsageDescription`, " + + "`NSLocationUsageDescription`, `ios.NSMicrophoneUsageDescription`, " + + "`ios.NSPhotoLibraryAddUsageDescription`, `ios.NSSpeechRecognitionUsageDescription`, " + + "`ios.NSSiriUsageDescription`")); + + h.add(new Hint("ios.appext.NAME.provisioningURL") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("Cloud device builds only. URL of the provisioning profile for a generic app extension " + + "dropped into `ios/app_extensions/NAME/` (or a generated extension such as `CN1Widgets`), " + + "used when the extension folder doesn't bundle a `.mobileprovision` itself. The profile " + + "is installed on the build machine and added to the export options per bundle id. Used " + + "for both debug and release builds unless a qualified variant (below) is set. An " + + "extension is signed against its own App ID, so a device build with no profile for it -- " + + "by any of the three carriers -- is refused unless the app's own profile is a wildcard " + + "that covers the extension's bundle id.")); + + h.add(new Hint("ios.application_exits") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .platform("ios") + .external() + .doc("true/false (defaults to false). Indicates whether the application should exit on home " + + "button press. The default is to exit, leaving the application running is only tested at " + + "the moment.")); + + h.add(new Hint("ios.debug.archs") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("Can be set to \"armv7\" to force iOS debug builds to be 32 bit. By default, debug builds " + + "are 64 bit only.")); + + h.add(new Hint("ios.debug.distributionMethod") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("Specifies distribution type for debug iOS builds only. This is used for enterprise or " + + "ad-hoc builds (using values \"enterprise\" and \"ad-hoc\" respectively).")); + + h.add(new Hint("ios.distributionMethod") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("Specifies distribution type for debug iOS builds. This is used for enterprise or ad-hoc " + + "builds (using values \"enterprise\" and \"ad-hoc\" respectively).")); + + h.add(new Hint("ios.entitlementsInject") + .group(HintGroup.IOS) + .type(HintType.XML) + .separator("") + .platform("ios") + .external() + .doc("Content to inject into the iOS entitlements file. This should be in the Plist XML " + + "format. See " + + "https://developer.apple.com/documentation/bundleresources/entitlements?language=objc[Apple " + + "Entitlements Documentation].")); + + h.add(new Hint("ios.keychainAccessGroup") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("Space-delimited list of keychain access groups that this app has access to as described " + + "in " + + "https://developer.apple.com/library/content/documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html#//apple_ref/doc/uid/TP30000897-CH204-SW11[Apple's " + + "documentation]. These are added to the entitlements file with the key " + + "`keychain-access-groups`.")); + + h.add(new Hint("ios.newPipeline") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .platform("ios") + .external() + .doc("Boolean true/false defaults to true. Allows toggling the OpenGL ES 2.0 drawing pipeline " + + "off to the older OGL ES 1.0 pipeline.")); + + h.add(new Hint("ios.release.archs") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("Can be set to \"arm64\" to only build iOS release builds for 64 bit. By default, release " + + "builds are both 32 and 64 bit.")); + + h.add(new Hint("ios.release.distributionMethod") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("Specifies distribution type for release iOS builds only. This is used for enterprise or " + + "ad-hoc builds (using values \"enterprise\" and \"ad-hoc\" respectively).")); + + h.add(new Hint("ios.rpmalloc") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("`true`/`false` Use https://github.com/rampantpixels/rpmalloc[rpmalloc] instead of " + + "malloc/free for memory allocation in ParparVM. This will cause the deployment target to " + + "be changed to a minimum of iOS 8.0.")); + + h.add(new Hint("ios.statusbar_hidden") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .platform("ios") + .external() + .doc("true/false defaults to false. Hides the iOS status bar if set to true.")); + + h.add(new Hint("ios.testFlight") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .platform("ios") + .external() + .doc("Boolean true/false defaults to false and works only for pro accounts. Enables the " + + "testflight support in the release binaries for easy beta testing. Notice that the IDE " + + "plugin has a \"Test Flight\" check box you *should* use under the iOS section.")); + + h.add(new Hint("ios.xcode_version") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .external() + .doc("The version of Xcode used on the server. Defaults to 4.5; accepts 5.0 as an option and " + + "nothing else.")); + + h.add(new Hint("javascript.inject.afterHead") + .group(HintGroup.JAVASCRIPT) + .type(HintType.STRING) + .platform("javascript") + .external() + .doc("Content to be injected into the index.html file at the end of the `` tag.")); + + h.add(new Hint("javascript.inject.beforeHead") + .group(HintGroup.JAVASCRIPT) + .type(HintType.STRING) + .platform("javascript") + .external() + .doc("Content to be injected into the index.html file at the beginning of the `` tag.")); + + h.add(new Hint("javascript.minifying") + .group(HintGroup.JAVASCRIPT) + .type(HintType.BOOLEAN) + .platform("javascript") + .external() + .doc("true/false (defaults to `true`). By default the JavaScript code is minified to reduce " + + "file size. You may optionally disable minification by setting `javascript.minifying` to " + + "`false`.")); + + h.add(new Hint("javascript.port") + .group(HintGroup.JAVASCRIPT) + .type(HintType.STRING) + .platform("javascript") + .external() + .doc("`parparvm` (default) or `teavm`. Selects the public JavaScript compiler for cloud " + + "builds. `teavm` retains the original builder as a compatibility fallback.")); + + h.add(new Hint("javascript.sourceFilesCopied") + .group(HintGroup.JAVASCRIPT) + .type(HintType.BOOLEAN) + .platform("javascript") + .external() + .doc("true/false (defaults to `false`). Setting this flag to `true` will cause available java " + + "source files to be included in the resulting .zip and .war files. These may be used by " + + "Chrome during debugging.")); + + h.add(new Hint("javascript.stopOnErrors") + .group(HintGroup.JAVASCRIPT) + .type(HintType.BOOLEAN) + .platform("javascript") + .external() + .doc("true/false (defaults to `true`). Causes a TeaVM JavaScript build to fail when the " + + "compiler reports warnings. Setting this to `false` may allow the fallback builder to " + + "complete, but can turn compiler diagnostics into runtime failures that are more " + + "difficult to debug.")); + + h.add(new Hint("javascript.teavm.version") + .group(HintGroup.JAVASCRIPT) + .type(HintType.STRING) + .platform("javascript") + .external() + .doc("(Optional) The version of TeaVM to use for the build. *Use caution*, only use this " + + "property if you know what you're doing!")); + + h.add(new Hint("mac.desktop-vm") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .external() + .doc("The JVM the should be bundled with Mac desktop build. Mac desktop builds only. Supported " + + "values: zuluFx8, zulu11, zuluFx11")); + + h.add(new Hint("macNative.entitlements.allowJit") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .external() + .doc("Mac Native builds only. `true` enables `com.apple.security.cs.allow-jit` for hardened " + + "runtime. ParparVM is AOT-compiled so this is `false` by default; flip when bundling a " + + "JIT-using cn1lib.")); + + h.add(new Hint("macNative.entitlements.appSandbox") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .external() + .doc("Mac Native builds only. `true` enables `com.apple.security.app-sandbox`. Default is " + + "`true` for the `appStore` channel (Mac App Store requires the sandbox), `false` for " + + "`developerID`.")); + + h.add(new Hint("macNative.entitlements.hardenedRuntime") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .external() + .doc("Mac Native builds only. `true` enables hardened runtime restrictions. Default is `true` " + + "for `developerID` (notarization requires it), `false` for `appStore`.")); + + h.add(new Hint("macNative.entitlements.network.client") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .external() + .doc("Mac Native builds only. Toggles `com.apple.security.network.client`. Default `true`.")); + + h.add(new Hint("macNative.entitlements.network.server") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .external() + .doc("Mac Native builds only. Toggles `com.apple.security.network.server`. Default `false`.")); + + h.add(new Hint("macNative.provisioningProfile.appStore") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .external() + .doc("Mac Native builds only. Provisioning profile name for App Store distribution — used only " + + "when `macNative.signing.style=manual`.")); + + h.add(new Hint("macNative.provisioningProfile.developerID") + .group(HintGroup.MAC_NATIVE) + .type(HintType.STRING) + .platform("mac") + .external() + .doc("Mac Native builds only. Provisioning profile name for Developer ID distribution — used " + + "only when `macNative.signing.style=manual`.")); + + h.add(new Hint("win.desktop-vm") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("windows") + .external() + .doc("The JVM that should be bundled in the Windows desktop build. Windows desktop builds " + + "only. Supported values: zulu8, zuluFx8, zulu8-32bit, zuluFx8-32bit, zulu11, zuluFx11, " + + "zulu11-32bit, zuluFx11-32bit")); + + h.add(new Hint("win.installDirName") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("windows") + .external() + .doc("Windows desktop builds only. Overrides the default installation folder name suggested by " + + "the installer (under `Program Files`). Defaults to the application's main class name for " + + "backward compatibility. Use this build hint to set a user-friendly installation folder " + + "name (for example, `win.installDirName=My Application`). The application ID used by " + + "Windows for upgrade detection is unaffected, so existing installations continue to " + + "upgrade.")); + + h.add(new Hint("win.shortcutName") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("windows") + .external() + .doc("Windows desktop builds only. Overrides the name used for the Start Menu shortcut, the " + + "Desktop shortcut and (when `win.launchOnStart=true`) the autostart shortcut. Defaults to " + + "the application's main class name for backward compatibility. Use this build hint to set " + + "a user-friendly shortcut label (for example, `win.shortcutName=My Application`).")); + + h.add(new Hint("win.vm32bit") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .platform("windows") + .external() + .doc("true/false (defaults to false). Forces windows desktop builds to use the Win32 JVM " + + "instead of the 64 bit VM making them compatible with older Windows Machines. This is off " + + "by default at the moment because of a bug in JDK 8 update 112 that might cause this to " + + "fail for some cases")); + + h.add(new Hint("windows.extensions") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .external() + .doc("Historical build hint for the discontinued UWP target. It's retained here only for " + + "legacy reference and isn't used by current supported build targets.")); + + h.add(new Hint("xxx.minPlayServicesVersion") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .external() + .doc("This is a special case build hint. You can use any prefix to the build hint and the " + + "convention is to use your cn1lib name. It's identical to " + + "`android.minPlayServicesVersion` with the exception that the \"highest version wins.\" " + + "That way if your cn1lib requires play services 9+ and uses: " + + "`myLib.minPlayServicesVersion=9.0.0` and another library has " + + "`otherLib.minPlayServicesVersion=10.0.0` then play services will be 10.0.0")); + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java new file mode 100644 index 00000000000..d02748e7855 --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * Hints with no platform prefix, plus hardening and on-device debugging. + * + *

Seeded by mining every {@code getArg} call site in the builders, so the + * name and the default match what the build actually reads. Curated entries + * carry an annotation attribute and, where the domain is provably closed, an + * enum; the rest are described but set through + * {@code codenameone_settings.properties}.

+ * + *

Split out of {@link BuildHints} because a single class initializer + * holding every entry would exceed the JVM's 64KB per-method limit.

+ */ +final class BuildHintsGeneral { + + private BuildHintsGeneral() { + } + + static void register(List h) { + h.add(new Hint("KeepScreenOn") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .def("false") + .platform("general") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("android.onDeviceDebug") + .annotatedAs(HintGroup.ON_DEVICE_DEBUG, "android") + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder", "CN1BuildMojo") + .doc("Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` " + + "is marked `android:debuggable=\"true\"`, R8/proguard is disabled, and the build is pinned " + + "to debug-only (`android.release` is forced off and `android.debug` is forced on) so a " + + "stray hint can't ship a release-signed APK that's `debuggable=\"true\"`. Pair with the " + + "`cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to " + + "install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds " + + "that don't carry it — release builds are unaffected. See the On-Device Debugging " + + "(Android) chapter for the full flow.")); + + h.add(new Hint("androidx.appcompat.version") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("build.incSources") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("CN1BuildMojo")); + + h.add(new Hint("build.testReporter") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("build.unitTest") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("CN1BuildMojo")); + + h.add(new Hint("cn1.androidTheme") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("cn1.buildKey") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("cn1.entitled") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .def("true") + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("cn1.harden.forceOff") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("cn1.hardenLevel") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .def("off") + .platform("general") + .consumedBy("AndroidGradleBuilder", "Executor")); + + h.add(new Hint("cn1.hardened") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .def("false") + .platform("general") + .consumedBy("AndroidGradleBuilder", "Executor")); + + h.add(new Hint("cn1.hardening.libraryJars") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("cn1.mappingId") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("cn1.nativeTheme") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder")); + + h.add(new Hint("db.legacy") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("Executor", "GenerateDesktopAppWrapperMojo")); + + h.add(new Hint("delayPushCompletion") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .def("false") + .platform("general") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder")); + + h.add(new Hint("facebook.appId") + .annotatedAs(HintGroup.GENERAL, "facebookAppId") + .type(HintType.STRING) + .def("706695982682332") + .platform("general") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder") + .doc("The application ID for an app that requires native Facebook login integration, this " + + "defaults to null which means native Facebook support shouldn't be in the app")); + + h.add(new Hint("facebook.clientToken") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("AndroidGradleBuilder") + .doc("The client token for an app that requires native Facebook login integration, this is " + + "required if the facebook.appId is set.")); + + h.add(new Hint("gcm.sender_id") + .annotatedAs(HintGroup.GENERAL, "gcmSenderId") + .type(HintType.STRING) + .platform("general") + .consumedBy("AndroidGradleBuilder") + .doc("The Android/chrome push identifier, see the push section for more details")); + + h.add(new Hint("google.adUnitId") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder") + .doc("Allows integrating Admob/Google Play ads into the application see " + + "link:https://www.codenameone.com/blog/adding-google-play-ads.html[this]")); + + h.add(new Hint("gradleDependencies") + .group(HintGroup.GENERAL) + .type(HintType.STRING_LIST) + .separator("\n") + .platform("general") + .consumedBy("AndroidGradleBuilder", "MapsProviderInjector")); + + h.add(new Hint("harden.allowUnhardenedLocalBuild") + .annotatedAs(HintGroup.HARDENING, "allowUnhardenedLocalBuild") + .type(HintType.BOOLEAN) + .def("false") + .platform("general") + .consumedBy("CN1BuildMojo") + .doc("Permits a local or source build to run with hardening requested but not applied. Without " + + "it such a build is refused, so a hardened app is never shipped from a target that cannot " + + "actually harden it.")); + + h.add(new Hint("harden.controlFlow") + .annotatedAs(HintGroup.HARDENING, "controlFlow") + .values("HardenControlFlow", "off", "on") + .platform("general") + .consumedBy("CN1BuildMojo") + .doc("Overrides control-flow obfuscation independently of harden.level.")); + + h.add(new Hint("harden.ios.enabled") + .group(HintGroup.HARDENING) + .type(HintType.BOOLEAN) + .def("true") + .platform("general") + .consumedBy("CN1BuildMojo")); + + h.add(new Hint("harden.keep") + .annotatedAs(HintGroup.HARDENING, "keep") + .type(HintType.TEXT_BLOCK) + .platform("general") + .consumedBy("AndroidGradleBuilder") + .doc("Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at " + + "runtime and so cannot be found by the automatic analysis. Same syntax as " + + "android.proguardKeep, so existing rules port directly. Rules are separated by newlines " + + "only, because a semicolon is legal inside a rule body such as { *; }.")); + + h.add(new Hint("harden.level") + .annotatedAs(HintGroup.HARDENING, "level") + .values("HardenLevel", "off", "standard", "aggressive", "paranoid") + .def("off") + .platform("general") + .consumedBy("AndroidGradleBuilder", "CN1BuildMojo", "Executor") + .doc("Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized " + + "value fails the build rather than being quietly treated as off.")); + + h.add(new Hint("harden.mac.enabled") + .group(HintGroup.HARDENING) + .type(HintType.BOOLEAN) + .def("true") + .platform("general") + .consumedBy("CN1BuildMojo")); + + h.add(new Hint("harden.rename") + .annotatedAs(HintGroup.HARDENING, "rename") + .type(HintType.BOOLEAN) + .platform("general") + .consumedBy("CN1BuildMojo") + .doc("Overrides symbol renaming independently of harden.level.")); + + h.add(new Hint("harden.strings") + .annotatedAs(HintGroup.HARDENING, "strings") + .values("HardenStrings", "off", "constants", "all") + .platform("general") + .consumedBy("CN1BuildMojo") + .doc("Overrides string obfuscation independently of harden.level: off, constants or all.")); + + h.add(new Hint("harden.tv.enabled") + .group(HintGroup.HARDENING) + .type(HintType.BOOLEAN) + .def("true") + .platform("general") + .consumedBy("CN1BuildMojo")); + + h.add(new Hint("harden.watch.enabled") + .group(HintGroup.HARDENING) + .type(HintType.BOOLEAN) + .def("true") + .platform("general") + .consumedBy("CN1BuildMojo")); + + h.add(new Hint("ios.onDeviceDebug") + .annotatedAs(HintGroup.ON_DEVICE_DEBUG, "ios") + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP " + + "listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits " + + "source-line and locals metadata so a desktop proxy can serve the running app to any " + + "JDWP-speaking debugger. Has no effect on release builds. See the On-Device Debugging " + + "(iOS) chapter for the full flow.")); + + h.add(new Hint("ios.onDeviceDebug.proxyHost") + .annotatedAs(HintGroup.ON_DEVICE_DEBUG, "iosProxyHost") + .type(HintType.STRING) + .def("127.0.0.1") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Hostname or IP address the device-side listener dials to reach the desktop proxy. " + + "Default `127.0.0.1` (correct for the native iOS simulator). For a physical device, set " + + "this to the developer laptop's LAN IP. Has no effect unless `ios.onDeviceDebug=true`.")); + + h.add(new Hint("ios.onDeviceDebug.proxyPort") + .annotatedAs(HintGroup.ON_DEVICE_DEBUG, "iosProxyPort") + .type(HintType.INT) + .def("55333") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("TCP port on `ios.onDeviceDebug.proxyHost` where the proxy is listening for the device. " + + "Default `55333`. Has no effect unless `ios.onDeviceDebug=true`.")); + + h.add(new Hint("ios.onDeviceDebug.waitForAttach") + .annotatedAs(HintGroup.ON_DEVICE_DEBUG, "iosWaitForAttach") + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to false. When `true`, the app blocks at startup until the " + + "proxy connects and the IDE tells the VM to continue. Useful when the breakpoint to " + + "investigate fires during app boot. Has no effect unless `ios.onDeviceDebug=true`.")); + + h.add(new Hint("java.version") + .group(HintGroup.GENERAL) + .type(HintType.INT) + .def("8") + .platform("general") + .consumedBy("AndroidGradleBuilder", "CN1BuildMojo", "CreateGameSceneMojo", "InstallCn1libsMojo", "OpenGameBuilderMojo") + .doc("Valid values include 5 or 8. Indicates the JVM version that should be used for server " + + "compilation, this is defined by default for newly created apps based on the Java 8 mode " + + "selection")); + + h.add(new Hint("maps.provider") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("MapsProviderInjector")); + + h.add(new Hint("nativeTheme") + .annotatedAs(HintGroup.GENERAL, "nativeTheme") + .values("NativeThemeMode", "modern", "legacy", "custom") + .platform("general") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder") + .doc("`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both " + + "`ios.themeMode` and `and.themeMode` together when those aren't set explicitly. `modern` " + + "= liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the " + + "framework native theme entirely. The legacy alias `cn1.nativeTheme` is still accepted.")); + + h.add(new Hint("noExtraResources") + .annotatedAs(HintGroup.GENERAL, "noExtraResources") + .type(HintType.BOOLEAN) + .def("false") + .platform("general") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder") + .doc("true/false (defaults to false). Blocks codename one from injecting its own resources " + + "when set to true, the only effect this has is in slightly reducing archive size. This " + + "might have adverse effects on some features of Codename One so it isn't recommended.")); + + h.add(new Hint("requireKotlinStdlib") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("AndroidGradleBuilder")); + + h.add(new Hint("tvMain") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("IPhoneBuilder", "TvNativeBuilder")); + + h.add(new Hint("vserv.allowSkipping") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .def("true") + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("vserv.category") + .group(HintGroup.GENERAL) + .type(HintType.INT) + .def("29") + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("vserv.countryCode") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .def("null") + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("vserv.locale") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .def("en_US") + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("vserv.networkCode") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .def("null") + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("vserv.scaleMode") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .def("false") + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("vserv.transition") + .group(HintGroup.GENERAL) + .type(HintType.INT) + .def("300000") + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("vserv.zone") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("Executor")); + + h.add(new Hint("watchMain") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder", "WatchNativeBuilder")); + + h.add(new Hint("watchStandalone") + .group(HintGroup.GENERAL) + .type(HintType.BOOLEAN) + .def("false") + .platform("general") + .consumedBy("AndroidGradleBuilder", "WatchNativeBuilder")); + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java new file mode 100644 index 00000000000..242951b086b --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java @@ -0,0 +1,1203 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import com.codename1.build.shared.BuildHints.Hint; + +import java.util.List; + +/** + * iOS build hints, including the Info.plist privacy strings. + * + *

Seeded by mining every {@code getArg} call site in the builders, so the + * name and the default match what the build actually reads. Curated entries + * carry an annotation attribute and, where the domain is provably closed, an + * enum; the rest are described but set through + * {@code codenameone_settings.properties}.

+ * + *

Split out of {@link BuildHints} because a single class initializer + * holding every entry would exceed the JVM's 64KB per-method limit.

+ */ +final class BuildHintsIos { + + private BuildHintsIos() { + } + + static void register(List h) { + h.add(new Hint("ios.NFCReaderUsageDescription") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.NSBonjourServices") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.NSCalendarsFullAccessUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "calendarsFullAccessUsageDescription") + .type(HintType.STRING) + .def("This app uses your calendars to read and schedule events.") + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder")); + + h.add(new Hint("ios.NSCalendarsUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "calendarsUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder")); + + h.add(new Hint("ios.NSCalendarsWriteOnlyAccessUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "calendarsWriteOnlyAccessUsageDescription") + .type(HintType.STRING) + .def("This app uses your calendar to schedule events.") + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder")); + + h.add(new Hint("ios.NSCameraUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "cameraUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("MacNativeBuilder")); + + h.add(new Hint("ios.NSHealthShareUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "healthShareUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.NSHealthUpdateUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "healthUpdateUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.NSLocalNetworkUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "localNetworkUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.NSLocationAlwaysAndWhenInUseUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "locationAlwaysAndWhenInUseUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.NSLocationAlwaysUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "locationAlwaysUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.NSLocationWhenInUseUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "locationWhenInUseUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.NSMicrophoneUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "microphoneUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("MacNativeBuilder")); + + h.add(new Hint("ios.NSRemindersFullAccessUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "remindersFullAccessUsageDescription") + .type(HintType.STRING) + .def("This app uses your reminders to read and schedule tasks.") + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder")); + + h.add(new Hint("ios.NSRemindersUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "remindersUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder")); + + h.add(new Hint("ios.UIRequiredDeviceCapabilities") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.actionSheetStyle") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.add_libs") + .annotatedAs(HintGroup.IOS, "addLibs") + .type(HintType.STRING_LIST) + .separator(";") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("A semicolon separated list of libraries that should be linked to the app to build it")); + + h.add(new Hint("ios.afterFinishLaunching") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Objective-C code that can be injected into the iOS app delegate at the bottom of the " + + "body of the didFinishLaunchingWithOptions callback method")); + + h.add(new Hint("ios.appAttest") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.appAttest.environment") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.appUsesNonExemptEncryption") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.app_groups") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Space-delimited list of app groups that this app belongs to as described in " + + "https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19[Apple's " + + "documentation]. These are added to the entitlements file with key " + + "`com.apple.security.application-groups`.")); + + h.add(new Hint("ios.applicationDidEnterBackground") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Objective-C code that can be injected into the iOS callback method (message) " + + "`applicationDidEnterBackground`.")); + + h.add(new Hint("ios.applicationQueriesSchemes") + .annotatedAs(HintGroup.IOS, "applicationQueriesSchemes") + .type(HintType.STRING_LIST) + .separator(",") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Comma separated list of url schemes that `canExecute` will respect on iOS. If the url " + + "scheme isn't mentioned here `canExecute` will return false starting with iOS 9. Notice " + + "that this collides with `ios.plistInject` when used with the " + + "`LSApplicationQueriesSchemes...` value so you should use one or the other. " + + "For example, to enable `canExecute` for a url like `myurl://xys` you can use: " + + "`myurl,myotherurl`")); + + h.add(new Hint("ios.associatedDomains") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Comma-delimited list of domains associated with this app. Each domain should be prefixed " + + "by a supported prefix. For example, \"applinks:\" or \"webcredentials:.\" See " + + "https://developer.apple.com/documentation/security/password_autofill/setting_up_an_app_s_associated_domains?language=objc[Apple's " + + "documentation on Associated domains] for more information.")); + + h.add(new Hint("ios.backgroundProcessingIds") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.background_modes") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.beforeFinishLaunching") + .annotatedAs(HintGroup.IOS, "beforeFinishLaunching") + .type(HintType.TEXT_BLOCK) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Objective-C code that can be injected into the iOS app delegate at the top of the body " + + "of the didFinishLaunchingWithOptions callback method")); + + h.add(new Hint("ios.bitcode") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("true/false defaults to false. Enables bitcode support for the build.")); + + h.add(new Hint("ios.blockScreenshotsOnEnterBackground") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("true/false (defaults to false). Indicates that app should prevent iOS from taking " + + "screenshots when app enters background. Described " + + "https://shannah.github.io/cn1-recipes/#_hiding_sensitive_data_when_entering_background[here].")); + + h.add(new Hint("ios.bluetooth.background") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.buildType") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("debug") + .platform("ios") + .consumedBy("IPhoneBuilder", "WatchNativeBuilder")); + + h.add(new Hint("ios.bundleVersion") + .annotatedAs(HintGroup.IOS, "bundleVersion") + .type(HintType.VERSION) + .platform("ios") + .consumedBy("IPhoneBuilder", "WatchNativeBuilder") + .doc("Indicates the version number of the bundle, this is useful if you want to create a minor " + + "version number change for the beta testing support")); + + h.add(new Hint("ios.carplay.audio") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.carplay.messaging") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.carplay.navigation") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.carplay.poi") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.convertSignalsToExceptions") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.criticalAlerts") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.crypto.gcm") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.debug.teamId") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder", "TvNativeBuilder", "WatchNativeBuilder") + .doc("Specifies the team ID associated with the iOS debug provisioning profile and " + + "certificate.")); + + h.add(new Hint("ios.delayPushCompletion") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.dependencyManager") + .annotatedAs(HintGroup.IOS, "dependencyManager") + .values("IosDependencyManager", "auto", "cocoapods", "spm", "both", "none") + .def("auto") + .platform("ios") + .consumedBy("IOSDependencyManager") + .doc("Which native dependency manager to use: auto picks one from whichever of ios.pods and " + + "ios.spm.packages is set, and cocoapods, spm or both require the matching hint to be set. " + + "An unrecognized value fails the build.")); + + h.add(new Hint("ios.deployment_target") + .annotatedAs(HintGroup.IOS, "deploymentTarget") + .type(HintType.VERSION) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Minimum iOS version the build targets. Set it to the lowest iOS you actually support; a " + + "higher value excludes older devices from the App Store listing.")); + + h.add(new Hint("ios.detectJailbreak") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("true/false (defaults to false). When true, the iOS app will exit on launch if it detects " + + "that it's running on a jailbroken device.")); + + h.add(new Hint("ios.devLocale") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.disableScreenshots") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.enableAutoplayVideo") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to false. Makes videos \"autoplay\" when loaded on iOS")); + + h.add(new Hint("ios.enableBadgeClear") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to true. Clears the badge value with every load of the app, " + + "this is useful if the app doesn't manually keep track of number values for the badge")); + + h.add(new Hint("ios.enableGalleryMultiselect") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.enableStatusBar7") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.entitlements.com.apple.developer") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.entitlements.com.apple.developer.applesignin") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.entitlements.com.apple.developer.healthkit") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.entitlements.com.apple.developer.homekit") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.entitlements.com.apple.developer.networking.HotspotConfiguration") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.entitlements.com.apple.developer.nfc.hce") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.entitlements.com.apple.developer.nfc.readersession.formats") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.facebook.usePods") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.facebook.version") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("~>5.6.0") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.facebook_permissions") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Permissions for Facebook used in the Android build target, applicable only if Facebook " + + "native integration is used.")); + + h.add(new Hint("ios.failOnWarning") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.fieldNullChecks") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.fileSharingEnabled") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.firebaseAnalytics") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.firebaseAnalyticsVersion") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.force64") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.generateSplashScreens") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to false. Enables legacy generation of splash screen images " + + "instead of the current launch storyboards.")); + + h.add(new Hint("ios.glAppDelegateBody") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Objective-C code that can be injected into the iOS app delegate within the body of the " + + "file before the end. This only makes sence for methods that aren't already declared in " + + "the class")); + + h.add(new Hint("ios.glAppDelegateHeader") + .annotatedAs(HintGroup.IOS, "glAppDelegateHeader") + .type(HintType.TEXT_BLOCK) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Objective-C code that can be injected into the iOS app delegate at the top of the file. " + + "For example, if you need to include headers or make special imports for other injected " + + "code")); + + h.add(new Hint("ios.googleAdUnitId") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Allows integrating admob/google play ads, this is effectively identical to " + + "google.adUnitId but only applies to iOS")); + + h.add(new Hint("ios.googleAdUnitIdPadding") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Indicates the amount of padding to pass to the Google Ads placed at the bottom of the " + + "screen with `google.adUnitId`")); + + h.add(new Hint("ios.googleAdUnitTestDevice") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("97cfc76e5efbc6dfa7eb2e6857b613a0") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.gplus.clientId") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.hceAids") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.headphoneCallback") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to false. When set to true it assumes the main class has two " + + "methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately " + + "as needed")); + + h.add(new Hint("ios.health.backgroundDelivery") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.health.recalibrateEstimates") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.health.required") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.home.appGroup") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.home.commissioning") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.home.commissioning.displayName") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.home.commissioning.fabric") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.home.commissioning.vendorId") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("0xFFF1") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.home.required") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.includeNullChecks") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.includePush") + .annotatedAs(HintGroup.IOS, "includePush") + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("true/false (defaults to false). Whether to include the push capabilities in the iOS " + + "build. Notice that the IDE plugin has an \"Include Push\" check box you *should* use under " + + "the iOS section.")); + + h.add(new Hint("ios.intents.appIntents") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.intents.minDeploymentTarget") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.interface_orientation") + .annotatedAs(HintGroup.IOS, "interfaceOrientation") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("UIInterfaceOrientationPortrait by default. Indicates the orientation, one or more of " + + "(separated by colon :): `UIInterfaceOrientationPortrait`, " + + "`UIInterfaceOrientationPortraitUpsideDown`, `UIInterfaceOrientationLandscapeLeft`, " + + "`UIInterfaceOrientationLandscapeRight`. Notice that the IDE plugin has an \"Interface " + + "Orientation\" combo box you *should* use under the iOS section.")); + + h.add(new Hint("ios.keyboardOpen") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Flips between iOS keyboard open mode and autofold keyboard mode. Defaults to true which " + + "means the keyboard will remain open and not fold automatically when editing moves to " + + "another field.")); + + h.add(new Hint("ios.launchPlaceholder") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.launchStoryboardName") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("LaunchScreen") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.locationUsageDescription") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder", "WatchNativeBuilder") + .doc("This flag is required for iOS 8 and newer if you're using the location API. It needs to " + + "include a description of the reason for which you need access to the users location")); + + h.add(new Hint("ios.lowMemCamera") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.metal") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to true. Selects the Metal rendering backend " + + "(`CAMetalLayer`) over the legacy OpenGL ES 2 path (`CAEAGLLayer`). Metal is the " + + "supported iOS graphics API; OpenGL ES is deprecated. Set to `false` to opt out if you " + + "hit a Metal-only rendering regression. See link:#_metal_renderer[Working with iOS / " + + "Metal renderer] for details.")); + + h.add(new Hint("ios.metal.colorSpace") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("sRGB") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Selects the `CAMetalLayer.colorspace` for the Metal renderer. Accepts `sRGB` (default), " + + "`displayP3`, `deviceRGB`, `linearSRGB`, `extendedSRGB`, `extendedLinearSRGB`, or `none`. " + + "Has no effect when `ios.metal=false`. See " + + "link:#_choosing_a_color_space_for_the_metal_renderer[Working with iOS / Choosing a color " + + "space] for the full table.")); + + h.add(new Hint("ios.minDeploymentTarget") + .annotatedAs(HintGroup.IOS, "minDeploymentTarget") + .type(HintType.VERSION) + .def("6.0") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("The null and empty-string reads of this hint are presence checks; 6.0 is the substantive " + + "default (IPhoneBuilder.java:4671).")); + + h.add(new Hint("ios.mopubAdSize") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("MOPUB_BANNER_SIZE") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.mopubId") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.mopubTabletAdSize") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("MOPUB_LEADERBOARD_SIZE") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.mopubTabletId") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.multitasking") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Set to true to enable iOS multitasking and split-screen support. This only works if " + + "`ios.xcode_verson=9.2`.")); + + h.add(new Hint("ios.newStorageLocation") + .annotatedAs(HintGroup.IOS, "newStorageLocation") + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("true/false defaults to false but defined on new projects as true by default. This " + + "changes the storage directory on iOS from using caches to using the documents directory " + + "which is the recommended location but might break compatibility. This is described in " + + "https://github.com/codenameone/CodenameOne/issues/1480[this issue]")); + + h.add(new Hint("ios.noUIWebView") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.no_strip") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.notificationPermissionAtLaunch") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("true/false (defaults to false). Backward-compatibility flag for the pre-issue-#4876 " + + "behavior. By default, the iOS notification permission prompt is deferred until the app " + + "calls `Push.register()` or schedules a `LocalNotification`, matching the Android flow " + + "and giving the developer a chance to display a rationale screen first. Set this hint to " + + "`true` to restore the legacy behavior in which the prompt fires automatically inside " + + "`application:didFinishLaunchingWithOptions:` as soon as the app launches. Existing apps " + + "relying on the prompt being shown at launch should set this to `true`; new apps should " + + "leave it disabled and trigger the prompt explicitly when they're ready to ask for " + + "permission.")); + + h.add(new Hint("ios.objC") + .annotatedAs(HintGroup.IOS, "objC") + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Added the `-ObjC` compile flag to the project files which some native libraries require")); + + h.add(new Hint("ios.openURLInject") + .group(HintGroup.IOS) + .type(HintType.XML) + .separator("") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.optimizer") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("on") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.plistInject") + .annotatedAs(HintGroup.IOS, "plistInject") + .type(HintType.XML) + .separator("") + .platform("ios") + .consumedBy("IPhoneBuilder", "WatchNativeBuilder") + .doc("entries to inject into the iOS plist file during build.")); + + h.add(new Hint("ios.pods") + .annotatedAs(HintGroup.IOS, "pods") + .type(HintType.STRING_LIST) + .separator(",") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be linked to " + + "the app to build it. For example, `AFNetworking ~> 2.6, ORStackView ~> 3.0, SwiftyJSON " + + "~> 2.3`")); + + h.add(new Hint("ios.pods.build.CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.pods.build.CLANG_ENABLE_MODULES") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.pods.platform") + .annotatedAs(HintGroup.IOS, "podsPlatform") + .type(HintType.VERSION) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a minimum " + + "platform level. For example, `ios.pods.platform=7.0`.")); + + h.add(new Hint("ios.pods.sources") + .annotatedAs(HintGroup.IOS, "podsSources") + .type(HintType.STRING_LIST) + .separator(",") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Extra CocoaPods spec repositories to search, in addition to the default trunk.")); + + h.add(new Hint("ios.pods.use_frameworks!") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.prerendered_icon") + .annotatedAs(HintGroup.IOS, "prerenderedIcon") + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("true/false defaults to false. The iOS build process adapts the submitted icon for iOS " + + "conventions (adding an overlay) that might not be appropriate on some icons. Setting " + + "this to true leaves the icon unchanged (only scaled).")); + + h.add(new Hint("ios.project_type") + .annotatedAs(HintGroup.IOS, "projectType") + .values("IosProjectType", "ios", "ipad", "iphone") + .def("ios") + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder") + .doc("one of ios, ipad, iphone (defaults to ios). Indicates whether the resulting binary is " + + "targeted to the iphone only or ipad only. Notice that the IDE plugin has a \"Project " + + "Type\" combo box you *should* use under the iOS section.")); + + h.add(new Hint("ios.release.teamId") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder", "TvNativeBuilder", "WatchNativeBuilder") + .doc("Specifies the team ID associated with the iOS release provisioning profile and " + + "certificate.")); + + h.add(new Hint("ios.shareAppGroup") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.spm.packages") + .annotatedAs(HintGroup.IOS, "spmPackages") + .type(HintType.STRING_LIST) + .separator(";") + .platform("ios") + .consumedBy("IOSDependencyManager", "IPhoneBuilder") + .doc("Swift Package Manager packages to link, one per entry, each written as " + + "identity|url|requirement.")); + + h.add(new Hint("ios.statusBarFG") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.superfastBuild") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.surfaces.appGroup") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.surfaces.deploymentTarget") + .group(HintGroup.IOS) + .type(HintType.VERSION) + .def("16.1") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.surfaces.extension") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.surfaces.frequentUpdates") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.swiftVersion") + .group(HintGroup.IOS) + .type(HintType.VERSION) + .def("5.0") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.teamId") + .annotatedAs(HintGroup.IOS, "teamId") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder", "MacNativeBuilder", "TvNativeBuilder", "WatchNativeBuilder") + .doc("Specifies the team ID associated with the iOS provisioning profile and certificate. Use " + + "`ios.debug.teamId` and `ios.release.teamId` to specify different team IDs for debug and " + + "release builds respectively.")); + + h.add(new Hint("ios.themeMode") + .annotatedAs(HintGroup.IOS, "themeMode") + .values("IosThemeMode", "auto", "modern", "ios7", "legacy") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("`auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the existing iOS 7 " + + "flat theme so pre-refactor screenshot goldens and apps see no behavior change. `modern` " + + "/ `liquid` opts in to the CSS-generated iOS Modern (liquid-glass) theme shipped from " + + "`native-themes/ios-modern/theme.css`. `ios7` / `flat` is the same as `auto` - pre-liquid " + + "iOS 7 flat theme; `legacy` / `iphone` loads the pre-iOS 7 iPhone theme. The `auto` -> " + + "modern flip is planned for a future release.")); + + h.add(new Hint("ios.timeSensitiveNotifications") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.twoDigitVersion") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder", "WatchNativeBuilder")); + + h.add(new Hint("ios.uiscene") + .annotatedAs(HintGroup.IOS, "uiscene") + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("true/false (defaults to true). Enables iOS UIScene lifecycle support. UIScene lets iOS " + + "manage one or more app UI sessions independently, improving lifecycle handling in modern " + + "iOS versions. Apple has indicated UIScene will be required starting with iOS 27, so this " + + "is now on by default; set the flag to `false` only if you need to temporarily fall back " + + "to the legacy `UIApplicationDelegate` lifecycle.")); + + h.add(new Hint("ios.urlScheme") + .annotatedAs(HintGroup.IOS, "urlScheme") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Allows intercepting a URL call using the syntax `urlPrefix`")); + + h.add(new Hint("ios.urlSchemes") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.useAVKit") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Use AVKit for video components on iOS rather than `MPMoviePlayerController` on iOS " + + "versions 8 through 12. iOS 13 will always use AVKit, and iOS 7 and lower will always use " + + "`MPMoviePlayerController`. Default value `false`")); + + h.add(new Hint("ios.useJavascriptCore") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.usePhotoKitForMultigallery") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.usePrintf") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.useWKWebView") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.usesBackgroundProcessing") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.viewDidLoad") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Objective-C code that can be injected into the iOS callback method (message) " + + "`viewDidLoad`")); + + h.add(new Hint("ios.viewDidLoadInclude") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.wallet.appGroup") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("App Group id starting with `group.` shared by the app and the generated Wallet " + + "extensions. The app publishes pass entries into this group through " + + "`com.codename1.payment.WalletExtension` and the group is added to the app and extension " + + "entitlements automatically. Required when `ios.wallet.extension=true`.")); + + h.add(new Hint("ios.wallet.authEndpoint") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("HTTPS URL the generated login UI extension POSTs `{\"username\",\"password\"}` to; the JSON " + + "response's `token` is stored in the App Group for the provisioning request. Required " + + "when `ios.wallet.includeUI=true`.")); + + h.add(new Hint("ios.wallet.extension") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to false. Generates an Apple Wallet issuer provisioning " + + "extension (the \"From apps on your iPhone\" flow in the Wallet app) and embeds it in the " + + "build. Requires `ios.wallet.appGroup` and `ios.wallet.issuerEndpoint`. See the Apple " + + "Wallet Extension chapter.")); + + h.add(new Hint("ios.wallet.includeUI") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("false") + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Boolean true/false defaults to false. Also generates the Wallet authorization UI " + + "extension - a login form shown inside the Wallet app when the app reports that " + + "authentication is required. Requires `ios.wallet.authEndpoint`.")); + + h.add(new Hint("ios.wallet.issuerEndpoint") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("HTTPS URL of the issuer backend endpoint that produces the encrypted provisioning " + + "payload. The generated extension POSTs Apple's certificates/nonce plus the card " + + "identifier and auth token there as JSON. Required when `ios.wallet.extension=true`.")); + + h.add(new Hint("ios.wallet.nonuiExtensionName") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("WalletNonUIExtension") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.wallet.uiExtensionName") + .group(HintGroup.IOS) + .type(HintType.STRING) + .def("WalletUIExtension") + .platform("ios") + .consumedBy("IPhoneBuilder")); + + h.add(new Hint("ios.zbar_flash") + .group(HintGroup.IOS) + .type(HintType.BOOLEAN) + .def("true") + .platform("ios") + .consumedBy("IPhoneBuilder")); + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/HintGroup.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/HintGroup.java new file mode 100644 index 00000000000..d10c5eb294a --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/HintGroup.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +/** + * Which annotation type a hint is exposed through, and which section of the + * documentation and the Settings UI it belongs to. + * + *

Assignment is by name prefix, except that two feature groups deliberately + * claim a subtree across platforms: {@link #ON_DEVICE_DEBUG} takes the + * {@code ios.onDeviceDebug*} and {@code android.onDeviceDebug} hints out of + * their platform groups, and {@link #IOS_PRIVACY} takes the literal + * {@code ios.NS*UsageDescription} keys out of {@link #IOS}.

+ * + *

{@link #NONE} means catalogued but not annotated — the hint is still + * described here for the documentation, the Settings tool and the drift gate, + * but it is set through {@code codenameone_settings.properties}. Every dynamic + * hint family is NONE, because a Java annotation cannot express a map.

+ */ +public enum HintGroup { + IOS("Ios", "ios."), + ANDROID("Android", "android."), + DESKTOP("Desktop", "desktop."), + MAC_NATIVE("MacNative", "macNative."), + WINDOWS("Windows", "windows."), + LINUX("Linux", "linux."), + JAVASCRIPT("JavaScript", "javascript."), + TV_NATIVE("TvNative", "tvNative."), + WATCH_NATIVE("WatchNative", "watchNative."), + HARDENING("Hardening", "harden."), + ON_DEVICE_DEBUG("OnDeviceDebug", null), + IOS_PRIVACY("IosPrivacy", null), + /** Unprefixed and one-off-prefix hints, exposed through {@code @Build}. */ + GENERAL("Build", null), + /** Catalogued but not annotated. */ + NONE(null, null); + + private final String annotationSimpleName; + private final String keyPrefix; + + HintGroup(String annotationSimpleName, String keyPrefix) { + this.annotationSimpleName = annotationSimpleName; + this.keyPrefix = keyPrefix; + } + + /** Simple name of the generated annotation type, or null for {@link #NONE}. */ + public String annotationSimpleName() { + return annotationSimpleName; + } + + /** + * The hint-name prefix this group owns, or null when membership is not + * decided by prefix. + */ + public String keyPrefix() { + return keyPrefix; + } + + /** Whether hints in this group are exposed as annotation attributes. */ + public boolean isAnnotated() { + return annotationSimpleName != null; + } +} diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/HintType.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/HintType.java new file mode 100644 index 00000000000..551a6dea287 --- /dev/null +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/HintType.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +/** + * The kind of value a build hint carries. + * + *

This is the single source of truth for hint typing. Three other + * vocabularies used to describe the same thing and drifted apart from each + * other; they are now derived from this one via + * {@link BuildHints#settingsType(HintType)} and + * {@link BuildHints#editorWidget(HintType)}.

+ */ +public enum HintType { + /** {@code "true"} or {@code "false"}. Maps to a Java {@code boolean}. */ + BOOLEAN, + /** A decimal integer. Maps to a Java {@code int}. */ + INT, + /** Free text on a single line. */ + STRING, + /** Free text that is expected to span lines. Same Java type as STRING. */ + TEXT_BLOCK, + /** A delimited list. Maps to {@code String[]}; requires a separator. */ + STRING_LIST, + /** A closed set of values. Maps to a generated Java enum. */ + ENUM, + /** An XML fragment spliced into a manifest or plist. */ + XML, + /** A filesystem path. */ + PATH, + /** An absolute URL. */ + URL, + /** A dotted version number. */ + VERSION, + /** A credential. Never echoed in logs or diagnostics. */ + SECRET +} diff --git a/maven/build-hint-catalog/src/test/java/com/codename1/build/shared/BuildHintsTest.java b/maven/build-hint-catalog/src/test/java/com/codename1/build/shared/BuildHintsTest.java new file mode 100644 index 00000000000..a1b8040a6bb --- /dev/null +++ b/maven/build-hint-catalog/src/test/java/com/codename1/build/shared/BuildHintsTest.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.build.shared; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Self-consistency of the build hint catalog. + * + *

Everything downstream is generated from this table, so a defect here + * becomes a broken annotation, a wrong manifest entry, or a hint that silently + * does nothing. These checks are the reason the catalog is Java rather than a + * data file.

+ */ +class BuildHintsTest { + + /** + * Annotation members cannot be named after a public method of Object or + * Annotation: JLS 9.6.1 makes that a compile error, and it is not a keyword + * rule so it is easy to miss until the generated source will not build. + */ + private static final Set ILLEGAL_MEMBER_NAMES = new HashSet(Arrays.asList( + "equals", "hashCode", "toString", "annotationType", "clone", "getClass", + "notify", "notifyAll", "wait", "finalize")); + + private static final Set JAVA_KEYWORDS = new HashSet(Arrays.asList( + "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", + "class", "const", "continue", "default", "do", "double", "else", "enum", + "extends", "final", "finally", "float", "for", "goto", "if", "implements", + "import", "instanceof", "int", "interface", "long", "native", "new", + "package", "private", "protected", "public", "return", "short", "static", + "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", + "transient", "try", "void", "volatile", "while", + "true", "false", "null", "_", "var", "record", "yield", "sealed", "permits")); + + private static final Set LEGAL_SEPARATORS = new HashSet(Arrays.asList( + "", ";", ",", "\n", " ")); + + /** + * The separators {@code LibraryHintMerger} defines today. The catalog has to + * agree with all of them before that map can be deleted in favour of + * {@link BuildHints#separatorFor(String)} -- if the two disagree, a cn1lib's + * contribution is spliced onto the project's value with the wrong delimiter + * and the resulting Gradle or plist fragment is malformed. + */ + private static Map libraryHintMergerSeparators() { + Map m = new LinkedHashMap(); + m.put("android.gradleDep", ";"); + m.put("gradleDependencies", "\n"); + m.put("android.topDependency", "\n"); + m.put("android.repositories", "\n"); + m.put("android.xgradle", "\n"); + m.put("android.gradle.androidx", "\n"); + m.put("android.xgradle_default_config", "\n"); + m.put("android.gradlePlugin", "\n"); + m.put("android.supportv4Dep", "\n"); + m.put("android.proguardKeep", "\n"); + m.put("ios.pods", ","); + m.put("ios.applicationQueriesSchemes", ","); + m.put("ios.add_libs", ";"); + m.put("android.xapplication_attr", " "); + return m; + } + + @Test + void theCatalogIsNotEmpty() { + assertTrue(BuildHints.entries().size() > 400, + "expected the mined hint set, got " + BuildHints.entries().size()); + } + + @Test + void namesAreUniqueAndLookupWorksWithOrWithoutThePrefix() { + for (BuildHints.Hint h : BuildHints.entries()) { + assertSame(h, BuildHints.byName(h.name())); + assertSame(h, BuildHints.byName(BuildHints.ARG_PREFIX + h.name())); + } + } + + private static void assertSame(BuildHints.Hint expected, BuildHints.Hint actual) { + if (expected != actual) { + fail("lookup returned a different entry for " + expected.name()); + } + } + + @Test + void aliasesResolveToARealNonAliasHint() { + for (BuildHints.Hint h : BuildHints.entries()) { + if (h.aliasOf() == null) { + continue; + } + BuildHints.Hint target = BuildHints.byName(h.aliasOf()); + assertNotNull(target, h.name() + " aliases unknown hint " + h.aliasOf()); + assertTrue(target.aliasOf() == null, + h.name() + " aliases " + target.name() + ", which is itself an alias"); + assertEquals(target.name(), BuildHints.canonicalName(h.name())); + } + } + + @Test + void everyAnnotationAttributeIsClaimedExactlyOnce() { + Map claimed = new HashMap(); + for (BuildHints.Hint h : BuildHints.entries()) { + if (!h.isAnnotated()) { + continue; + } + String key = h.group().annotationSimpleName() + "#" + h.attr(); + String previous = claimed.put(key, h.name()); + assertTrue(previous == null, + "@" + key + " is claimed by both " + previous + " and " + h.name()); + } + } + + @Test + void annotationAttributeNamesAreLegalJavaMembers() { + for (BuildHints.Hint h : BuildHints.entries()) { + if (!h.isAnnotated()) { + continue; + } + String a = h.attr(); + assertTrue(a.length() > 0, h.name() + " has an empty attribute name"); + assertTrue(Character.isJavaIdentifierStart(a.charAt(0)), + h.name() + " -> '" + a + "' is not a legal identifier start"); + for (int i = 1; i < a.length(); i++) { + assertTrue(Character.isJavaIdentifierPart(a.charAt(i)), + h.name() + " -> '" + a + "' has an illegal identifier character"); + } + assertFalse(JAVA_KEYWORDS.contains(a), + h.name() + " -> '" + a + "' is a Java keyword"); + assertFalse(ILLEGAL_MEMBER_NAMES.contains(a), + h.name() + " -> '" + a + "' is override-equivalent to a method of " + + "Object or Annotation, which JLS 9.6.1 forbids as an " + + "annotation member name"); + } + } + + @Test + void anAnnotatedHintIsNeverDynamicAndNeverAnAlias() { + for (BuildHints.Hint h : BuildHints.entries()) { + if (!h.isAnnotated()) { + continue; + } + assertFalse(h.isDynamic(), + h.name() + " is a dynamic family; a Java annotation cannot express a map"); + assertTrue(h.aliasOf() == null, + h.name() + " is an alias, so annotating it would create two attributes " + + "for one effective setting"); + } + } + + @Test + void declaredDefaultsMatchTheirDeclaredType() { + for (BuildHints.Hint h : BuildHints.entries()) { + String d = h.def(); + if (d == null || d.length() == 0) { + continue; + } + switch (h.type()) { + case BOOLEAN: + assertTrue("true".equals(d) || "false".equals(d), + h.name() + " is BOOLEAN but defaults to '" + d + "'"); + break; + case INT: + try { + Integer.parseInt(d.trim()); + } catch (NumberFormatException e) { + fail(h.name() + " is INT but defaults to '" + d + "'"); + } + break; + case ENUM: + assertTrue(h.values().contains(d), + h.name() + " defaults to '" + d + "', which is outside its domain " + + h.values()); + break; + default: + break; + } + } + } + + @Test + void everyEnumHasAUsableDomain() { + for (BuildHints.Hint h : BuildHints.entries()) { + if (h.type() != HintType.ENUM) { + continue; + } + assertNotNull(h.enumName(), h.name() + " is ENUM with no enum type name"); + assertTrue(h.values().size() >= 2, + h.name() + " is ENUM with fewer than two values: " + h.values()); + for (String v : h.values()) { + assertFalse(v.indexOf(',') >= 0, + h.name() + " value '" + v + "' contains a comma, which the simulator's " + + "Build Hint editor uses to delimit its value list"); + } + if (!h.valueLabels().isEmpty()) { + assertEquals(h.values().size(), h.valueLabels().size(), + h.name() + " has a label list of a different length to its values"); + } + } + } + + /** + * One-way, deliberately. A list needs a delimiter, but a hint can carry a + * delimiter without being a list the user edits as items -- + * {@code android.xapplication_attr} joins XML attributes with a space. + */ + @Test + void everyListHintHasANonEmptySeparator() { + for (BuildHints.Hint h : BuildHints.entries()) { + if (h.type() == HintType.STRING_LIST) { + assertNotNull(h.separator(), h.name() + " is a list with no separator"); + assertFalse(h.separator().isEmpty(), + h.name() + " is a list with an empty separator, so its values would " + + "run together"); + } + if (h.separator() != null) { + assertTrue(LEGAL_SEPARATORS.contains(h.separator()), + h.name() + " uses an unsupported separator " + quote(h.separator())); + } + } + } + + @Test + void theCatalogAgreesWithLibraryHintMergerOnEverySeparatorItDefines() { + for (Map.Entry e : libraryHintMergerSeparators().entrySet()) { + BuildHints.Hint h = BuildHints.byName(e.getKey()); + assertNotNull(h, "LibraryHintMerger defines a separator for " + e.getKey() + + " but the catalog does not describe it"); + assertEquals(e.getValue(), BuildHints.separatorFor(e.getKey()), + "separator mismatch for " + e.getKey() + ": LibraryHintMerger says " + + quote(e.getValue()) + ", catalog says " + + quote(BuildHints.separatorFor(e.getKey()))); + } + } + + @Test + void anUnknownHintFallsBackToBareConcatenation() { + assertEquals("", BuildHints.separatorFor("some.hint.nobody.catalogued")); + assertEquals("", BuildHints.separatorFor(null)); + } + + @Test + void everyDynamicFamilyDeclaresItsPattern() { + int found = 0; + for (BuildHints.Hint h : BuildHints.entries()) { + if (!h.isDynamic()) { + continue; + } + found++; + assertNotNull(h.pattern(), h.name() + " is dynamic with no pattern"); + assertTrue(h.pattern().indexOf('*') >= 0, + h.name() + " is dynamic but its pattern matches only itself"); + } + assertTrue(found > 10, "expected the known dynamic families, found " + found); + } + + @Test + void derivedTypeVocabulariesCoverEveryHintType() { + Set widgets = new HashSet( + Arrays.asList("TextField", "TextArea", "Checkbox", "Select")); + for (HintType t : HintType.values()) { + assertNotNull(BuildHints.settingsType(t)); + assertTrue(widgets.contains(BuildHints.editorWidget(t)), + t + " maps to '" + BuildHints.editorWidget(t) + + "', which the Build Hint editor does not recognise and would " + + "silently render as a plain text field"); + } + } + + @Test + void everyHintNamesTheCodeThatReadsIt() { + for (BuildHints.Hint h : BuildHints.entries()) { + List by = h.consumedBy(); + assertTrue(!by.isEmpty() || h.isExternal(), + h.name() + " names no consumer and is not marked external(); either it is " + + "read somewhere this catalog does not record, or it is dead"); + } + } + + private static String quote(String s) { + if (s == null) { + return "null"; + } + return "'" + s.replace("\n", "\\n") + "'"; + } +} diff --git a/scripts/build_hint_miner.py b/scripts/build_hint_miner.py index 47498c3547f..a5e7509cd8b 100644 --- a/scripts/build_hint_miner.py +++ b/scripts/build_hint_miner.py @@ -67,7 +67,8 @@ def split_args(text, i): if not fn.endswith(".java"): continue path = os.path.join(dirpath, fn) - text = open(path, encoding="utf-8", errors="replace").read() + with open(path, encoding="utf-8", errors="replace") as fh: + text = fh.read() rel = os.path.relpath(path, ROOT) for pat, prefixed in OPENERS: for m in pat.finditer(text): @@ -97,4 +98,5 @@ def split_args(text, i): if out == "-": json.dump(payload, sys.stdout, indent=1) else: - json.dump(payload, open(out, "w"), indent=1) + with open(out, "w", encoding="utf-8") as fh: + json.dump(payload, fh, indent=1) diff --git a/scripts/check-build-hint-catalog.py b/scripts/check-build-hint-catalog.py index eb8c4c787dc..bb68df8799c 100755 --- a/scripts/check-build-hint-catalog.py +++ b/scripts/check-build-hint-catalog.py @@ -25,7 +25,8 @@ def catalog(): for fn in sorted(os.listdir(src)): if not fn.startswith("BuildHints") or not fn.endswith(".java"): continue - text = open(os.path.join(src, fn), encoding="utf-8").read() + with open(os.path.join(src, fn), encoding="utf-8") as fh: + text = fh.read() for m in re.finditer(r'new Hint\("((?:[^"\\]|\\.)*)"\)', text): names.add(m.group(1)) for m in re.finditer(r'\.dynamic\("((?:[^"\\]|\\.)*)"\)', text): @@ -60,7 +61,8 @@ def documented_hints(): continue path = os.path.join(dirpath, fn) try: - text = open(path, encoding="utf-8", errors="replace").read() + with open(path, encoding="utf-8", errors="replace") as fh: + text = fh.read() except OSError: continue for m in re.finditer(r'codename1\.arg\.([A-Za-z][A-Za-z0-9_.]*)', text): @@ -102,10 +104,11 @@ def main(): baseline = set() if os.path.exists(BASELINE): - for line in open(BASELINE): - line = line.strip() - if line and not line.startswith("#"): - baseline.add(line.split("|")[0]) + with open(BASELINE, encoding="utf-8") as fh: + for line in fh: + line = line.strip() + if line and not line.startswith("#"): + baseline.add(line.split("|")[0]) current = {f.split("|")[0]: f for f in findings} added = sorted(set(current) - baseline) diff --git a/scripts/copyright-header-exclusions.txt b/scripts/copyright-header-exclusions.txt index 77a600506a3..0ef1e55292f 100644 --- a/scripts/copyright-header-exclusions.txt +++ b/scripts/copyright-header-exclusions.txt @@ -27,3 +27,4 @@ vm/ByteCodeTranslator/src/cn1_sqlite3.h | SQLite3 Multiple Ciphers public header vm/ByteCodeTranslator/src/cn1_sqlite3_amalgamation.h | SQLite3 Multiple Ciphers amalgamation, upstream MIT notice over public-domain SQLite Ports/JavaScriptPort/src/main/webapp/js/sqlite3mc.js | SQLite3 Multiple Ciphers WebAssembly loader, Emscripten generated, MIT over public-domain SQLite Ports/JavaScriptPort/src/main/webapp/js/sqlite3-opfs-async-proxy.js | SQLite3 Multiple Ciphers OPFS proxy worker, MIT over public-domain SQLite +maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java | Archetype template for the application class of a user's own project, not Codename One source; a GPL header here would be applied to the user's code diff --git a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java index 5fb34da91d0..53f5f3ac844 100644 --- a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java +++ b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java @@ -1,11 +1,11 @@ /* * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. - * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this + * published by the Free Software Foundation. Codename One designates this * particular file as subject to the "Classpath" exception as provided - * by Codename One in the LICENSE file that accompanied this code. + * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or diff --git a/scripts/input-validation-app/common/src/main/java/com/codenameone/inputvalidation/InputValidationApp.java b/scripts/input-validation-app/common/src/main/java/com/codenameone/inputvalidation/InputValidationApp.java index 530fd3634f6..b1a0e065416 100644 --- a/scripts/input-validation-app/common/src/main/java/com/codenameone/inputvalidation/InputValidationApp.java +++ b/scripts/input-validation-app/common/src/main/java/com/codenameone/inputvalidation/InputValidationApp.java @@ -1,6 +1,24 @@ /* - * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. */ package com.codenameone.inputvalidation; diff --git a/scripts/purchase-test-app/app/common/src/main/java/com/codenameone/examples/purchasetest/PurchaseTestApp.java b/scripts/purchase-test-app/app/common/src/main/java/com/codenameone/examples/purchasetest/PurchaseTestApp.java index 08cb15458c1..f4453c9d921 100644 --- a/scripts/purchase-test-app/app/common/src/main/java/com/codenameone/examples/purchasetest/PurchaseTestApp.java +++ b/scripts/purchase-test-app/app/common/src/main/java/com/codenameone/examples/purchasetest/PurchaseTestApp.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codenameone.examples.purchasetest; import com.codename1.payment.Purchase; diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintCatalog.java b/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintCatalog.java index f6191f5d820..4246df1387e 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintCatalog.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintCatalog.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codename1.settings.hints; import com.codename1.build.shared.BuildHints; diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintMetadata.java b/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintMetadata.java index dcb1cfdc2bc..92e4f72af73 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintMetadata.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/hints/BuildHintMetadata.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codename1.settings.hints; import java.util.Collections; diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java b/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java index 1f72dafe25f..6b4ea49bfcf 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codename1.settings.project; public final class ProjectBinding { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 23a7e8f2a39..38673e3f57e 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codename1.settings; import com.codename1.settings.hints.BuildHintCatalog; diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java index 51b615d94c5..df308511628 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codename1.settings; import com.codename1.ui.css.CSSThemeCompiler; diff --git a/tools/build-hint-bootstrap/gen_catalog.py b/tools/build-hint-bootstrap/gen_catalog.py index e1b07755067..d2fb0be54b9 100644 --- a/tools/build-hint-bootstrap/gen_catalog.py +++ b/tools/build-hint-bootstrap/gen_catalog.py @@ -5,9 +5,11 @@ ROOT = "/Users/shai/dev/cn6/CodenameOne" SC = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.join(ROOT, "maven/build-hint-catalog/src/main/java/com/codename1/build/shared") -LICENSE = open(os.path.join(SC, "license.txt")).read() +with open(os.path.join(SC, "license.txt"), encoding="utf-8") as _fh: + LICENSE = _fh.read() -mined = json.load(open(os.path.join(SC, "mined.json"))) +with open(os.path.join(SC, "mined.json"), encoding="utf-8") as _fh: + mined = json.load(_fh) sys.path.insert(0, SC) from curation import CURATED, ENUMS, PRIVACY_PREFIX, DEFAULT_NOTES, DOC_OVERRIDES, TYPE_OVERRIDES @@ -26,7 +28,8 @@ def load_docs(): # This bootstrap is a one-off: the catalog is the source of truth now and is # edited directly. p = os.path.join(SC, "guide_old.asciidoc") - lines = open(p, encoding="utf-8").read().split("\n")[30:736] + with open(p, encoding="utf-8") as fh: + lines = fh.read().split("\n")[30:736] docs, i = {}, 0 while i < len(lines): ln = lines[i] @@ -261,7 +264,8 @@ def wrap(text, indent, width=88): static void register(List h) {{ ''' + "\n\n".join(body) + "\n }\n}\n" - open(os.path.join(OUT, fname + ".java"), "w").write(src) + with open(os.path.join(OUT, fname + ".java"), "w", encoding="utf-8") as fh: + fh.write(src) print("entries per file:", dict(counts), file=sys.stderr) print("total:", sum(counts.values()), file=sys.stderr) diff --git a/tools/build-hint-bootstrap/gen_external.py b/tools/build-hint-bootstrap/gen_external.py index 979fe6de47e..1e6c7fa6cfb 100644 --- a/tools/build-hint-bootstrap/gen_external.py +++ b/tools/build-hint-bootstrap/gen_external.py @@ -10,9 +10,11 @@ ROOT = "/Users/shai/dev/cn6/CodenameOne" OUT = os.path.join(ROOT, "maven/build-hint-catalog/src/main/java/com/codename1/build/shared") -LICENSE = open(os.path.join(SC, "license.txt")).read() +with open(os.path.join(SC, "license.txt"), encoding="utf-8") as _fh: + LICENSE = _fh.read() -mined = set(json.load(open(SC + "/mined.json"))) +with open(SC + "/mined.json", encoding="utf-8") as _fh: + mined = set(json.load(_fh)) PLACEHOLDER = re.compile(r'PERMISSION_NAME|[A-Z_]{4,}$|[<>]') names = sorted(k for k in G.DOCS @@ -62,5 +64,6 @@ static void register(List h) { ''' + "\n\n".join(body) + "\n }\n}\n" -open(os.path.join(OUT, "BuildHintsExternal.java"), "w").write(src) +with open(os.path.join(OUT, "BuildHintsExternal.java"), "w", encoding="utf-8") as _fh: + _fh.write(src) print("external entries:", len(names), file=sys.stderr) From d727c7d976dbd24d885d93e613a7f4cee7132aba Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 22 Aug 2026 21:46:49 +0300 Subject: [PATCH 003/115] Stop the bootstrap doing its work at import time The archived bootstrap ran generation at module scope, so gen_external.py's `import gen_catalog` -- which only wants three helper functions -- rewrote every catalog source as a side effect. Generation and its diagnostics now live in `main()` behind a `__main__` guard, and the module-level file reads became `load_license()` / `load_mined()` / `load_docs()`, so importing does no I/O and cannot fail on inputs the archived copy deliberately does not carry. Verified both directions: importing leaves the catalog untouched, and running the two scripts end to end still reproduces the committed catalog byte for byte. Also drops `json` and `subprocess` from check-build-hint-catalog.py. Both were left from an earlier version that shelled out to the miner instead of importing it. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/check-build-hint-catalog.py | 2 +- tools/build-hint-bootstrap/gen_catalog.py | 146 +++++++++++---------- tools/build-hint-bootstrap/gen_external.py | 8 +- 3 files changed, 85 insertions(+), 71 deletions(-) diff --git a/scripts/check-build-hint-catalog.py b/scripts/check-build-hint-catalog.py index bb68df8799c..14e9506e235 100755 --- a/scripts/check-build-hint-catalog.py +++ b/scripts/check-build-hint-catalog.py @@ -10,7 +10,7 @@ Held against a baseline rather than failing outright: a large tail of hints predates the catalog. The point is that *new* code cannot add another one. """ -import fnmatch, json, os, re, subprocess, sys +import fnmatch, os, re, sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.join(ROOT, "scripts")) diff --git a/tools/build-hint-bootstrap/gen_catalog.py b/tools/build-hint-bootstrap/gen_catalog.py index d2fb0be54b9..27c97219313 100644 --- a/tools/build-hint-bootstrap/gen_catalog.py +++ b/tools/build-hint-bootstrap/gen_catalog.py @@ -5,11 +5,13 @@ ROOT = "/Users/shai/dev/cn6/CodenameOne" SC = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.join(ROOT, "maven/build-hint-catalog/src/main/java/com/codename1/build/shared") -with open(os.path.join(SC, "license.txt"), encoding="utf-8") as _fh: - LICENSE = _fh.read() +def load_license(): + with open(os.path.join(SC, "license.txt"), encoding="utf-8") as fh: + return fh.read() -with open(os.path.join(SC, "mined.json"), encoding="utf-8") as _fh: - mined = json.load(_fh) +def load_mined(): + with open(os.path.join(SC, "mined.json"), encoding="utf-8") as fh: + return json.load(fh) sys.path.insert(0, SC) from curation import CURATED, ENUMS, PRIVACY_PREFIX, DEFAULT_NOTES, DOC_OVERRIDES, TYPE_OVERRIDES @@ -48,8 +50,6 @@ def load_docs(): i += 1 return docs -DOCS = load_docs() -print(f"doc rows parsed: {len(DOCS)}", file=sys.stderr) def clean_doc(t): t = re.sub(r'<<[^,>]*,\s*([^>]*)>>', r'\1', t) # <> -> text @@ -184,62 +184,72 @@ def wrap(text, indent, width=88): "BuildHintsGeneral": "Hints with no platform prefix, plus hardening and on-device debugging.", } -# A mined key ending in a dot is the constant half of a concatenation -- -# getArg("android.permission." + name) -- not a hint anyone can set. Cataloguing -# it would put a phantom row in the guide and a phantom entry in the Settings -# tool. Each one is covered by a dynamic family instead. -mined = {k: v for k, v in mined.items() if not k.endswith(".")} - -counts = collections.Counter() -for fname, pred in FILES.items(): - names = sorted(n for n in mined if pred(n)) - counts[fname] = len(names) - body = [] - for n in names: - defaults = [d for d, _, _ in mined[n]] - sites = sorted({os.path.basename(f)[:-5] for _, f, _ in mined[n]}) - doc = clean_doc(DOCS.get(n, "")) - if not doc and n in DOC_OVERRIDES: - doc = DOC_OVERRIDES[n] - if n in DEFAULT_NOTES: - if doc and not doc.rstrip().endswith((".", "!", "?")): - doc = doc.rstrip() + "." - doc = (doc + " " + DEFAULT_NOTES[n]).strip() - htype, lit, sep = infer(n, defaults, doc) - parts = [' h.add(new Hint("%s")' % jesc(n)] - g = group_of(n) - cur = CURATED.get(n) - enum_name = None - if g == "IOS_PRIVACY": - parts.append(' .annotatedAs(HintGroup.IOS_PRIVACY, "%s")' % privacy_attr(n)) - htype = "STRING" - elif cur: - cg, attr, enum_name, forced_type, forced_def = cur - parts.append(' .annotatedAs(HintGroup.%s, "%s")' % (cg, attr)) - if forced_type: - htype = forced_type - if forced_def is not None: - lit = forced_def - else: - parts.append(' .group(HintGroup.%s)' % g) - if enum_name: - vals = ", ".join('"%s"' % v for v in ENUMS[enum_name]) - parts.append(' .values("%s", %s)' % (enum_name, vals)) - if lit is not None and lit not in ENUMS[enum_name]: - lit = None - else: - parts.append(' .type(HintType.%s)' % htype) - if lit is not None and lit != "": - parts.append(' .def("%s")' % jesc(lit)) - if sep is not None: - parts.append(' .separator("%s")' % jesc(sep)) - parts.append(' .platform("%s")' % platform_of(n)) - parts.append(' .consumedBy(%s)' % ", ".join('"%s"' % s for s in sites)) - if doc: - parts.append(' .doc(%s)' % wrap(doc, 24)) - body.append("\n".join(parts) + ");") - - src = LICENSE + f'''package com.codename1.build.shared; +def main(): + """Emit the BuildHints* registration classes. + + Guarded rather than run at import: gen_external.py imports this module for + load_docs/clean_doc/infer, and without the guard that import would rewrite + every catalog source as a side effect. + """ + LICENSE = load_license() + DOCS = load_docs() + print(f"doc rows parsed: {len(DOCS)}", file=sys.stderr) + # A mined key ending in a dot is the constant half of a concatenation -- + # getArg("android.permission." + name) -- not a hint anyone can set. + # Cataloguing it would put a phantom row in the guide and a phantom entry in + # the Settings tool. Each one is covered by a dynamic family instead. + mined = {k: v for k, v in load_mined().items() if not k.endswith(".")} + + counts = collections.Counter() + for fname, pred in FILES.items(): + names = sorted(n for n in mined if pred(n)) + counts[fname] = len(names) + body = [] + for n in names: + defaults = [d for d, _, _ in mined[n]] + sites = sorted({os.path.basename(f)[:-5] for _, f, _ in mined[n]}) + doc = clean_doc(DOCS.get(n, "")) + if not doc and n in DOC_OVERRIDES: + doc = DOC_OVERRIDES[n] + if n in DEFAULT_NOTES: + if doc and not doc.rstrip().endswith((".", "!", "?")): + doc = doc.rstrip() + "." + doc = (doc + " " + DEFAULT_NOTES[n]).strip() + htype, lit, sep = infer(n, defaults, doc) + parts = [' h.add(new Hint("%s")' % jesc(n)] + g = group_of(n) + cur = CURATED.get(n) + enum_name = None + if g == "IOS_PRIVACY": + parts.append(' .annotatedAs(HintGroup.IOS_PRIVACY, "%s")' % privacy_attr(n)) + htype = "STRING" + elif cur: + cg, attr, enum_name, forced_type, forced_def = cur + parts.append(' .annotatedAs(HintGroup.%s, "%s")' % (cg, attr)) + if forced_type: + htype = forced_type + if forced_def is not None: + lit = forced_def + else: + parts.append(' .group(HintGroup.%s)' % g) + if enum_name: + vals = ", ".join('"%s"' % v for v in ENUMS[enum_name]) + parts.append(' .values("%s", %s)' % (enum_name, vals)) + if lit is not None and lit not in ENUMS[enum_name]: + lit = None + else: + parts.append(' .type(HintType.%s)' % htype) + if lit is not None and lit != "": + parts.append(' .def("%s")' % jesc(lit)) + if sep is not None: + parts.append(' .separator("%s")' % jesc(sep)) + parts.append(' .platform("%s")' % platform_of(n)) + parts.append(' .consumedBy(%s)' % ", ".join('"%s"' % s for s in sites)) + if doc: + parts.append(' .doc(%s)' % wrap(doc, 24)) + body.append("\n".join(parts) + ");") + + src = LICENSE + f'''package com.codename1.build.shared; import com.codename1.build.shared.BuildHints.Hint; @@ -264,8 +274,12 @@ def wrap(text, indent, width=88): static void register(List h) {{ ''' + "\n\n".join(body) + "\n }\n}\n" - with open(os.path.join(OUT, fname + ".java"), "w", encoding="utf-8") as fh: - fh.write(src) + with open(os.path.join(OUT, fname + ".java"), "w", encoding="utf-8") as fh: + fh.write(src) + + print("entries per file:", dict(counts), file=sys.stderr) + print("total:", sum(counts.values()), file=sys.stderr) + -print("entries per file:", dict(counts), file=sys.stderr) -print("total:", sum(counts.values()), file=sys.stderr) +if __name__ == "__main__": + main() diff --git a/tools/build-hint-bootstrap/gen_external.py b/tools/build-hint-bootstrap/gen_external.py index 1e6c7fa6cfb..6794244504d 100644 --- a/tools/build-hint-bootstrap/gen_external.py +++ b/tools/build-hint-bootstrap/gen_external.py @@ -10,21 +10,21 @@ ROOT = "/Users/shai/dev/cn6/CodenameOne" OUT = os.path.join(ROOT, "maven/build-hint-catalog/src/main/java/com/codename1/build/shared") -with open(os.path.join(SC, "license.txt"), encoding="utf-8") as _fh: - LICENSE = _fh.read() +LICENSE = G.load_license() with open(SC + "/mined.json", encoding="utf-8") as _fh: mined = set(json.load(_fh)) +DOCS = G.load_docs() PLACEHOLDER = re.compile(r'PERMISSION_NAME|[A-Z_]{4,}$|[<>]') -names = sorted(k for k in G.DOCS +names = sorted(k for k in DOCS if k not in mined and not PLACEHOLDER.search(k) and "." in k or (k not in mined and not PLACEHOLDER.search(k) and k.islower())) names = sorted(set(n for n in names if not PLACEHOLDER.search(n))) body = [] for n in names: - doc = G.clean_doc(G.DOCS[n]) + doc = G.clean_doc(DOCS[n]) htype, lit, sep = G.infer(n, [], doc) parts = [' h.add(new Hint("%s")' % G.jesc(n)] parts.append(' .group(HintGroup.%s)' % G.group_of(n)) From 96bff9038a0f71f6d557f7012844d6df9ed68338 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 22 Aug 2026 23:06:32 +0300 Subject: [PATCH 004/115] Make the generated docs and sources survive the ASCII and prose gates Three separate gates rejected generated output. Each is fixed in the generator so the class of problem cannot come back through a catalog edit. Unmappable characters. The prose is imported from the developer guide, which uses typographic punctuation, and `CodenameOne/src` is also compiled by an Ant javac step with ASCII encoding where a single em dash is `error: unmappable character for encoding ASCII` -- a build failure, not a warning. A Unicode escape would not have helped: javac expands `\uXXXX` before it strips comments, so the character reappears. `toAscii` now folds the punctuation that actually occurs, and *refuses* anything it has no mapping for rather than dropping it, because silently deleting a character from a hint's documentation is the worse outcome. Broken table. `ios.spm.packages` is documented as `identity|url|requirement`, and a bare `|` starts a new AsciiDoc cell, so asciidoctor reported "dropping cells from incomplete row" for the whole 529-row table. Cells are escaped now. Vale. The guide enforces the Microsoft style as errors, and the generated table feeds it, so the catalog's prose has to satisfy it too: contractions, no "and so on", no stray adverbs. A default value is not prose, though -- the one remaining hit was `android.file_paths`, whose default is an XML fragment -- so a quoted default now carries the `// vale-skip:` comment .vale.ini documents for individual false positives. Also fixes a data bug the guide exposed. The miner preserved Java escape sequences instead of decoding them, so `android.file_paths` and `android.facebook_permissions` recorded defaults containing literal backslashes that the build never sees, and those reached the rendered table. The miner decodes escapes and re-quotes safely, and the two catalog entries are corrected. Co-Authored-By: Claude Opus 5 (1M context) --- .../annotations/buildhints/Hardening.java | 6 +- .../annotations/buildhints/OnDeviceDebug.java | 4 +- .../impl/javase/BuildHintCatalogDefaults.java | 8 +- .../Advanced-Topics-Under-The-Hood.asciidoc | 2 +- .../_generated-build-hints.adoc | 16 ++-- .../build/shared/BuildHintCodeGenerator.java | 94 +++++++++++++++++-- .../build/shared/BuildHintsAndroid.java | 4 +- .../build/shared/BuildHintsDynamic.java | 7 +- .../build/shared/BuildHintsGeneral.java | 8 +- scripts/build_hint_miner.py | 23 ++++- tools/build-hint-bootstrap/gen_catalog.py | 4 +- 11 files changed, 139 insertions(+), 37 deletions(-) diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/Hardening.java b/CodenameOne/src/com/codename1/annotations/buildhints/Hardening.java index c9190a591fd..657ef4e737a 100644 --- a/CodenameOne/src/com/codename1/annotations/buildhints/Hardening.java +++ b/CodenameOne/src/com/codename1/annotations/buildhints/Hardening.java @@ -43,21 +43,21 @@ /// Permits a local or source build to run with hardening requested but not /// applied. Without it such a build is refused, so a hardened app is never - /// shipped from a target that cannot actually harden it. + /// shipped from a target that can't actually harden it. boolean allowUnhardenedLocalBuild() default false; /// Overrides control-flow obfuscation independently of harden.level. HardenControlFlow controlFlow() default HardenControlFlow.OFF; /// Keep rules in ProGuard syntax, one per line, for classes that are resolved - /// by name at runtime and so cannot be found by the automatic analysis. Same + /// by name at runtime and so can't be found by the automatic analysis. Same /// syntax as android.proguardKeep, so existing rules port directly. Rules are /// separated by newlines only, because a semicolon is legal inside a rule body /// such as { *; }. String keep() default ""; /// Master switch for app hardening: off, standard, aggressive or paranoid. An - /// unrecognized value fails the build rather than being quietly treated as off. + /// unrecognized value fails the build rather than being treated as off. HardenLevel level() default HardenLevel.OFF; /// Overrides symbol renaming independently of harden.level. diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java b/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java index ef5fca7085a..21c7a20e283 100644 --- a/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java +++ b/CodenameOne/src/com/codename1/annotations/buildhints/OnDeviceDebug.java @@ -48,8 +48,8 @@ /// release-signed APK that's `debuggable="true"`. Pair with the /// `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run /// configs) to install, launch, forward JDWP, and stream logcat through adb. - /// Has no effect on builds that don't carry it — release builds are unaffected. - /// See the On-Device Debugging (Android) chapter for the full flow. + /// Has no effect on builds that don't carry it -- release builds are + /// unaffected. See the On-Device Debugging (Android) chapter for the full flow. boolean android() default false; /// Boolean true/false defaults to false. When `true`, the iOS build links a diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java index 477dbc68ce8..8a8014201c3 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java @@ -244,7 +244,7 @@ static void register() { set("{{@OnDeviceDebug}}.label", "On-Device Debugging"); set("{{#OnDeviceDebug#android.onDeviceDebug}}.label", "Android"); set("{{#OnDeviceDebug#android.onDeviceDebug}}.type", "Checkbox"); - set("{{#OnDeviceDebug#android.onDeviceDebug}}.description", "Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable=\"true\"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable=\"true\"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it — release builds are unaffected. See the On-Device Debugging (Android) chapter for the full flow."); + set("{{#OnDeviceDebug#android.onDeviceDebug}}.description", "Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable=\"true\"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable=\"true\"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it -- release builds are unaffected. See the On-Device Debugging (Android) chapter for the full flow."); set("{{#OnDeviceDebug#ios.onDeviceDebug}}.label", "Ios"); set("{{#OnDeviceDebug#ios.onDeviceDebug}}.type", "Checkbox"); set("{{#OnDeviceDebug#ios.onDeviceDebug}}.description", "Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits source-line and locals metadata so a desktop proxy can serve the running app to any JDWP-speaking debugger. Has no effect on release builds. See the On-Device Debugging (iOS) chapter for the full flow."); @@ -276,18 +276,18 @@ static void register() { set("{{@Hardening}}.label", "App Hardening"); set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.label", "Allow unhardened local build"); set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.type", "Checkbox"); - set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.description", "Permits a local or source build to run with hardening requested but not applied. Without it such a build is refused, so a hardened app is never shipped from a target that cannot actually harden it."); + set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.description", "Permits a local or source build to run with hardening requested but not applied. Without it such a build is refused, so a hardened app is never shipped from a target that can't actually harden it."); set("{{#Hardening#harden.controlFlow}}.label", "Control flow"); set("{{#Hardening#harden.controlFlow}}.type", "Select"); set("{{#Hardening#harden.controlFlow}}.values", "off,on"); set("{{#Hardening#harden.controlFlow}}.description", "Overrides control-flow obfuscation independently of harden.level."); set("{{#Hardening#harden.keep}}.label", "Keep"); set("{{#Hardening#harden.keep}}.type", "TextArea"); - set("{{#Hardening#harden.keep}}.description", "Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at runtime and so cannot be found by the automatic analysis. Same syntax as android.proguardKeep, so existing rules port directly. Rules are separated by newlines only, because a semicolon is legal inside a rule body such as { *; }."); + set("{{#Hardening#harden.keep}}.description", "Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at runtime and so can't be found by the automatic analysis. Same syntax as android.proguardKeep, so existing rules port directly. Rules are separated by newlines only, because a semicolon is legal inside a rule body such as { *; }."); set("{{#Hardening#harden.level}}.label", "Level"); set("{{#Hardening#harden.level}}.type", "Select"); set("{{#Hardening#harden.level}}.values", "off,standard,aggressive,paranoid"); - set("{{#Hardening#harden.level}}.description", "Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized value fails the build rather than being quietly treated as off."); + set("{{#Hardening#harden.level}}.description", "Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized value fails the build rather than being treated as off."); set("{{#Hardening#harden.rename}}.label", "Rename"); set("{{#Hardening#harden.rename}}.type", "Checkbox"); set("{{#Hardening#harden.rename}}.description", "Overrides symbol renaming independently of harden.level."); diff --git a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc index fb3dd6f1703..ead34ed89a4 100644 --- a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc +++ b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc @@ -32,7 +32,7 @@ Most of the commonly used hints also have a compiler-checked form: an annotation in `com.codename1.annotations.buildhints` that you put on the application's main class. Written that way a misspelled name is an unknown symbol and an unsupported value is an unknown enum constant, instead of a properties line that -is accepted, never read, and silently does nothing. The Annotation column below +is accepted, never read, and has no effect. The Annotation column below names that form where one exists. Setting the same hint both ways fails the build. diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc index 38586afdbfd..2d540549aad 100644 --- a/docs/developer-guide/_generated-build-hints.adoc +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -278,7 +278,7 @@ |string |_(none)_ |_(properties file only)_ -|Numbered custom layout resources: android.cusom_layout1, 2, and so on. The misspelling is load-bearing -- it is the key the builder actually reads, so correcting it would silently drop the layout. +|Numbered custom layout resources: android.cusom_layout1, android.cusom_layout2 and upward. The misspelling is load-bearing: it's the key the builder actually reads, so correcting it drops the layout with no warning. |android.cusom_layout1 |string @@ -366,13 +366,15 @@ |android.facebook_permissions |string -|`\"public_profile\",\"email\",\"user_friends\"` +// vale-skip: Microsoft.Quotes: this is a literal default value, not prose -- the quotes belong to the value. +|`"public_profile","email","user_friends"` |_(none)_ |Permissions for Facebook used in the Android build target, applicable only if Facebook native integration is used. |android.file_paths |string -|` ` +// vale-skip: Microsoft.Quotes: this is a literal default value, not prose -- the quotes belong to the value. +|` ` |_(none)_ | @@ -1454,7 +1456,7 @@ |boolean |`false` |`@Hardening(allowUnhardenedLocalBuild)` -|Permits a local or source build to run with hardening requested but not applied. Without it such a build is refused, so a hardened app is never shipped from a target that cannot actually harden it. +|Permits a local or source build to run with hardening requested but not applied. Without it such a build is refused, so a hardened app is never shipped from a target that can't actually harden it. |harden.controlFlow |`off`, `on` @@ -1472,13 +1474,13 @@ |text_block |_(none)_ |`@Hardening(keep)` -|Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at runtime and so cannot be found by the automatic analysis. Same syntax as android.proguardKeep, so existing rules port directly. Rules are separated by newlines only, because a semicolon is legal inside a rule body such as { *; }. +|Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at runtime and so can't be found by the automatic analysis. Same syntax as android.proguardKeep, so existing rules port directly. Rules are separated by newlines only, because a semicolon is legal inside a rule body such as { *; }. |harden.level |`off`, `standard`, `aggressive`, `paranoid` |`off` |`@Hardening(level)` -|Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized value fails the build rather than being quietly treated as off. +|Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized value fails the build rather than being treated as off. |harden.mac.enabled |boolean @@ -2486,7 +2488,7 @@ |list (`;` delimited) |_(none)_ |`@Ios(spmPackages)` -|Swift Package Manager packages to link, one per entry, each written as identity|url|requirement. +|Swift Package Manager packages to link, one per entry, each written as identity\|url\|requirement. |ios.spm.products.* |string diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java index 9b130800487..60864ab4ef8 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java @@ -494,7 +494,7 @@ private static String simulatorSchemaSource(Map for (Map.Entry> e : byGroup.entrySet()) { String group = e.getKey().annotationSimpleName(); sb.append("\n set(\"{{@").append(group).append("}}.label\", ") - .append(quote(groupLabel(e.getKey()))).append(");\n"); + .append(quote(toAscii(groupLabel(e.getKey())))).append(");\n"); for (BuildHints.Hint h : e.getValue()) { String key = "{{#" + group + "#" + h.name() + "}}"; sb.append(" set(\"").append(key).append(".label\", ") @@ -514,7 +514,7 @@ private static String simulatorSchemaSource(Map } if (h.doc() != null && h.doc().length() > 0) { sb.append(" set(\"").append(key).append(".description\", ") - .append(quote(h.doc())).append(");\n"); + .append(quote(toAscii(h.doc()))).append(");\n"); } } } @@ -558,10 +558,19 @@ public int compare(BuildHints.Hint a, BuildHints.Hint b) { for (BuildHints.Hint h : all) { // Dynamic families are listed too: their names are patterns rather than // keys, but they are real settings a reader needs to find. - sb.append('|').append(h.name()).append('\n'); - sb.append('|').append(adocType(h)).append('\n'); + sb.append('|').append(cell(h.name())).append('\n'); + sb.append('|').append(cell(adocType(h))).append('\n'); + // A default is a literal value, not prose. One containing a quote -- + // android.file_paths defaults to an XML fragment -- trips Vale's + // Microsoft.Quotes rule, which the developer guide enforces as an + // error. Protect just that line using the mechanism .vale.ini + // documents for individual false positives. + if (h.def() != null && h.def().indexOf('"') >= 0) { + sb.append("// vale-skip: Microsoft.Quotes: this is a literal default value, ") + .append("not prose -- the quotes belong to the value.\n"); + } sb.append('|').append(h.def() == null || h.def().length() == 0 - ? "_(none)_" : "`" + h.def() + "`").append('\n'); + ? "_(none)_" : "`" + cell(h.def()) + "`").append('\n'); sb.append('|').append(h.isAnnotated() ? "`@" + h.group().annotationSimpleName() + "(" + h.attr() + ")`" : (h.isDynamic() ? "_(properties file only)_" : "_(none)_")).append('\n'); @@ -572,12 +581,25 @@ public int compare(BuildHints.Hint a, BuildHints.Hint b) { + "repository, so there is no in-repo reference for it." : ""; } - sb.append('|').append(doc).append("\n\n"); + sb.append('|').append(cell(doc)).append("\n\n"); } sb.append("|===\n"); return sb.toString(); } + /** + * Escapes a value for an AsciiDoc table cell. + * + *

A bare {@code |} starts a new cell, so a hint whose documentation + * contains one -- {@code ios.spm.packages} is written + * {@code identity|url|requirement} -- silently shifts every following column + * and asciidoctor reports "dropping cells from incomplete row" for the whole + * table.

+ */ + private static String cell(String text) { + return text == null ? "" : text.replace("|", "\\|"); + } + private static String adocType(BuildHints.Hint h) { if (h.type() == HintType.ENUM) { StringBuilder sb = new StringBuilder(); @@ -698,9 +720,56 @@ private static String markdownType(BuildHints.Hint h) { return javaType(h); } + /** + * Folds text to ASCII. + * + *

The prose is imported from the developer guide, which uses typographic + * punctuation. {@code CodenameOne/src} is also compiled by an Ant javac step + * with ASCII encoding, where a single em dash is + * {@code error: unmappable character for encoding ASCII} -- a build failure, + * not a warning. A Unicode escape would not help: javac processes + * {@code \\uXXXX} before it strips comments, so the character would simply + * reappear.

+ */ + static String toAscii(String text) { + if (text == null) { + return null; + } + StringBuilder sb = new StringBuilder(text.length()); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + switch (c) { + case '\u2014': sb.append("--"); break; // em dash + case '\u2013': sb.append('-'); break; // en dash + case '\u2018': + case '\u2019': sb.append('\''); break; // curly single quotes + case '\u201c': + case '\u201d': sb.append('"'); break; // curly double quotes + case '\u2026': sb.append("..."); break; // ellipsis + case '\u2192': sb.append("->"); break; // right arrow + case '\u00d7': sb.append('x'); break; // multiplication sign + case '\u00a0': sb.append(' '); break; // non-breaking space + default: + if (c < 0x80) { + sb.append(c); + break; + } + // Refuse rather than drop it. Silently deleting a character + // from a hint's documentation is a worse outcome than telling + // whoever edited the catalog to add a mapping here. + throw new IllegalArgumentException("Build hint documentation contains '" + + c + "' (U+" + Integer.toHexString(c).toUpperCase() + + "), which has no ASCII equivalent in toAscii(). The Ant javac step " + + "compiles CodenameOne/src as ASCII and rejects it as unmappable. " + + "Add a mapping, or reword the text."); + } + } + return sb.toString(); + } + /** Wraps text as /// markdown doc comment lines. */ private static String doc(String text, String indent) { - String clean = text.replace("@since", "since").replaceAll("\\s+", " ").trim(); + String clean = toAscii(text).replace("@since", "since").replaceAll("\\s+", " ").trim(); StringBuilder sb = new StringBuilder(); StringBuilder line = new StringBuilder(); for (String word : clean.split(" ")) { @@ -732,6 +801,17 @@ private static String esc(String s) { } private static void write(File f, String content) throws IOException { + if (f.getName().endsWith(".java")) { + for (int i = 0; i < content.length(); i++) { + if (content.charAt(i) >= 0x80) { + throw new IOException(f.getName() + " would contain the non-ASCII character '" + + content.charAt(i) + "' (U+" + + Integer.toHexString(content.charAt(i)).toUpperCase() + + "), which the Ant javac step rejects as unmappable. Add it to " + + "toAscii()."); + } + } + } File parent = f.getParentFile(); if (parent != null && !parent.isDirectory() && !parent.mkdirs()) { throw new IOException("Could not create " + parent); diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java index 240be102a79..ccf306ecffc 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java @@ -483,7 +483,7 @@ static void register(List h) { h.add(new Hint("android.facebook_permissions") .group(HintGroup.ANDROID) .type(HintType.STRING) - .def("\\\"public_profile\\\",\\\"email\\\",\\\"user_friends\\\"") + .def("\"public_profile\",\"email\",\"user_friends\"") .platform("android") .consumedBy("AndroidGradleBuilder") .doc("Permissions for Facebook used in the Android build target, applicable only if Facebook " @@ -492,7 +492,7 @@ static void register(List h) { h.add(new Hint("android.file_paths") .group(HintGroup.ANDROID) .type(HintType.STRING) - .def(" ") + .def(" ") .platform("android") .consumedBy("AndroidGradleBuilder")); diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDynamic.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDynamic.java index f82955cf1a4..9f3a99ee178 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDynamic.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDynamic.java @@ -63,9 +63,10 @@ static void register(List h) { "Opts a single Google Play service in or out. The sibling " + ".minPlayServicesVersion pins its version."); family(h, "android.cusom_layout*", "android", "AndroidGradleBuilder", - "Numbered custom layout resources: android.cusom_layout1, 2, and so on. " - + "The misspelling is load-bearing -- it is the key the builder " - + "actually reads, so correcting it would silently drop the layout."); + "Numbered custom layout resources: android.cusom_layout1, android.cusom_layout2 " + + "and upward. The misspelling is load-bearing: it's the key the " + + "builder actually reads, so correcting it drops the layout with " + + "no warning."); family(h, "ios.NS*UsageDescription", "ios", "IPhoneBuilder", "Info.plist privacy strings. The commonly used keys are catalogued " + "individually and exposed through @IosPrivacy; this entry covers the " diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java index d02748e7855..d1d5a46a4e5 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java @@ -206,8 +206,8 @@ static void register(List h) { .platform("general") .consumedBy("CN1BuildMojo") .doc("Permits a local or source build to run with hardening requested but not applied. Without " - + "it such a build is refused, so a hardened app is never shipped from a target that cannot " - + "actually harden it.")); + + "it such a build is refused, so a hardened app is never shipped from a target that " + + "can't actually harden it.")); h.add(new Hint("harden.controlFlow") .annotatedAs(HintGroup.HARDENING, "controlFlow") @@ -229,7 +229,7 @@ static void register(List h) { .platform("general") .consumedBy("AndroidGradleBuilder") .doc("Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at " - + "runtime and so cannot be found by the automatic analysis. Same syntax as " + + "runtime and so can't be found by the automatic analysis. Same syntax as " + "android.proguardKeep, so existing rules port directly. Rules are separated by newlines " + "only, because a semicolon is legal inside a rule body such as { *; }.")); @@ -240,7 +240,7 @@ static void register(List h) { .platform("general") .consumedBy("AndroidGradleBuilder", "CN1BuildMojo", "Executor") .doc("Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized " - + "value fails the build rather than being quietly treated as off.")); + + "value fails the build rather than being treated as off.")); h.add(new Hint("harden.mac.enabled") .group(HintGroup.HARDENING) diff --git a/scripts/build_hint_miner.py b/scripts/build_hint_miner.py index a5e7509cd8b..3f4699dac82 100644 --- a/scripts/build_hint_miner.py +++ b/scripts/build_hint_miner.py @@ -22,13 +22,28 @@ (re.compile(r'\bgetProperty\(\s*"codename1\.arg\.'), True), ] +_ESCAPES = {'n': '\n', 't': '\t', 'r': '\r', 'b': '\b', 'f': '\f', + '"': '"', "'": "'", '\\': '\\'} + + def read_literal(text, i): - """Read a Java string literal body starting just after the opening quote.""" + """Read a Java string literal starting just after the opening quote. + + Escape sequences are decoded, not preserved. Keeping them verbatim recorded + android.file_paths' default as `` -- + backslashes the build never sees -- which then reached the developer guide. + """ out = [] while i < len(text): c = text[i] if c == '\\': - out.append(text[i:i+2]); i += 2; continue + nxt = text[i + 1] if i + 1 < len(text) else '' + if nxt == 'u': + try: + out.append(chr(int(text[i + 2:i + 6], 16))); i += 6; continue + except ValueError: + pass + out.append(_ESCAPES.get(nxt, nxt)); i += 2; continue if c == '"': return "".join(out), i + 1 out.append(c); i += 1 @@ -41,7 +56,9 @@ def split_args(text, i): c = text[i] if c == '"': lit, j = read_literal(text, i + 1) - cur.append('"' + (lit or "") + '"'); i = j; continue + # Re-quote with proper escaping so a decoded inner quote does not + # break the literal-default check below. + cur.append(json.dumps(lit or "", ensure_ascii=False)); i = j; continue if c == "'": j = i + 1 while j < len(text) and text[j] != "'": diff --git a/tools/build-hint-bootstrap/gen_catalog.py b/tools/build-hint-bootstrap/gen_catalog.py index 27c97219313..9015d1fbe11 100644 --- a/tools/build-hint-bootstrap/gen_catalog.py +++ b/tools/build-hint-bootstrap/gen_catalog.py @@ -105,7 +105,9 @@ def infer(name, defaults, doc): lit = None for x in d: if x.startswith('"') and x.endswith('"'): - lit = x[1:-1]; break + # json.loads, not a slice: the miner re-quotes with real escaping, so + # a naive strip would leave backslashes the build never sees. + lit = json.loads(x); break if x in ("true", "false") or re.fullmatch(r'-?\d+', x): lit = x; break dl = doc.lower() From a343fe33358d88f8ee639121d3efba136031fc5a Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 22 Aug 2026 23:54:32 +0300 Subject: [PATCH 005/115] Give ThreadSafeDatabaseTest headroom under the FormTest timeout `killedThreadReportsItselfFinished` failed the Java 21 leg with "FormTest timed out after 5000ms; edt=initialized pendingSerialCalls=0". The waits in this class used a 5000ms deadline, which is exactly the `@FormTest` timeout in EDTTestInterceptor -- so on a loaded runner the poll loop consumed the entire harness budget and the interceptor fired first. The report then said only that the method timed out, with nothing about which condition never became true. The waits now use 2000ms, well inside the harness budget and still roughly two thousand times the ~1ms these threads actually take to stop. A genuine regression now fails on the test's own assertion, which names what went wrong. Pre-existing (the test arrived with #5526) and unrelated to the build hint work: core-unittests has no dependency on the JavaSE port, so none of the simulator registration in this branch runs there, this branch changes nothing under com.codename1.db or EasyThread, and the Java 8 leg passed the same commit. Co-Authored-By: Claude Opus 5 (1M context) --- .../codename1/db/ThreadSafeDatabaseTest.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/maven/core-unittests/src/test/java/com/codename1/db/ThreadSafeDatabaseTest.java b/maven/core-unittests/src/test/java/com/codename1/db/ThreadSafeDatabaseTest.java index 162675b7636..ae786cb60b5 100644 --- a/maven/core-unittests/src/test/java/com/codename1/db/ThreadSafeDatabaseTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/db/ThreadSafeDatabaseTest.java @@ -30,6 +30,19 @@ public class ThreadSafeDatabaseTest extends UITestBase { + /** + * How long a test waits for a worker thread to stop. + * + *

Deliberately below the 5000ms {@code @FormTest} timeout in + * {@link com.codename1.junit.EDTTestInterceptor}. These waits used to use + * 5000ms as well, so a slow runner spent the whole harness budget inside the + * poll loop and the interceptor fired first: the report was + * "FormTest timed out after 5000ms" with nothing about which condition never + * became true. With headroom the test fails on its own assertion instead, + * which names the thing that went wrong.

+ */ + private static final long WORKER_STOP_TIMEOUT_MILLIS = 2000; + @FormTest public void testDelegation() throws Exception { Database db = TestCodenameOneImplementation.getInstance().openOrCreateDB("test_threadsafe.db"); @@ -91,7 +104,7 @@ public void run() { } } }); - long deadline = System.currentTimeMillis() + 5000; + long deadline = System.currentTimeMillis() + WORKER_STOP_TIMEOUT_MILLIS; while (!tsDb.getThread().isFinished() && System.currentTimeMillis() < deadline) { Thread.sleep(10); } @@ -118,7 +131,7 @@ public void killedThreadReportsItselfFinished() throws Exception { com.codename1.util.EasyThread et = com.codename1.util.EasyThread.start("test-kill-flag"); Assertions.assertFalse(et.isFinished(), "a running thread is not finished"); et.kill(); - long deadline = System.currentTimeMillis() + 5000; + long deadline = System.currentTimeMillis() + WORKER_STOP_TIMEOUT_MILLIS; while (!et.isFinished() && System.currentTimeMillis() < deadline) { Thread.sleep(10); } @@ -150,7 +163,7 @@ public void closingAfterTheWorkerHasGoneStaysQuiet() throws Exception { .openOrCreateDB("test_threadsafe_close_after_worker.db"); ThreadSafeDatabase tsDb = new ThreadSafeDatabase(db); tsDb.getThread().killWhenIdle(); - long deadline = System.currentTimeMillis() + 5000; + long deadline = System.currentTimeMillis() + WORKER_STOP_TIMEOUT_MILLIS; while (!tsDb.getThread().isFinished() && System.currentTimeMillis() < deadline) { Thread.sleep(10); } @@ -202,7 +215,7 @@ public void run() { }); synchronized (started) { while (!started[0]) { - started.wait(5000); + started.wait(WORKER_STOP_TIMEOUT_MILLIS); } } From 0edef42ca46a387f5a6830e439f74180b3cd5df1 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 00:52:08 +0300 Subject: [PATCH 006/115] Refuse to migrate a project that never runs process-annotations A mojo's defaultPhase does not bind it to a project -- the project's POM has to -- and nothing turns a build hint annotation back into a codename1.arg.* pair except the process-annotations goal. So migrating a project without that binding deleted working properties and replaced them with annotations no goal ever reads: the hints vanished from the build with no diagnostic anywhere. Five projects in this branch were already in that state. gamebuilder, docs/demos, video-builder and cn1playground bind the plugin but not that goal, so the binding is added. input-validation-app's common module has no build section at all, so its migration is reverted rather than inventing a lifecycle for a demo app. The goal now checks the reactor for the binding and refuses with the execution block to paste, so this cannot happen to anyone else. Three more from the same review: - The deletion pass recognized only `key=value`. `Properties.load` also accepts `key:value`, `key value`, escaped separators inside the key, and logical continuation lines; a declaration it failed to match was left behind while the annotation was added, so the next build failed with the duplicate-hint error this goal exists to prevent. Keys are parsed the way Properties.load defines them now, with a unit test per form. - The settings file was read as ISO-8859-1 and written back as UTF-8, turning any unrelated non-ASCII byte -- an accented displayName, say -- into mojibake. It is written back as ISO-8859-1. - cn1.androidTheme and cn1.nativeTheme are deprecated aliases of and.themeMode and nativeTheme, which the builders honour as fallbacks. Neither declared aliasOf, so conflict detection missed them and one value silently won. Also: the generation script rebuilt the generator only when its class was absent, so editing a catalog source and rerunning regenerated every view from the previous build's bytecode -- reporting success while ignoring the edit, and passing --check on a tree that was genuinely stale. It always rebuilds now. Co-Authored-By: Claude Opus 5 (1M context) --- docs/demos/common/pom.xml | 1 + .../_generated-build-hints.adoc | 4 +- .../build/shared/BuildHintsGeneral.java | 14 +- .../maven/MigrateBuildHintsMojo.java | 161 ++++++++++++++++-- .../MigrateBuildHintsPropertyParsingTest.java | 92 ++++++++++ .../BuildHintAnnotationProcessorTest.java | 13 ++ scripts/build_hint_miner.py | 4 + scripts/cn1playground/common/pom.xml | 1 + scripts/gamebuilder/common/pom.xml | 1 + scripts/gen-build-hint-annotations.sh | 10 +- .../common/codenameone_settings.properties | 3 + .../inputvalidation/InputValidationApp.java | 23 +-- scripts/video-builder/common/pom.xml | 8 +- 13 files changed, 290 insertions(+), 45 deletions(-) create mode 100644 maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java diff --git a/docs/demos/common/pom.xml b/docs/demos/common/pom.xml index 40104bae6ec..3f1b32b5884 100644 --- a/docs/demos/common/pom.xml +++ b/docs/demos/common/pom.xml @@ -350,6 +350,7 @@ compliance-check css + process-annotations diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc index 2d540549aad..d23082316d4 100644 --- a/docs/developer-guide/_generated-build-hints.adoc +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -1324,7 +1324,7 @@ |string |_(none)_ |_(none)_ -| +|Deprecated alias for and.themeMode (AndroidGradleBuilder.java:4097). Both names configure one setting, so declaring this alongside @Android(themeMode) is a conflict. |cn1.buildKey |string @@ -1372,7 +1372,7 @@ |string |_(none)_ |_(none)_ -| +|Deprecated alias for nativeTheme (AndroidGradleBuilder.java:4099, IPhoneBuilder.java:947). Both names configure one setting, so declaring this alongside @Build(nativeTheme) is a conflict. |codename1.mac.appid |string diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java index d1d5a46a4e5..c85cf6117a0 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java @@ -91,10 +91,15 @@ static void register(List h) { .consumedBy("CN1BuildMojo")); h.add(new Hint("cn1.androidTheme") + .aliasOf("and.themeMode") + .deprecated("Use and.themeMode, or @Android(themeMode = ...).") .group(HintGroup.GENERAL) .type(HintType.STRING) .platform("general") - .consumedBy("AndroidGradleBuilder")); + .consumedBy("AndroidGradleBuilder") + .doc("Deprecated alias for and.themeMode (AndroidGradleBuilder.java:4097). " + + "Both names configure one setting, so declaring this alongside " + + "@Android(themeMode) is a conflict.")); h.add(new Hint("cn1.buildKey") .group(HintGroup.GENERAL) @@ -142,10 +147,15 @@ static void register(List h) { .consumedBy("Executor")); h.add(new Hint("cn1.nativeTheme") + .aliasOf("nativeTheme") + .deprecated("Use nativeTheme, or @Build(nativeTheme = ...).") .group(HintGroup.GENERAL) .type(HintType.STRING) .platform("general") - .consumedBy("AndroidGradleBuilder", "IPhoneBuilder")); + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder") + .doc("Deprecated alias for nativeTheme (AndroidGradleBuilder.java:4099, " + + "IPhoneBuilder.java:947). Both names configure one setting, so " + + "declaring this alongside @Build(nativeTheme) is a conflict.")); h.add(new Hint("db.legacy") .group(HintGroup.GENERAL) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index a9f7ab5280e..6ecc9fc579a 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -99,6 +99,25 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException throw new MojoExecutionException("No codenameone_settings.properties in " + projectDir); } + // Nothing turns an annotation back into a build hint except the + // process-annotations goal, and a mojo's defaultPhase does not bind it to + // a project -- the project's own POM has to. Migrating without that + // binding deletes working properties and replaces them with annotations no + // goal ever reads, so the hints disappear from the build silently. + if (!processAnnotationsIsBound()) { + throw new MojoFailureException("This project does not run the cn1 process-annotations " + + "goal, so build hint annotations would never be turned back into the " + + "codename1.arg.* pairs the builders read, and migrating would silently drop " + + "them.\n\nAdd it to the common module's POM first:\n" + + " \n" + + " cn1-process-classes\n" + + " process-classes\n" + + " \n" + + " process-annotations\n" + + " \n" + + " "); + } + // The annotations ship in codenameone-core. A project pinned to a release // that predates them would migrate cleanly here and then fail to compile, // so refuse rather than hand back a broken project. @@ -211,6 +230,38 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException + new File(mainSource).getName()); } + /** + * Whether any module in the reactor binds the {@code process-annotations} + * goal. + * + *

Checked across the reactor rather than on {@code project} because this + * goal is an aggregator, so {@code project} is the root POM while the binding + * lives in the common module.

+ */ + private boolean processAnnotationsIsBound() { + java.util.List projects = reactorProjects; + if (projects == null || projects.isEmpty()) { + projects = java.util.Collections.singletonList(project); + } + for (org.apache.maven.project.MavenProject p : projects) { + java.util.List plugins = p.getBuildPlugins(); + if (plugins == null) { + continue; + } + for (org.apache.maven.model.Plugin plugin : plugins) { + if (!"codenameone-maven-plugin".equals(plugin.getArtifactId())) { + continue; + } + for (org.apache.maven.model.PluginExecution e : plugin.getExecutions()) { + if (e.getGoals() != null && e.getGoals().contains("process-annotations")) { + return true; + } + } + } + } + return false; + } + /** * Whether the codenameone-core on this project's compile classpath actually * carries the annotations. @@ -445,10 +496,27 @@ static int classDeclarationIndex(String text, boolean kotlin, String simpleName) * Deletes the migrated lines, leaving every other line -- comments, * ordering, unrelated settings -- byte for byte as it was. */ + /** + * Deletes the migrated declarations, leaving every other line -- comments, + * ordering, unrelated settings -- byte for byte as it was. + * + *

Keys are recognised the way {@code Properties.load} defines them, not + * just {@code key=value}: {@code key:value} and {@code key value} are equally + * valid, and a line ending in an odd number of backslashes continues onto the + * next. A declaration this pass fails to recognise is left behind while the + * annotation is added, and the very next build fails with the duplicate-hint + * error this goal exists to avoid.

+ * + *

Written back as ISO-8859-1 because that is what {@code Properties.load} + * reads a {@code .properties} stream as. Rewriting the file as UTF-8 would + * turn any non-ASCII byte elsewhere in it -- an accented + * {@code codename1.displayName}, say -- into mojibake, even though it has + * nothing to do with the hint being migrated.

+ */ private void removeMigratedLines(File settingsFile, List keys) throws IOException { List lines = new ArrayList(); BufferedReader r = new BufferedReader( - new InputStreamReader(new FileInputStream(settingsFile), "ISO-8859-1")); + new InputStreamReader(new FileInputStream(settingsFile), PROPERTIES_ENCODING)); try { String line; while ((line = r.readLine()) != null) { @@ -457,27 +525,92 @@ private void removeMigratedLines(File settingsFile, List keys) throws IO } finally { r.close(); } + Map wanted = new LinkedHashMap(); for (String k : keys) { wanted.put(k, Boolean.TRUE); } + StringBuilder out = new StringBuilder(); - for (String line : lines) { - String t = line.trim(); - boolean drop = false; - if (t.length() > 0 && t.charAt(0) != '#' && t.charAt(0) != '!') { - int eq = t.indexOf('='); - int colon = t.indexOf(':'); - int split = eq < 0 ? colon : (colon < 0 ? eq : Math.min(eq, colon)); - if (split > 0 && wanted.containsKey(t.substring(0, split).trim())) { - drop = true; - } + for (int i = 0; i < lines.size(); i++) { + // Gather the whole logical line: continuations belong to the same + // declaration and have to go with it. + int last = i; + StringBuilder logical = new StringBuilder(lines.get(i)); + while (continues(lines.get(last)) && last + 1 < lines.size()) { + last++; + logical.append(lines.get(last).replaceFirst("^\\s+", "")); } - if (!drop) { - out.append(line).append('\n'); + String key = propertyKeyOf(logical.toString()); + if (key != null && wanted.containsKey(key)) { + i = last; + continue; } + for (int j = i; j <= last; j++) { + out.append(lines.get(j)).append('\n'); + } + i = last; + } + writeProperties(settingsFile, out.toString()); + } + + /** Whether a physical line ends in an odd number of backslashes. */ + private static boolean continues(String line) { + int backslashes = 0; + for (int i = line.length() - 1; i >= 0 && line.charAt(i) == '\\'; i--) { + backslashes++; + } + return backslashes % 2 == 1; + } + + /** + * The key a logical properties line declares, or null when the line is blank + * or a comment. + * + *

Follows {@code java.util.Properties}: the key runs to the first + * unescaped {@code =}, {@code :} or whitespace.

+ */ + static String propertyKeyOf(String logicalLine) { + int i = 0; + while (i < logicalLine.length() && isPropertySpace(logicalLine.charAt(i))) { + i++; + } + if (i >= logicalLine.length()) { + return null; + } + char first = logicalLine.charAt(i); + if (first == '#' || first == '!') { + return null; + } + StringBuilder key = new StringBuilder(); + for (; i < logicalLine.length(); i++) { + char c = logicalLine.charAt(i); + if (c == '\\' && i + 1 < logicalLine.length()) { + key.append(logicalLine.charAt(++i)); + continue; + } + if (c == '=' || c == ':' || isPropertySpace(c)) { + break; + } + key.append(c); + } + return key.length() == 0 ? null : key.toString(); + } + + private static boolean isPropertySpace(char c) { + return c == ' ' || c == '\t' || c == '\f'; + } + + /** The encoding {@code Properties.load(InputStream)} reads. */ + private static final String PROPERTIES_ENCODING = "ISO-8859-1"; + + private static void writeProperties(File f, String content) throws IOException { + Writer w = new OutputStreamWriter(new FileOutputStream(f), PROPERTIES_ENCODING); + try { + w.write(content); + } finally { + w.close(); } - write(settingsFile, out.toString()); } private static String read(File f) throws IOException { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java new file mode 100644 index 00000000000..136d6faae4f --- /dev/null +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/// Covers the properties parsing in `MigrateBuildHintsMojo`. +/// +/// The migration deletes a hint's declaration and replaces it with an +/// annotation. A declaration the deletion pass fails to recognise is left +/// behind while the annotation is added, and the very next build fails with the +/// duplicate-hint error the goal exists to prevent -- so the parser has to +/// accept every form `java.util.Properties` does, not just `key=value`. +public class MigrateBuildHintsPropertyParsingTest { + + @Test + public void equalsSeparatorIsRecognized() { + assertEquals("codename1.arg.ios.teamId", + MigrateBuildHintsMojo.propertyKeyOf("codename1.arg.ios.teamId=ABCDE")); + } + + @Test + public void colonSeparatorIsRecognized() { + assertEquals("codename1.arg.ios.teamId", + MigrateBuildHintsMojo.propertyKeyOf("codename1.arg.ios.teamId:ABCDE")); + } + + /// The form that used to survive the deletion pass and break the next build. + @Test + public void whitespaceSeparatorIsRecognized() { + assertEquals("codename1.arg.ios.teamId", + MigrateBuildHintsMojo.propertyKeyOf("codename1.arg.ios.teamId ABCDE")); + assertEquals("codename1.arg.ios.teamId", + MigrateBuildHintsMojo.propertyKeyOf("codename1.arg.ios.teamId\tABCDE")); + } + + @Test + public void leadingWhitespaceIsIgnored() { + assertEquals("codename1.arg.ios.teamId", + MigrateBuildHintsMojo.propertyKeyOf(" codename1.arg.ios.teamId = ABCDE")); + } + + @Test + public void spacingAroundTheSeparatorIsIgnored() { + assertEquals("codename1.arg.ios.teamId", + MigrateBuildHintsMojo.propertyKeyOf("codename1.arg.ios.teamId = ABCDE")); + } + + /// A key may escape the characters that would otherwise end it. + @Test + public void escapedSeparatorsStayPartOfTheKey() { + assertEquals("a=b", MigrateBuildHintsMojo.propertyKeyOf("a\\=b=value")); + assertEquals("a b", MigrateBuildHintsMojo.propertyKeyOf("a\\ b=value")); + assertEquals("a:b", MigrateBuildHintsMojo.propertyKeyOf("a\\:b=value")); + } + + @Test + public void commentsAndBlanksDeclareNothing() { + assertNull(MigrateBuildHintsMojo.propertyKeyOf("# codename1.arg.ios.teamId=ABCDE")); + assertNull(MigrateBuildHintsMojo.propertyKeyOf("! codename1.arg.ios.teamId=ABCDE")); + assertNull(MigrateBuildHintsMojo.propertyKeyOf("")); + assertNull(MigrateBuildHintsMojo.propertyKeyOf(" ")); + } + + @Test + public void aValueOnlyLineHasNoSeparator() { + assertEquals("bare", MigrateBuildHintsMojo.propertyKeyOf("bare")); + } +} diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index b9b8e0ba559..8ea6d65479e 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -235,6 +235,19 @@ public void aHintOnlyInThePropertiesFileIsFine() throws Exception { assertFalse(ctx.hasErrors()); } + /// A hint's deprecated alias names the same setting, so declaring the alias + /// in the properties file collides with the annotation just as the canonical + /// name would. Without this one value silently wins: AndroidGradleBuilder + /// reads `and.themeMode` and falls back to `cn1.androidTheme`. + @Test + public void aDeprecatedAliasOfAnAnnotatedHintIsAConflict() throws Exception { + Properties s = settings(); + s.setProperty("codename1.arg.cn1.androidTheme", "legacy"); + File classes = compile("@Android(themeMode = AndroidThemeMode.MODERN)"); + ProcessorContext ctx = run(classes, s, MAIN, false); + assertErrorContaining(ctx, "codename1.arg.cn1.androidTheme is declared twice"); + } + /// A commented-out line is not a declaration, and the archetype ships /// several. Properties.load skips them, so this is really a guard against /// anyone reintroducing a hand-rolled line scan. diff --git a/scripts/build_hint_miner.py b/scripts/build_hint_miner.py index 3f4699dac82..647a308e670 100644 --- a/scripts/build_hint_miner.py +++ b/scripts/build_hint_miner.py @@ -42,6 +42,10 @@ def read_literal(text, i): try: out.append(chr(int(text[i + 2:i + 6], 16))); i += 6; continue except ValueError: + # Not a well-formed \uXXXX after all. Fall through and treat + # the backslash as a plain escape rather than guessing at a + # code point; a malformed escape in a builder source is not + # this script's problem to diagnose. pass out.append(_ESCAPES.get(nxt, nxt)); i += 2; continue if c == '"': diff --git a/scripts/cn1playground/common/pom.xml b/scripts/cn1playground/common/pom.xml index d4ca37ab375..aa34a1fdd46 100644 --- a/scripts/cn1playground/common/pom.xml +++ b/scripts/cn1playground/common/pom.xml @@ -403,6 +403,7 @@ bytecode-compliance css + process-annotations diff --git a/scripts/gamebuilder/common/pom.xml b/scripts/gamebuilder/common/pom.xml index a07d212aad8..d22977aef1d 100644 --- a/scripts/gamebuilder/common/pom.xml +++ b/scripts/gamebuilder/common/pom.xml @@ -115,6 +115,7 @@ bytecode-compliance css + process-annotations diff --git a/scripts/gen-build-hint-annotations.sh b/scripts/gen-build-hint-annotations.sh index 27fb5ebb814..0ba5d0e386f 100755 --- a/scripts/gen-build-hint-annotations.sh +++ b/scripts/gen-build-hint-annotations.sh @@ -25,10 +25,12 @@ CATALOG_SRC="$CATALOG/src/main/java" check=0 [ "${1:-}" = "--check" ] && check=1 -if [ ! -f "$CLASSES/com/codename1/build/shared/BuildHintCodeGenerator.class" ]; then - echo "gen-build-hint-annotations: building the catalog" >&2 - (cd "$REPO_ROOT/maven" && mvn -q -B -pl build-hint-catalog package -DskipTests) -fi +# Always rebuild. Skipping when the class merely exists meant that editing a +# BuildHints*.java source and rerunning this script regenerated every view from +# the previous build's bytecode -- reporting success while silently ignoring the +# edit, and in --check mode passing a tree that is genuinely out of date. +echo "gen-build-hint-annotations: building the catalog" >&2 +(cd "$REPO_ROOT/maven" && mvn -q -B -pl build-hint-catalog package -DskipTests) SKILL_REF="$REPO_ROOT/scripts/initializr/common/src/main/resources/skill/references/build-hints.md" JAVASE_SRC="$REPO_ROOT/Ports/JavaSE/src" diff --git a/scripts/input-validation-app/common/codenameone_settings.properties b/scripts/input-validation-app/common/codenameone_settings.properties index 8726b661aa6..4973a52e4a0 100644 --- a/scripts/input-validation-app/common/codenameone_settings.properties +++ b/scripts/input-validation-app/common/codenameone_settings.properties @@ -1,6 +1,9 @@ codename1.android.keystore= codename1.android.keystoreAlias= codename1.android.keystorePassword= +codename1.arg.android.useAndroidX=true +codename1.arg.ios.newStorageLocation=true +codename1.arg.ios.uiscene=true codename1.arg.java.version=17 codename1.cssTheme=false codename1.displayName=CN1InputValidation diff --git a/scripts/input-validation-app/common/src/main/java/com/codenameone/inputvalidation/InputValidationApp.java b/scripts/input-validation-app/common/src/main/java/com/codenameone/inputvalidation/InputValidationApp.java index b1a0e065416..f52515a4f2f 100644 --- a/scripts/input-validation-app/common/src/main/java/com/codenameone/inputvalidation/InputValidationApp.java +++ b/scripts/input-validation-app/common/src/main/java/com/codenameone/inputvalidation/InputValidationApp.java @@ -1,38 +1,17 @@ /* - * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. */ package com.codenameone.inputvalidation; import com.codename1.system.Lifecycle; import com.codenameone.inputvalidation.gestures.GestureSuite; -import com.codename1.annotations.buildhints.*; /// Lifecycle entry point for the input-validation CN1 app. The whole app does /// one thing: it runs `GestureSuite` once and exits. No theme, no resources, /// no asset bundle -- by design, so a regression in input handling can never /// hide behind a missing texture, a slow startup, or a stale screenshot /// baseline. -@Android(useAndroidX = true) -@Ios(newStorageLocation = true, uiscene = true) public class InputValidationApp extends Lifecycle { @Override public void runApp() { diff --git a/scripts/video-builder/common/pom.xml b/scripts/video-builder/common/pom.xml index 5a9f0e5d5c8..54c1ef39659 100644 --- a/scripts/video-builder/common/pom.xml +++ b/scripts/video-builder/common/pom.xml @@ -38,7 +38,13 @@ compile-css process-classes - css + + css + + process-annotations + From 2803847f1dae4064dde12bb1283ccc0608f9921e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 02:15:20 +0300 Subject: [PATCH 007/115] Do not migrate the guide's snippet project, and keep Settings from duplicating a hint docs/demos is the developer guide's snippet project: deliberately incomplete code fragments that illustrate @Entity, @Route, @AppIntent and @Mapped. Binding process-annotations there put those snippets in front of the other processors, which correctly rejected six of them, so the migration is reverted and its two hints are back in the properties file. That the project omitted the goal was the point, not an oversight. The other three newly bound projects were checked rather than assumed: gamebuilder, video-builder and cn1playground each run process-annotations cleanly and emit 6, 3 and 5 hints respectively. Settings could still create the duplicate the migration is careful to avoid. In a generated project ios.themeMode and its neighbours are annotations, but the Build Hints UI decides a hint is inactive from the properties file alone and its Add button writes a property -- producing a second declaration that fails the next build. The tool now reads META-INF/codenameone/build-hints.properties, the file the processor writes on every build and deletes when the last annotation goes, and renders those hints read-only with the attribute that owns them: "Set by @Ios(themeMode) on the main class." An unbuilt project has no such file and behaves as before. Also fixes the SpotBugs finding this branch introduced: `backslashes % 2 == 1` in the continuation scan is false for negative odd numbers, so it is `!= 0`. The count cannot go negative, but the idiom is wrong regardless of that. Co-Authored-By: Claude Opus 5 (1M context) --- .../common/codenameone_settings.properties | 1 + docs/demos/common/pom.xml | 1 - .../codenameone/developerguide/DemoCode.java | 24 - .../maven/MigrateBuildHintsMojo.java | 2 +- .../main/java/bsh/cn1/GeneratedCN1Access.java | 1437 ++++++--- ...neratedAccess_com_codename1_ai_vision.java | 2756 ++++++++++++++++- ...ratedAccess_com_codename1_annotations.java | 254 +- ..._com_codename1_annotations_buildhints.java | 1153 +++++++ .../GeneratedAccess_com_codename1_crash.java | 6 + .../gen/GeneratedAccess_com_codename1_db.java | 12 + .../GeneratedAccess_com_codename1_home.java | 2561 +++++++++++++++ ...cess_com_codename1_home_commissioning.java | 665 ++++ ...eneratedAccess_com_codename1_home_spi.java | 580 ++++ ...GeneratedAccess_com_codename1_intents.java | 1168 +++++++ ...ratedAccess_com_codename1_intents_spi.java | 446 +++ ...eneratedAccess_com_codename1_security.java | 95 +- ...cess_com_codename1_security_hardening.java | 394 +++ ...dAccess_com_codename1_security_shield.java | 1 + ...eneratedAccess_com_codename1_surfaces.java | 6 + .../gen/GeneratedAccess_com_codename1_ui.java | 153 + ...neratedAccess_com_codename1_ui_editor.java | 66 + .../GeneratedAccess_com_codename1_util.java | 15 + ...eneratedAccess_com_codename1_wearable.java | 95 +- .../bsh/cn1/gen/GeneratedAccess_java_io.java | 4 + .../settings/CodenameOneSettings.java | 72 +- .../settings/BuildHintCatalogTest.java | 21 + 26 files changed, 11306 insertions(+), 682 deletions(-) create mode 100644 scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_annotations_buildhints.java create mode 100644 scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home.java create mode 100644 scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home_commissioning.java create mode 100644 scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home_spi.java create mode 100644 scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_intents.java create mode 100644 scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_intents_spi.java create mode 100644 scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security_hardening.java diff --git a/docs/demos/common/codenameone_settings.properties b/docs/demos/common/codenameone_settings.properties index f096134cbe8..8494fded9dd 100644 --- a/docs/demos/common/codenameone_settings.properties +++ b/docs/demos/common/codenameone_settings.properties @@ -1,6 +1,7 @@ codename1.android.keystore= codename1.android.keystoreAlias= codename1.android.keystorePassword= +codename1.arg.ios.newStorageLocation=true codename1.arg.java.version=17 codename1.displayName=DemoCode codename1.icon=icon.png diff --git a/docs/demos/common/pom.xml b/docs/demos/common/pom.xml index 3f1b32b5884..40104bae6ec 100644 --- a/docs/demos/common/pom.xml +++ b/docs/demos/common/pom.xml @@ -350,7 +350,6 @@ compliance-check css - process-annotations diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java index 02cb53f3755..759bde784ab 100644 --- a/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/DemoCode.java @@ -1,34 +1,10 @@ -/* - * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ package com.codenameone.developerguide; import com.codename1.system.Lifecycle; -import com.codename1.annotations.buildhints.*; /** * Application entry point that launches the demo browser. */ -@Ios(newStorageLocation = true) public class DemoCode extends Lifecycle { @Override public void runApp() { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 6ecc9fc579a..e1ea5a01430 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -560,7 +560,7 @@ private static boolean continues(String line) { for (int i = line.length() - 1; i >= 0 && line.charAt(i) == '\\'; i--) { backslashes++; } - return backslashes % 2 == 1; + return backslashes % 2 != 0; } /** diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/GeneratedCN1Access.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/GeneratedCN1Access.java index b201e255758..754b57a9faa 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/GeneratedCN1Access.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/GeneratedCN1Access.java @@ -35,6 +35,7 @@ import bsh.cn1.gen.GeneratedAccess_com_codename1_ai_vision; import bsh.cn1.gen.GeneratedAccess_com_codename1_analytics; import bsh.cn1.gen.GeneratedAccess_com_codename1_annotations; +import bsh.cn1.gen.GeneratedAccess_com_codename1_annotations_buildhints; import bsh.cn1.gen.GeneratedAccess_com_codename1_annotations_graphql; import bsh.cn1.gen.GeneratedAccess_com_codename1_annotations_grpc; import bsh.cn1.gen.GeneratedAccess_com_codename1_annotations_rest; @@ -88,6 +89,11 @@ import bsh.cn1.gen.GeneratedAccess_com_codename1_health_nutrition; import bsh.cn1.gen.GeneratedAccess_com_codename1_health_sensors; import bsh.cn1.gen.GeneratedAccess_com_codename1_health_workout; +import bsh.cn1.gen.GeneratedAccess_com_codename1_home; +import bsh.cn1.gen.GeneratedAccess_com_codename1_home_commissioning; +import bsh.cn1.gen.GeneratedAccess_com_codename1_home_spi; +import bsh.cn1.gen.GeneratedAccess_com_codename1_intents; +import bsh.cn1.gen.GeneratedAccess_com_codename1_intents_spi; import bsh.cn1.gen.GeneratedAccess_com_codename1_io; import bsh.cn1.gen.GeneratedAccess_com_codename1_io_bonjour; import bsh.cn1.gen.GeneratedAccess_com_codename1_io_graphql; @@ -125,6 +131,7 @@ import bsh.cn1.gen.GeneratedAccess_com_codename1_push; import bsh.cn1.gen.GeneratedAccess_com_codename1_router; import bsh.cn1.gen.GeneratedAccess_com_codename1_security; +import bsh.cn1.gen.GeneratedAccess_com_codename1_security_hardening; import bsh.cn1.gen.GeneratedAccess_com_codename1_security_shield; import bsh.cn1.gen.GeneratedAccess_com_codename1_security_shield_spi; import bsh.cn1.gen.GeneratedAccess_com_codename1_sensors; @@ -264,24 +271,31 @@ public final class GeneratedCN1Access implements CN1Access { "com.codename1.ai.language.Translator", "com.codename1.ai.language.Translator.Session", "com.codename1.ai.vision.Barcode", + "com.codename1.ai.vision.BarcodeFormat", "com.codename1.ai.vision.BarcodeScanner", + "com.codename1.ai.vision.CodeScanner", + "com.codename1.ai.vision.CodeScannerOptions", "com.codename1.ai.vision.DocumentScanResult", "com.codename1.ai.vision.DocumentScanner", "com.codename1.ai.vision.Face", "com.codename1.ai.vision.FaceDetector", + "com.codename1.ai.vision.FaceLandmarks", "com.codename1.ai.vision.ImageLabel", "com.codename1.ai.vision.ImageLabeler", "com.codename1.ai.vision.Pose", "com.codename1.ai.vision.Pose.Landmark", "com.codename1.ai.vision.PoseDetector", + "com.codename1.ai.vision.PoseLandmarks", "com.codename1.ai.vision.SegmentationMask", "com.codename1.ai.vision.SelfieSegmenter", "com.codename1.ai.vision.TextRecognitionResult", "com.codename1.ai.vision.TextRecognitionResult.TextBlock", "com.codename1.ai.vision.TextRecognizer", + "com.codename1.ai.vision.TextScript", "com.codename1.ai.vision.VisionAnalyzer", "com.codename1.ai.vision.VisionBackend", "com.codename1.ai.vision.VisionBackends", + "com.codename1.ai.vision.VisionCameraView", "com.codename1.ai.vision.VisionException", "com.codename1.ai.vision.VisionFeature", "com.codename1.ai.vision.VisionImage", @@ -311,6 +325,7 @@ public final class GeneratedCN1Access implements CN1Access { "com.codename1.analytics.LegacyAnalyticsProviderAdapter", "com.codename1.analytics.LoggingAnalyticsProvider", "com.codename1.analytics.MatomoAnalyticsProvider", + "com.codename1.annotations.AppIntent", "com.codename1.annotations.Async", "com.codename1.annotations.Async.Execute", "com.codename1.annotations.Async.Schedule", @@ -323,9 +338,17 @@ public final class GeneratedCN1Access implements CN1Access { "com.codename1.annotations.DisableNullChecksAndArrayBoundsChecks", "com.codename1.annotations.Email", "com.codename1.annotations.Entity", + "com.codename1.annotations.EntityId", + "com.codename1.annotations.EntityImage", + "com.codename1.annotations.EntityQuery", + "com.codename1.annotations.EntityQuery.Kind", + "com.codename1.annotations.EntitySubtitle", + "com.codename1.annotations.EntityTitle", "com.codename1.annotations.ExistIn", "com.codename1.annotations.Fused", "com.codename1.annotations.Id", + "com.codename1.annotations.IntentEntity", + "com.codename1.annotations.IntentParam", "com.codename1.annotations.JsonIgnore", "com.codename1.annotations.JsonProperty", "com.codename1.annotations.Length", @@ -342,6 +365,23 @@ public final class GeneratedCN1Access implements CN1Access { "com.codename1.annotations.XmlElement", "com.codename1.annotations.XmlRoot", "com.codename1.annotations.XmlTransient", + "com.codename1.annotations.buildhints.Android", + "com.codename1.annotations.buildhints.AndroidThemeMode", + "com.codename1.annotations.buildhints.Build", + "com.codename1.annotations.buildhints.Desktop", + "com.codename1.annotations.buildhints.DesktopTitleBar", + "com.codename1.annotations.buildhints.HardenControlFlow", + "com.codename1.annotations.buildhints.HardenLevel", + "com.codename1.annotations.buildhints.HardenStrings", + "com.codename1.annotations.buildhints.Hardening", + "com.codename1.annotations.buildhints.InstallLocation", + "com.codename1.annotations.buildhints.Ios", + "com.codename1.annotations.buildhints.IosDependencyManager", + "com.codename1.annotations.buildhints.IosPrivacy", + "com.codename1.annotations.buildhints.IosProjectType", + "com.codename1.annotations.buildhints.IosThemeMode", + "com.codename1.annotations.buildhints.NativeThemeMode", + "com.codename1.annotations.buildhints.OnDeviceDebug", "com.codename1.annotations.graphql.GraphQLClient", "com.codename1.annotations.graphql.Mutation", "com.codename1.annotations.graphql.Query", @@ -671,7 +711,10 @@ public final class GeneratedCN1Access implements CN1Access { "com.codename1.crash.CrashProtection", "com.codename1.crash.PiiScrubber", "com.codename1.db.Cursor", + "com.codename1.db.CursorExt", "com.codename1.db.Database", + "com.codename1.db.DatabaseConfig", + "com.codename1.db.DatabaseEncryptionException", "com.codename1.db.Row", "com.codename1.db.RowExt", "com.codename1.db.ThreadSafeDatabase", @@ -961,6 +1004,70 @@ public final class GeneratedCN1Access implements CN1Access { "com.codename1.health.workout.WorkoutSession", "com.codename1.health.workout.WorkoutSessionListener", "com.codename1.health.workout.WorkoutSessionState", + "com.codename1.home.Accessory", + "com.codename1.home.AccessoryCategory", + "com.codename1.home.AccessoryService", + "com.codename1.home.AirQualityLevel", + "com.codename1.home.AlarmState", + "com.codename1.home.ChargingState", + "com.codename1.home.DoorState", + "com.codename1.home.FanMode", + "com.codename1.home.HeatingCoolingMode", + "com.codename1.home.HomeAuthorizationStatus", + "com.codename1.home.HomeAvailability", + "com.codename1.home.HomeBackend", + "com.codename1.home.HomeChangeListener", + "com.codename1.home.HomeConfigurationException", + "com.codename1.home.HomeError", + "com.codename1.home.HomeException", + "com.codename1.home.HomeRoom", + "com.codename1.home.HomeStructure", + "com.codename1.home.HomeStructureEvent", + "com.codename1.home.HomeStructureListener", + "com.codename1.home.HomeZone", + "com.codename1.home.LockState", + "com.codename1.home.PositionState", + "com.codename1.home.Scene", + "com.codename1.home.SceneAction", + "com.codename1.home.SceneType", + "com.codename1.home.ServiceType", + "com.codename1.home.SmartHome", + "com.codename1.home.StructureChangeKind", + "com.codename1.home.SubscriptionRequest", + "com.codename1.home.Trait", + "com.codename1.home.TraitChangeBatch", + "com.codename1.home.TraitConstraint", + "com.codename1.home.TraitReadRequest", + "com.codename1.home.TraitReading", + "com.codename1.home.TraitSubscription", + "com.codename1.home.TraitUnit", + "com.codename1.home.TraitUnitDimension", + "com.codename1.home.TraitValue", + "com.codename1.home.TraitValueKind", + "com.codename1.home.TraitWrite", + "com.codename1.home.TraitWriteResult", + "com.codename1.home.commissioning.Commissioner", + "com.codename1.home.commissioning.CommissioningRequest", + "com.codename1.home.commissioning.CommissioningResult", + "com.codename1.home.commissioning.CommissioningStyle", + "com.codename1.home.commissioning.SetupPayload", + "com.codename1.home.spi.HomeBridge", + "com.codename1.intents.AppEntity", + "com.codename1.intents.DynamicIntent", + "com.codename1.intents.EntitySelectionHandler", + "com.codename1.intents.Exposure", + "com.codename1.intents.IntentCompletion", + "com.codename1.intents.IntentContext", + "com.codename1.intents.IntentDates", + "com.codename1.intents.IntentDeclaration", + "com.codename1.intents.IntentDispatcher", + "com.codename1.intents.IntentParameterInfo", + "com.codename1.intents.IntentParameterType", + "com.codename1.intents.IntentResult", + "com.codename1.intents.IntentSerializer", + "com.codename1.intents.IntentSource", + "com.codename1.intents.Intents", + "com.codename1.intents.spi.IntentBridge", "com.codename1.io.AccessToken", "com.codename1.io.BufferedInputStream", "com.codename1.io.BufferedOutputStream", @@ -1361,6 +1468,8 @@ public final class GeneratedCN1Access implements CN1Access { "com.codename1.security.SecureRandom", "com.codename1.security.SecureStorage", "com.codename1.security.Signature", + "com.codename1.security.TapjackingPolicy", + "com.codename1.security.hardening.Hardening", "com.codename1.security.shield.AppShield", "com.codename1.security.shield.FailureMode", "com.codename1.security.shield.HostPolicy", @@ -1820,6 +1929,7 @@ public final class GeneratedCN1Access implements CN1Access { "com.codename1.vr.VRSettings", "com.codename1.vr.VRView", "com.codename1.wearable.WearableConnection", + "com.codename1.wearable.WearableConnection.DroppedDeliveryHandler", "com.codename1.wearable.WearableDataListener", "com.codename1.wearable.WearableMessage", "com.codename1.wearable.WearableMessageListener", @@ -2072,6 +2182,8 @@ private static Map buildMethodIndex() { fillMethodIndex26(index); fillMethodIndex27(index); fillMethodIndex28(index); + fillMethodIndex29(index); + fillMethodIndex30(index); return index; } @@ -2157,24 +2269,31 @@ private static void fillMethodIndex1(Map index) { index.put("com.codename1.ai.language.Translator", splitMembers("")); index.put("com.codename1.ai.language.Translator.Session", splitMembers("")); index.put("com.codename1.ai.vision.Barcode", splitMembers("")); + index.put("com.codename1.ai.vision.BarcodeFormat", splitMembers("")); index.put("com.codename1.ai.vision.BarcodeScanner", splitMembers("")); + index.put("com.codename1.ai.vision.CodeScanner", splitMembers("")); + index.put("com.codename1.ai.vision.CodeScannerOptions", splitMembers("")); index.put("com.codename1.ai.vision.DocumentScanResult", splitMembers("")); index.put("com.codename1.ai.vision.DocumentScanner", splitMembers("")); index.put("com.codename1.ai.vision.Face", splitMembers("")); index.put("com.codename1.ai.vision.FaceDetector", splitMembers("")); + index.put("com.codename1.ai.vision.FaceLandmarks", splitMembers("")); index.put("com.codename1.ai.vision.ImageLabel", splitMembers("")); index.put("com.codename1.ai.vision.ImageLabeler", splitMembers("")); index.put("com.codename1.ai.vision.Pose", splitMembers("")); index.put("com.codename1.ai.vision.Pose.Landmark", splitMembers("")); index.put("com.codename1.ai.vision.PoseDetector", splitMembers("")); + index.put("com.codename1.ai.vision.PoseLandmarks", splitMembers("")); index.put("com.codename1.ai.vision.SegmentationMask", splitMembers("")); index.put("com.codename1.ai.vision.SelfieSegmenter", splitMembers("")); index.put("com.codename1.ai.vision.TextRecognitionResult", splitMembers("")); index.put("com.codename1.ai.vision.TextRecognitionResult.TextBlock", splitMembers("")); index.put("com.codename1.ai.vision.TextRecognizer", splitMembers("")); + index.put("com.codename1.ai.vision.TextScript", splitMembers("")); index.put("com.codename1.ai.vision.VisionAnalyzer", splitMembers("")); index.put("com.codename1.ai.vision.VisionBackend", splitMembers("")); index.put("com.codename1.ai.vision.VisionBackends", splitMembers("")); + index.put("com.codename1.ai.vision.VisionCameraView", splitMembers("")); index.put("com.codename1.ai.vision.VisionException", splitMembers("")); index.put("com.codename1.ai.vision.VisionFeature", splitMembers("")); index.put("com.codename1.ai.vision.VisionImage", splitMembers("")); @@ -2200,16 +2319,17 @@ private static void fillMethodIndex1(Map index) { index.put("com.codename1.analytics.ConsentMode", splitMembers("")); index.put("com.codename1.analytics.FirebaseAnalyticsProvider", splitMembers("")); index.put("com.codename1.analytics.FirebaseAnalyticsProvider.Bridge", splitMembers("")); + } + + private static void fillMethodIndex2(Map index) { index.put("com.codename1.analytics.GoogleAnalyticsProvider", splitMembers("")); index.put("com.codename1.analytics.LegacyAnalyticsProviderAdapter", splitMembers("")); index.put("com.codename1.analytics.LoggingAnalyticsProvider", splitMembers("")); index.put("com.codename1.analytics.MatomoAnalyticsProvider", splitMembers("")); + index.put("com.codename1.annotations.AppIntent", splitMembers("")); index.put("com.codename1.annotations.Async", splitMembers("")); index.put("com.codename1.annotations.Async.Execute", splitMembers("")); index.put("com.codename1.annotations.Async.Schedule", splitMembers("")); - } - - private static void fillMethodIndex2(Map index) { index.put("com.codename1.annotations.Bind", splitMembers("")); index.put("com.codename1.annotations.Bindable", splitMembers("")); index.put("com.codename1.annotations.Column", splitMembers("")); @@ -2219,9 +2339,17 @@ private static void fillMethodIndex2(Map index) { index.put("com.codename1.annotations.DisableNullChecksAndArrayBoundsChecks", splitMembers("")); index.put("com.codename1.annotations.Email", splitMembers("")); index.put("com.codename1.annotations.Entity", splitMembers("")); + index.put("com.codename1.annotations.EntityId", splitMembers("")); + index.put("com.codename1.annotations.EntityImage", splitMembers("")); + index.put("com.codename1.annotations.EntityQuery", splitMembers("")); + index.put("com.codename1.annotations.EntityQuery.Kind", splitMembers("")); + index.put("com.codename1.annotations.EntitySubtitle", splitMembers("")); + index.put("com.codename1.annotations.EntityTitle", splitMembers("")); index.put("com.codename1.annotations.ExistIn", splitMembers("")); index.put("com.codename1.annotations.Fused", splitMembers("")); index.put("com.codename1.annotations.Id", splitMembers("")); + index.put("com.codename1.annotations.IntentEntity", splitMembers("")); + index.put("com.codename1.annotations.IntentParam", splitMembers("")); index.put("com.codename1.annotations.JsonIgnore", splitMembers("")); index.put("com.codename1.annotations.JsonProperty", splitMembers("")); index.put("com.codename1.annotations.Length", splitMembers("")); @@ -2238,9 +2366,29 @@ private static void fillMethodIndex2(Map index) { index.put("com.codename1.annotations.XmlElement", splitMembers("")); index.put("com.codename1.annotations.XmlRoot", splitMembers("")); index.put("com.codename1.annotations.XmlTransient", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Android", splitMembers("")); + index.put("com.codename1.annotations.buildhints.AndroidThemeMode", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Build", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Desktop", splitMembers("")); + index.put("com.codename1.annotations.buildhints.DesktopTitleBar", splitMembers("")); + index.put("com.codename1.annotations.buildhints.HardenControlFlow", splitMembers("")); + index.put("com.codename1.annotations.buildhints.HardenLevel", splitMembers("")); + index.put("com.codename1.annotations.buildhints.HardenStrings", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Hardening", splitMembers("")); + index.put("com.codename1.annotations.buildhints.InstallLocation", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Ios", splitMembers("")); + index.put("com.codename1.annotations.buildhints.IosDependencyManager", splitMembers("")); + index.put("com.codename1.annotations.buildhints.IosPrivacy", splitMembers("")); + index.put("com.codename1.annotations.buildhints.IosProjectType", splitMembers("")); + index.put("com.codename1.annotations.buildhints.IosThemeMode", splitMembers("")); + index.put("com.codename1.annotations.buildhints.NativeThemeMode", splitMembers("")); + index.put("com.codename1.annotations.buildhints.OnDeviceDebug", splitMembers("")); index.put("com.codename1.annotations.graphql.GraphQLClient", splitMembers("")); index.put("com.codename1.annotations.graphql.Mutation", splitMembers("")); index.put("com.codename1.annotations.graphql.Query", splitMembers("")); + } + + private static void fillMethodIndex3(Map index) { index.put("com.codename1.annotations.graphql.Subscription", splitMembers("")); index.put("com.codename1.annotations.graphql.Var", splitMembers("")); index.put("com.codename1.annotations.grpc.GrpcClient", splitMembers("")); @@ -2274,9 +2422,6 @@ private static void fillMethodIndex2(Map index) { index.put("com.codename1.ar.ARHitResult.Type", splitMembers("")); index.put("com.codename1.ar.ARImageAnchor", splitMembers("")); index.put("com.codename1.ar.ARLightEstimate", splitMembers("")); - } - - private static void fillMethodIndex3(Map index) { index.put("com.codename1.ar.ARModel", splitMembers("")); index.put("com.codename1.ar.ARNode", splitMembers("")); index.put("com.codename1.ar.ARPlane", splitMembers("")); @@ -2308,6 +2453,9 @@ private static void fillMethodIndex3(Map index) { index.put("com.codename1.binding.Binding", splitMembers("")); index.put("com.codename1.binding.NotifiableBinding", splitMembers("")); index.put("com.codename1.bluetooth.AdapterState", splitMembers("")); + } + + private static void fillMethodIndex4(Map index) { index.put("com.codename1.bluetooth.AdapterStateListener", splitMembers("")); index.put("com.codename1.bluetooth.Bluetooth", splitMembers("")); index.put("com.codename1.bluetooth.BluetoothDevice", splitMembers("")); @@ -2341,9 +2489,6 @@ private static void fillMethodIndex3(Map index) { index.put("com.codename1.bluetooth.le.L2capServer", splitMembers("")); index.put("com.codename1.bluetooth.le.ScanFilter", splitMembers("")); index.put("com.codename1.bluetooth.le.ScanListener", splitMembers("")); - } - - private static void fillMethodIndex4(Map index) { index.put("com.codename1.bluetooth.le.ScanMode", splitMembers("")); index.put("com.codename1.bluetooth.le.ScanResult", splitMembers("")); index.put("com.codename1.bluetooth.le.ScanSettings", splitMembers("")); @@ -2375,6 +2520,9 @@ private static void fillMethodIndex4(Map index) { index.put("com.codename1.calendar.CalendarCache", splitMembers("")); index.put("com.codename1.calendar.CalendarCapabilities", splitMembers("")); index.put("com.codename1.calendar.CalendarCapability", splitMembers("")); + } + + private static void fillMethodIndex5(Map index) { index.put("com.codename1.calendar.CalendarChange", splitMembers("")); index.put("com.codename1.calendar.CalendarChange.ChangeType", splitMembers("")); index.put("com.codename1.calendar.CalendarChange.EntityType", splitMembers("")); @@ -2408,9 +2556,6 @@ private static void fillMethodIndex4(Map index) { index.put("com.codename1.calendar.CalendarTokenProvider", splitMembers("")); index.put("com.codename1.calendar.DefaultCalendarHttpTransport", splitMembers("")); index.put("com.codename1.calendar.FreeBusyInterval", splitMembers("")); - } - - private static void fillMethodIndex5(Map index) { index.put("com.codename1.calendar.GoogleCalendarSource", splitMembers("")); index.put("com.codename1.calendar.ICalendarCodec", splitMembers("")); index.put("com.codename1.calendar.LocalCalendarSource", splitMembers("")); @@ -2442,6 +2587,9 @@ private static void fillMethodIndex5(Map index) { index.put("com.codename1.car.CarActionListener", splitMembers("")); index.put("com.codename1.car.CarActionStrip", splitMembers("")); index.put("com.codename1.car.CarApplication", splitMembers("")); + } + + private static void fillMethodIndex6(Map index) { index.put("com.codename1.car.CarColor", splitMembers("")); index.put("com.codename1.car.CarConnectionListener", splitMembers("")); index.put("com.codename1.car.CarContext", splitMembers("")); @@ -2475,9 +2623,6 @@ private static void fillMethodIndex5(Map index) { index.put("com.codename1.charts.models.Point", splitMembers("")); index.put("com.codename1.charts.models.RangeCategorySeries", splitMembers("")); index.put("com.codename1.charts.models.SeriesSelection", splitMembers("")); - } - - private static void fillMethodIndex6(Map index) { index.put("com.codename1.charts.models.TimeSeries", splitMembers("")); index.put("com.codename1.charts.models.XYMultipleSeriesDataset", splitMembers("")); index.put("com.codename1.charts.models.XYSeries", splitMembers("")); @@ -2509,6 +2654,9 @@ private static void fillMethodIndex6(Map index) { index.put("com.codename1.charts.views.CubicLineChart", splitMembers("")); index.put("com.codename1.charts.views.DialChart", splitMembers("")); index.put("com.codename1.charts.views.DoughnutChart", splitMembers("")); + } + + private static void fillMethodIndex7(Map index) { index.put("com.codename1.charts.views.LineChart", splitMembers("")); index.put("com.codename1.charts.views.PieChart", splitMembers("")); index.put("com.codename1.charts.views.PieMapper", splitMembers("")); @@ -2542,9 +2690,6 @@ private static void fillMethodIndex6(Map index) { index.put("com.codename1.components.FileTreeModel", splitMembers("addExtensionFilter(String)getChildren(Object)isLeaf(Object)")); index.put("com.codename1.components.FloatingActionButton", splitMembers("accessibilityChanged()accessibilityChanged(int)addActionListener(ActionListener)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindFabToContainer(Component)bindFabToContainer(Component, int, int)bindProperty(String, BindTarget)bindStateTo(Button)blocksSideSwipe()clearClientProperties()contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)createSubFAB(char, String)drop(Component, int, int)getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getActionListeners()getAlignment()getAllStyles()getAnimationManager()getBadgeStyleComponent()getBadgeText()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getCommand()getComponentForm()getComponentState()getCursor()getDirtyRegion()getDisabledIcon()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getFloatingActionTextUIID()getFontIcon()getFontIconSize()getGap()getHeight()getIcon()getIconFont()getIconFromState()getIconStyleComponent()getIconUIID()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getListeners()getMask()getMaskName()getMaskedIcon()getMaterialIcon()getMaterialIconSize()getMaxAutoSize()getMinAutoSize()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedIcon()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getReleaseRadius()getRolloverIcon()getRolloverPressedIcon()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getShiftMillimeters()getShiftMillimetersF()getShiftText()getSideGap()getState()getStringWidth(Font)getStyle()getTabIndex()getTensileLength()getText()getTextPosition()getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVerticalAlignment()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()isAlwaysTensile()isAutoRelease()isAutoSizeMode()isBlockLead()isCapsText()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isEndsWith3Points()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isLegacyRenderer()isOpaque()isOppositeSide()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSelected()isShouldLocalize()isShowEvenIfBlank()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTextSelectionEnabled()isTickerEnabled()isTickerRunning()isToggle()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])pressed()putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)released()released(int, int)remove()removeActionListener(ActionListener)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlignment(int)setAlwaysTensile(boolean)setAutoRelease(boolean)setAutoSizeMode(boolean)setBadgeText(String)setBadgeUIID(String)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCapsText(boolean)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setCommand(Command)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledIcon(Image)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setEndsWith3Points(boolean)setFlatten(boolean)setFloatingActionTextUIID(String)setFocus(boolean)setFocusable(boolean)setFontIcon(char)setFontIcon(Font, char)setFontIcon(Font, char, float)setGap(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIcon(Image)setIconUIID(String)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLegacyRenderer(boolean)setMask(Object)setMaskName(String)setMaterialIcon(char)setMaterialIcon(char, float)setMaxAutoSize(float)setMinAutoSize(float)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedIcon(Image)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setReleaseRadius(int)setReleased()setRippleEffect(boolean)setRolloverIcon(Image)setRolloverPressedIcon(Image)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShiftMillimeters(float)setShiftMillimeters(int)setShiftText(int)setShouldCalcPreferredSize(boolean)setShouldLocalize(boolean)setShowEvenIfBlank(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextPosition(int)setTextSelectionEnabled(boolean)setTickerEnabled(boolean)setToggle(boolean)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalAlignment(int)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)shouldTickerStart()startEditingAsync()startTicker()startTicker(long, boolean)stopEditing(Runnable)stopTicker()stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbind()unbindProperty(String, BindTarget)unbindStateFrom(Button)visibleBoundsContains(int, int)createBadge(String)createFAB(char)createFAB(char, String)getIconDefaultSize()isAutoSizing()setAutoSizing(boolean)setIconDefaultSize(float)")); index.put("com.codename1.components.FloatingHint", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)drop(Component, int, int)findDropTargetAt(int, int)findFirstFocusable()flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeAll()removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)")); - } - - private static void fillMethodIndex7(Map index) { index.put("com.codename1.components.ImageViewer", splitMembers("accessibilityChanged()accessibilityChanged(int)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)deinitialize()drop(Component, int, int)getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getCroppedImage(int)getCroppedImage(int, int, int)getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getImage()getImageList()getImageX()getImageY()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getSwipePlaceholder()getSwipeThreshold()getTabIndex()getTensileLength()getTextSelectionSupport()getThumbnailBarHeight()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()getZoom()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()initComponent()isAllowScaleDown()isAlwaysTensile()isAnimatedZoom()isBlockLead()isCellRenderer()isChildOf(Container)isCycleLeft()isCycleRight()isDraggable()isDropTarget()isEagerLock()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isNavigationArrowsVisible()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isThumbnailsVisible()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAllowScaleDown(boolean)setAlwaysTensile(boolean)setAnimateZoom(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setCycleLeft(boolean)setCycleRight(boolean)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEagerLock(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setImage(Image)setImageInitialPosition(int)setImageList(ListModel)setImageNoReposition(Image)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setName(String)setNavigationArrowsVisible(boolean)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setSwipePlaceholder(Image)setSwipeThreshold(float)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setThumbnailBarHeight(float)setThumbnailsVisible(boolean)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)setZoom(float)setZoom(float, float, float)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)")); index.put("com.codename1.components.InfiniteProgress", splitMembers("accessibilityChanged()accessibilityChanged(int)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animate(boolean)announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)drop(Component, int, int)getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAngleIncrease()getAnimation()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getMaterialDesignColor()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTickCount()getTintColor()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isMaterialDesignMode()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setAngleIncrease(int)setAnimation(Image)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setMaterialDesignColor(int)setMaterialDesignMode(boolean)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTickCount(int)setTintColor(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)showInfiniteBlocking()showInifiniteBlocking()startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)getDefaultMaterialDesignColor()isDefaultMaterialDesignMode()setDefaultMaterialDesignColor(int)setDefaultMaterialDesignMode(boolean)")); index.put("com.codename1.components.InfiniteScrollAdapter", splitMembers("addMoreComponents(Component[], boolean)continueFetching()getComponentLimit()getInfiniteProgress()setComponentLimit(int)addMoreComponents(Container, Component[], boolean)continueFetching(Container)createInfiniteScroll(Container, Runnable)createInfiniteScroll(Container, Runnable, boolean)")); @@ -2576,13 +2721,19 @@ private static void fillMethodIndex7(Map index) { index.put("com.codename1.components.ToastBar", splitMembers("createStatus()getDefaultMessageUIID()getDefaultUIID()getPosition()setDefaultMessageUIID(String)setDefaultUIID(String)setPosition(int)setVisible(boolean)useFormLayeredPane(boolean)getDefaultMessageTimeout()getInstance()setDefaultMessageTimeout(int)showConnectionProgress(String, ConnectionRequest, SuccessCallback, FailureCallback)showErrorMessage(String)showErrorMessage(String, int)showInfoMessage(String)showMessage(String, char)showMessage(String, char, int)showMessage(String, char, ActionListener)showMessage(String, char, int, ActionListener)")); index.put("com.codename1.components.WebBrowser", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)destroy()drop(Component, int, int)findDropTargetAt(int, int)findFirstFocusable()flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getBrowserNavigationCallback()getChildrenAsList(boolean)getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getInternal()getLabelForComponent()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getPage()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTitle()getTooltip()getUIID()getUIManager()getURL()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)onError(String, int)onLoad(String)onStart(String)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)reload()remove()removeAll()removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setBrowserNavigationCallback(BrowserNavigationCallback)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPage(String, String)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setURL(String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stop()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)createDataURI(byte[], String)")); index.put("com.codename1.contacts.Address", splitMembers("")); + } + + private static void fillMethodIndex8(Map index) { index.put("com.codename1.contacts.Contact", splitMembers("")); index.put("com.codename1.contacts.ContactsManager", splitMembers("")); index.put("com.codename1.contacts.ContactsModel", splitMembers("")); index.put("com.codename1.crash.CrashProtection", splitMembers("")); index.put("com.codename1.crash.PiiScrubber", splitMembers("")); index.put("com.codename1.db.Cursor", splitMembers("")); + index.put("com.codename1.db.CursorExt", splitMembers("")); index.put("com.codename1.db.Database", splitMembers("")); + index.put("com.codename1.db.DatabaseConfig", splitMembers("")); + index.put("com.codename1.db.DatabaseEncryptionException", splitMembers("")); index.put("com.codename1.db.Row", splitMembers("")); index.put("com.codename1.db.RowExt", splitMembers("")); index.put("com.codename1.db.ThreadSafeDatabase", splitMembers("")); @@ -2609,9 +2760,6 @@ private static void fillMethodIndex7(Map index) { index.put("com.codename1.gaming.VirtualButton", splitMembers("")); index.put("com.codename1.gaming.VirtualJoystick", splitMembers("")); index.put("com.codename1.gaming.VoiceListener", splitMembers("")); - } - - private static void fillMethodIndex8(Map index) { index.put("com.codename1.gaming.level.AssetCatalog", splitMembers("")); index.put("com.codename1.gaming.level.AssetDef", splitMembers("")); index.put("com.codename1.gaming.level.AssetDef.Kind", splitMembers("")); @@ -2640,6 +2788,9 @@ private static void fillMethodIndex8(Map index) { index.put("com.codename1.gaming.level.TileLayer", splitMembers("")); index.put("com.codename1.gaming.physics.BodyType", splitMembers("")); index.put("com.codename1.gaming.physics.ContactListener", splitMembers("")); + } + + private static void fillMethodIndex9(Map index) { index.put("com.codename1.gaming.physics.PhysicsBody", splitMembers("")); index.put("com.codename1.gaming.physics.PhysicsContact", splitMembers("")); index.put("com.codename1.gaming.physics.PhysicsJoint", splitMembers("")); @@ -2676,9 +2827,6 @@ private static void fillMethodIndex8(Map index) { index.put("com.codename1.gaming.physics.box2d.collision.TimeOfImpact.TOIOutput", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.collision.TimeOfImpact.TOIOutputState", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.collision.WorldManifold", splitMembers("")); - } - - private static void fillMethodIndex9(Map index) { index.put("com.codename1.gaming.physics.box2d.collision.broadphase.BroadPhase", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.collision.broadphase.BroadPhaseStrategy", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.collision.broadphase.DynamicTree", splitMembers("")); @@ -2707,6 +2855,9 @@ private static void fillMethodIndex9(Map index) { index.put("com.codename1.gaming.physics.box2d.common.Vec3", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.Body", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.BodyDef", splitMembers("")); + } + + private static void fillMethodIndex10(Map index) { index.put("com.codename1.gaming.physics.box2d.dynamics.BodyType", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.ContactManager", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.Filter", splitMembers("")); @@ -2743,9 +2894,6 @@ private static void fillMethodIndex9(Map index) { index.put("com.codename1.gaming.physics.box2d.dynamics.joints.FrictionJoint", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.joints.FrictionJointDef", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.joints.GearJoint", splitMembers("")); - } - - private static void fillMethodIndex10(Map index) { index.put("com.codename1.gaming.physics.box2d.dynamics.joints.GearJointDef", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.joints.Jacobian", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.joints.Joint", splitMembers("")); @@ -2774,6 +2922,9 @@ private static void fillMethodIndex10(Map index) { index.put("com.codename1.gaming.physics.box2d.pooling.arrays.IntArray", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.pooling.arrays.Vec2Array", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.pooling.normal.CircleStack", splitMembers("")); + } + + private static void fillMethodIndex11(Map index) { index.put("com.codename1.gaming.physics.box2d.pooling.normal.DefaultWorldPool", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.pooling.normal.MutableStack", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.pooling.normal.OrderedStack", splitMembers("")); @@ -2810,9 +2961,6 @@ private static void fillMethodIndex10(Map index) { index.put("com.codename1.health.AggregateResult", splitMembers("")); index.put("com.codename1.health.BloodPressureSample", splitMembers("")); index.put("com.codename1.health.CategorySample", splitMembers("")); - } - - private static void fillMethodIndex11(Map index) { index.put("com.codename1.health.Health", splitMembers("")); index.put("com.codename1.health.HealthAccess", splitMembers("")); index.put("com.codename1.health.HealthAggregationStyle", splitMembers("")); @@ -2841,6 +2989,9 @@ private static void fillMethodIndex11(Map index) { index.put("com.codename1.health.HealthUnitDimension", splitMembers("")); index.put("com.codename1.health.HealthWriteResult", splitMembers("")); index.put("com.codename1.health.QuantitySample", splitMembers("")); + } + + private static void fillMethodIndex12(Map index) { index.put("com.codename1.health.RecordingMethod", splitMembers("")); index.put("com.codename1.health.SamplePage", splitMembers("")); index.put("com.codename1.health.SampleQuery", splitMembers("")); @@ -2877,9 +3028,6 @@ private static void fillMethodIndex11(Map index) { index.put("com.codename1.health.sensors.TemperatureMeasurement", splitMembers("")); index.put("com.codename1.health.sensors.WeightMeasurement", splitMembers("")); index.put("com.codename1.health.workout.WorkoutConfiguration", splitMembers("")); - } - - private static void fillMethodIndex12(Map index) { index.put("com.codename1.health.workout.WorkoutEvent", splitMembers("")); index.put("com.codename1.health.workout.WorkoutEvent.Kind", splitMembers("")); index.put("com.codename1.health.workout.WorkoutLocationType", splitMembers("")); @@ -2887,6 +3035,73 @@ private static void fillMethodIndex12(Map index) { index.put("com.codename1.health.workout.WorkoutSession", splitMembers("")); index.put("com.codename1.health.workout.WorkoutSessionListener", splitMembers("")); index.put("com.codename1.health.workout.WorkoutSessionState", splitMembers("")); + index.put("com.codename1.home.Accessory", splitMembers("")); + index.put("com.codename1.home.AccessoryCategory", splitMembers("")); + index.put("com.codename1.home.AccessoryService", splitMembers("")); + index.put("com.codename1.home.AirQualityLevel", splitMembers("")); + index.put("com.codename1.home.AlarmState", splitMembers("")); + index.put("com.codename1.home.ChargingState", splitMembers("")); + index.put("com.codename1.home.DoorState", splitMembers("")); + index.put("com.codename1.home.FanMode", splitMembers("")); + index.put("com.codename1.home.HeatingCoolingMode", splitMembers("")); + index.put("com.codename1.home.HomeAuthorizationStatus", splitMembers("")); + index.put("com.codename1.home.HomeAvailability", splitMembers("")); + index.put("com.codename1.home.HomeBackend", splitMembers("")); + index.put("com.codename1.home.HomeChangeListener", splitMembers("")); + index.put("com.codename1.home.HomeConfigurationException", splitMembers("")); + index.put("com.codename1.home.HomeError", splitMembers("")); + index.put("com.codename1.home.HomeException", splitMembers("")); + index.put("com.codename1.home.HomeRoom", splitMembers("")); + index.put("com.codename1.home.HomeStructure", splitMembers("")); + index.put("com.codename1.home.HomeStructureEvent", splitMembers("")); + index.put("com.codename1.home.HomeStructureListener", splitMembers("")); + index.put("com.codename1.home.HomeZone", splitMembers("")); + } + + private static void fillMethodIndex13(Map index) { + index.put("com.codename1.home.LockState", splitMembers("")); + index.put("com.codename1.home.PositionState", splitMembers("")); + index.put("com.codename1.home.Scene", splitMembers("")); + index.put("com.codename1.home.SceneAction", splitMembers("")); + index.put("com.codename1.home.SceneType", splitMembers("")); + index.put("com.codename1.home.ServiceType", splitMembers("")); + index.put("com.codename1.home.SmartHome", splitMembers("")); + index.put("com.codename1.home.StructureChangeKind", splitMembers("")); + index.put("com.codename1.home.SubscriptionRequest", splitMembers("")); + index.put("com.codename1.home.Trait", splitMembers("")); + index.put("com.codename1.home.TraitChangeBatch", splitMembers("")); + index.put("com.codename1.home.TraitConstraint", splitMembers("")); + index.put("com.codename1.home.TraitReadRequest", splitMembers("")); + index.put("com.codename1.home.TraitReading", splitMembers("")); + index.put("com.codename1.home.TraitSubscription", splitMembers("")); + index.put("com.codename1.home.TraitUnit", splitMembers("")); + index.put("com.codename1.home.TraitUnitDimension", splitMembers("")); + index.put("com.codename1.home.TraitValue", splitMembers("")); + index.put("com.codename1.home.TraitValueKind", splitMembers("")); + index.put("com.codename1.home.TraitWrite", splitMembers("")); + index.put("com.codename1.home.TraitWriteResult", splitMembers("")); + index.put("com.codename1.home.commissioning.Commissioner", splitMembers("")); + index.put("com.codename1.home.commissioning.CommissioningRequest", splitMembers("")); + index.put("com.codename1.home.commissioning.CommissioningResult", splitMembers("")); + index.put("com.codename1.home.commissioning.CommissioningStyle", splitMembers("")); + index.put("com.codename1.home.commissioning.SetupPayload", splitMembers("")); + index.put("com.codename1.home.spi.HomeBridge", splitMembers("")); + index.put("com.codename1.intents.AppEntity", splitMembers("")); + index.put("com.codename1.intents.DynamicIntent", splitMembers("")); + index.put("com.codename1.intents.EntitySelectionHandler", splitMembers("")); + index.put("com.codename1.intents.Exposure", splitMembers("")); + index.put("com.codename1.intents.IntentCompletion", splitMembers("")); + index.put("com.codename1.intents.IntentContext", splitMembers("")); + index.put("com.codename1.intents.IntentDates", splitMembers("")); + index.put("com.codename1.intents.IntentDeclaration", splitMembers("")); + index.put("com.codename1.intents.IntentDispatcher", splitMembers("")); + index.put("com.codename1.intents.IntentParameterInfo", splitMembers("")); + index.put("com.codename1.intents.IntentParameterType", splitMembers("")); + index.put("com.codename1.intents.IntentResult", splitMembers("")); + index.put("com.codename1.intents.IntentSerializer", splitMembers("")); + index.put("com.codename1.intents.IntentSource", splitMembers("")); + index.put("com.codename1.intents.Intents", splitMembers("")); + index.put("com.codename1.intents.spi.IntentBridge", splitMembers("")); index.put("com.codename1.io.AccessToken", splitMembers("")); index.put("com.codename1.io.BufferedInputStream", splitMembers("")); index.put("com.codename1.io.BufferedOutputStream", splitMembers("")); @@ -2908,6 +3123,9 @@ private static void fillMethodIndex12(Map index) { index.put("com.codename1.io.JSONParser", splitMembers("")); index.put("com.codename1.io.JSONParser.RawJson", splitMembers("")); index.put("com.codename1.io.JSONWriter", splitMembers("")); + } + + private static void fillMethodIndex14(Map index) { index.put("com.codename1.io.JSONWriter.ArrayBuilder", splitMembers("")); index.put("com.codename1.io.JSONWriter.ObjectBuilder", splitMembers("")); index.put("com.codename1.io.Log", splitMembers("")); @@ -2944,9 +3162,6 @@ private static void fillMethodIndex12(Map index) { index.put("com.codename1.io.bonjour.BonjourService", splitMembers("")); index.put("com.codename1.io.bonjour.BonjourServiceListener", splitMembers("")); index.put("com.codename1.io.graphql.GraphQL", splitMembers("")); - } - - private static void fillMethodIndex13(Map index) { index.put("com.codename1.io.graphql.GraphQLClients", splitMembers("")); index.put("com.codename1.io.graphql.GraphQLClients.Factory", splitMembers("")); index.put("com.codename1.io.graphql.GraphQLError", splitMembers("")); @@ -2975,6 +3190,9 @@ private static void fillMethodIndex13(Map index) { index.put("com.codename1.io.gzip.GZIPHeader", splitMembers("")); index.put("com.codename1.io.gzip.GZIPInputStream", splitMembers("")); index.put("com.codename1.io.gzip.GZIPOutputStream", splitMembers("")); + } + + private static void fillMethodIndex15(Map index) { index.put("com.codename1.io.gzip.Inflater", splitMembers("")); index.put("com.codename1.io.gzip.InflaterInputStream", splitMembers("")); index.put("com.codename1.io.gzip.JZlib", splitMembers("")); @@ -3011,9 +3229,6 @@ private static void fillMethodIndex13(Map index) { index.put("com.codename1.io.usb.UsbDeviceListener", splitMembers("")); index.put("com.codename1.io.usb.UsbPlatform", splitMembers("")); index.put("com.codename1.io.webauthn.PublicKeyCredential", splitMembers("")); - } - - private static void fillMethodIndex14(Map index) { index.put("com.codename1.io.webauthn.PublicKeyCredentialCreationOptions", splitMembers("")); index.put("com.codename1.io.webauthn.PublicKeyCredentialCreationOptions.Builder", splitMembers("")); index.put("com.codename1.io.webauthn.PublicKeyCredentialRequestOptions", splitMembers("")); @@ -3042,6 +3257,9 @@ private static void fillMethodIndex14(Map index) { index.put("com.codename1.l10n.ParseException", splitMembers("")); index.put("com.codename1.l10n.SimpleDateFormat", splitMembers("")); index.put("com.codename1.location.Geofence", splitMembers("")); + } + + private static void fillMethodIndex16(Map index) { index.put("com.codename1.location.GeofenceListener", splitMembers("")); index.put("com.codename1.location.GeofenceManager", splitMembers("")); index.put("com.codename1.location.GeofenceManager.Listener", splitMembers("")); @@ -3078,9 +3296,6 @@ private static void fillMethodIndex14(Map index) { index.put("com.codename1.maps.layers.AbstractLayer", splitMembers("")); index.put("com.codename1.maps.layers.ArrowLinesLayer", splitMembers("")); index.put("com.codename1.maps.layers.Layer", splitMembers("")); - } - - private static void fillMethodIndex15(Map index) { index.put("com.codename1.maps.layers.LinesLayer", splitMembers("")); index.put("com.codename1.maps.layers.PointLayer", splitMembers("")); index.put("com.codename1.maps.layers.PointsLayer", splitMembers("")); @@ -3109,6 +3324,9 @@ private static void fillMethodIndex15(Map index) { index.put("com.codename1.maps.vector.StyleLayer", splitMembers("")); index.put("com.codename1.maps.vector.TileCallback", splitMembers("")); index.put("com.codename1.maps.vector.TileSource", splitMembers("")); + } + + private static void fillMethodIndex17(Map index) { index.put("com.codename1.maps.vector.VectorFeature", splitMembers("")); index.put("com.codename1.maps.vector.VectorLayer", splitMembers("")); index.put("com.codename1.maps.vector.VectorMapEngine", splitMembers("")); @@ -3145,9 +3363,6 @@ private static void fillMethodIndex15(Map index) { index.put("com.codename1.media.SpeechRecognizer", splitMembers("")); index.put("com.codename1.media.TextToSpeech", splitMembers("")); index.put("com.codename1.media.TimedRecognitionCallback", splitMembers("")); - } - - private static void fillMethodIndex16(Map index) { index.put("com.codename1.media.Transcriber", splitMembers("")); index.put("com.codename1.media.TranscriptionRequest", splitMembers("")); index.put("com.codename1.media.TranscriptionResult", splitMembers("")); @@ -3176,6 +3391,9 @@ private static void fillMethodIndex16(Map index) { index.put("com.codename1.nfc.NfcError", splitMembers("")); index.put("com.codename1.nfc.NfcException", splitMembers("")); index.put("com.codename1.nfc.NfcF", splitMembers("")); + } + + private static void fillMethodIndex18(Map index) { index.put("com.codename1.nfc.NfcListener", splitMembers("")); index.put("com.codename1.nfc.NfcReadOptions", splitMembers("")); index.put("com.codename1.nfc.NfcV", splitMembers("")); @@ -3212,9 +3430,6 @@ private static void fillMethodIndex16(Map index) { index.put("com.codename1.plugin.event.OpenGalleryEvent", splitMembers("")); index.put("com.codename1.plugin.event.PluginEvent", splitMembers("")); index.put("com.codename1.printing.PrintResult", splitMembers("")); - } - - private static void fillMethodIndex17(Map index) { index.put("com.codename1.printing.PrintResultListener", splitMembers("")); index.put("com.codename1.printing.Printer", splitMembers("")); index.put("com.codename1.processing.Result", splitMembers("")); @@ -3243,6 +3458,9 @@ private static void fillMethodIndex17(Map index) { index.put("com.codename1.properties.UiBinding", splitMembers("")); index.put("com.codename1.properties.UiBinding.BooleanConverter", splitMembers("")); index.put("com.codename1.properties.UiBinding.BoundTableModel", splitMembers("")); + } + + private static void fillMethodIndex19(Map index) { index.put("com.codename1.properties.UiBinding.CheckBoxRadioSelectionAdapter", splitMembers("")); index.put("com.codename1.properties.UiBinding.ComponentAdapter", splitMembers("")); index.put("com.codename1.properties.UiBinding.DateConverter", splitMembers("")); @@ -3279,9 +3497,6 @@ private static void fillMethodIndex17(Map index) { index.put("com.codename1.router.PopGuard", splitMembers("")); index.put("com.codename1.router.PopReason", splitMembers("")); index.put("com.codename1.router.RouteDispatcher", splitMembers("")); - } - - private static void fillMethodIndex18(Map index) { index.put("com.codename1.security.AuthenticationOptions", splitMembers("")); index.put("com.codename1.security.Base32", splitMembers("")); index.put("com.codename1.security.BiometricError", splitMembers("")); @@ -3305,9 +3520,14 @@ private static void fillMethodIndex18(Map index) { index.put("com.codename1.security.SecureRandom", splitMembers("")); index.put("com.codename1.security.SecureStorage", splitMembers("")); index.put("com.codename1.security.Signature", splitMembers("")); + index.put("com.codename1.security.TapjackingPolicy", splitMembers("")); + index.put("com.codename1.security.hardening.Hardening", splitMembers("")); index.put("com.codename1.security.shield.AppShield", splitMembers("")); index.put("com.codename1.security.shield.FailureMode", splitMembers("")); index.put("com.codename1.security.shield.HostPolicy", splitMembers("")); + } + + private static void fillMethodIndex20(Map index) { index.put("com.codename1.security.shield.PinSet", splitMembers("")); index.put("com.codename1.security.shield.ShieldConfig", splitMembers("")); index.put("com.codename1.security.shield.ShieldException", splitMembers("")); @@ -3346,9 +3566,6 @@ private static void fillMethodIndex18(Map index) { index.put("com.codename1.social.Login", splitMembers("")); index.put("com.codename1.social.LoginCallback", splitMembers("")); index.put("com.codename1.social.MicrosoftConnect", splitMembers("")); - } - - private static void fillMethodIndex19(Map index) { index.put("com.codename1.surfaces.LiveActivity", splitMembers("")); index.put("com.codename1.surfaces.LiveActivityDescriptor", splitMembers("")); index.put("com.codename1.surfaces.SurfaceActionEvent", splitMembers("")); @@ -3375,6 +3592,9 @@ private static void fillMethodIndex19(Map index) { index.put("com.codename1.surfaces.WidgetKind", splitMembers("")); index.put("com.codename1.surfaces.WidgetSize", splitMembers("")); index.put("com.codename1.surfaces.WidgetTimeline", splitMembers("")); + } + + private static void fillMethodIndex21(Map index) { index.put("com.codename1.surfaces.WidgetTimeline.Entry", splitMembers("")); index.put("com.codename1.surfaces.spi.SurfaceBridge", splitMembers("")); index.put("com.codename1.system.CrashReport", splitMembers("")); @@ -3413,12 +3633,9 @@ private static void fillMethodIndex19(Map index) { index.put("com.codename1.ui.CheckBox", splitMembers("accessibilityChanged()accessibilityChanged(int)addActionListener(ActionListener)addChangeListener(ActionListener)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)bindStateTo(Button)blocksSideSwipe()clearClientProperties()contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)drop(Component, int, int)getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getActionListeners()getAlignment()getAllStyles()getAnimationManager()getBadgeStyleComponent()getBadgeText()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getCommand()getComponentForm()getComponentState()getCursor()getDirtyRegion()getDisabledIcon()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getFontIcon()getFontIconSize()getGap()getHeight()getIcon()getIconFont()getIconFromState()getIconStyleComponent()getIconUIID()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getListeners()getMask()getMaskName()getMaskedIcon()getMaterialIcon()getMaterialIconSize()getMaxAutoSize()getMinAutoSize()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedIcon()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getReleaseRadius()getRolloverIcon()getRolloverPressedIcon()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getShiftMillimeters()getShiftMillimetersF()getShiftText()getSideGap()getState()getStringWidth(Font)getStyle()getTabIndex()getTensileLength()getText()getTextPosition()getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVerticalAlignment()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()isAlwaysTensile()isAutoRelease()isAutoSizeMode()isBlockLead()isCapsText()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isEndsWith3Points()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isLegacyRenderer()isOpaque()isOppositeSide()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSelected()isShouldLocalize()isShowEvenIfBlank()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTextSelectionEnabled()isTickerEnabled()isTickerRunning()isToggle()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])pressed()putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)released()released(int, int)remove()removeActionListener(ActionListener)removeChangeListeners(ActionListener)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlignment(int)setAlwaysTensile(boolean)setAutoRelease(boolean)setAutoSizeMode(boolean)setBadgeText(String)setBadgeUIID(String)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCapsText(boolean)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setCommand(Command)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledIcon(Image)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setEndsWith3Points(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontIcon(char)setFontIcon(Font, char)setFontIcon(Font, char, float)setGap(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIcon(Image)setIconUIID(String)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLegacyRenderer(boolean)setMask(Object)setMaskName(String)setMaterialIcon(char)setMaterialIcon(char, float)setMaxAutoSize(float)setMinAutoSize(float)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOppositeSide(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedIcon(Image)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setReleaseRadius(int)setReleased()setRippleEffect(boolean)setRolloverIcon(Image)setRolloverPressedIcon(Image)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelected(boolean)setSelectedStyle(Style)setShiftMillimeters(float)setShiftMillimeters(int)setShiftText(int)setShouldCalcPreferredSize(boolean)setShouldLocalize(boolean)setShowEvenIfBlank(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextPosition(int)setTextSelectionEnabled(boolean)setTickerEnabled(boolean)setToggle(boolean)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalAlignment(int)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)shouldTickerStart()startEditingAsync()startTicker()startTicker(long, boolean)stopEditing(Runnable)stopTicker()stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)unbindStateFrom(Button)visibleBoundsContains(int, int)createToggle(Image)createToggle(String)createToggle(String, Image)")); index.put("com.codename1.ui.ClipboardContent", splitMembers("findPreferredMimeType(String[])getBytes(String)getData(String)getMimeTypes()getText(String)hasMimeType(String)setData(String, Object)")); index.put("com.codename1.ui.CodeCompletion", splitMembers("getDetail()getDisplayText()getInsertText()getType()setDetail(String)setType(String)")); - } - - private static void fillMethodIndex20(Map index) { index.put("com.codename1.ui.CodeCompletionProvider", splitMembers("getCompletions(CodeEditor, String, int, SuccessCallback)")); index.put("com.codename1.ui.CodeDiagnostic", splitMembers("getColumn()getEndColumn()getEndLine()getLine()getMessage()getSeverity()setSeverity(String)")); - index.put("com.codename1.ui.CodeEditor", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addChangeListener(ActionListener)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addReadyListener(ActionListener)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()blurEditor()clearClientProperties()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)drop(Component, int, int)editorChanged()findDropTargetAt(int, int)findFirstFocusable()fireEditorEvent(String, String)flushReplace()focusEditor()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getCompletionProvider()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getCursor()getCursorPosition(SuccessCallback)getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLanguage()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTabSize()getTensileLength()getText(SuccessCallback)getTextSelectionSupport()getTheme()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()insertAtCursor(String)invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEditorReady()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isNativeEditor()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isReadOnly()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isShowLineNumbers()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTextInputSupported()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)onReady(Runnable)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeAll()removeChangeListener(ActionListener)removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeReadyListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setCompletionProvider(CodeCompletionProvider)setComponentState(Object)setCursor(int)setDiagnostics(List)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditable(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLanguage(String)setLayout(Layout)setLeadComponent(Component)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setReadOnly(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setShowLineNumbers(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTabSize(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTheme(String)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()startTextInput(TextInputClient, TextInputConfig)stopEditing(Runnable)stopTextInput(Object)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)updateTextInputState(Object, TextInputState)visibleBoundsContains(int, int)getRegisteredSyntaxHighlighter(String)registerSyntaxHighlighter(String, SyntaxHighlighter)")); + index.put("com.codename1.ui.CodeEditor", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addChangeListener(ActionListener)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addProtectedEditListener(ActionListener)addPullToRefresh(Runnable)addReadyListener(ActionListener)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()blurEditor()clearClientProperties()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)drop(Component, int, int)editorChanged()findDropTargetAt(int, int)findFirstFocusable()fireEditorEvent(String, String)flushReplace()focusEditor()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getCompletionProvider()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getCursor()getCursorPosition(SuccessCallback)getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLanguage()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTabSize()getTensileLength()getText(SuccessCallback)getTextSelectionSupport()getTheme()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()insertAtCursor(String)invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEditorReady()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isNativeEditor()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isReadOnly()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isShowLineNumbers()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTextInputSupported()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)onReady(Runnable)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeAll()removeChangeListener(ActionListener)removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeProtectedEditListener(ActionListener)removeReadyListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setCompletionProvider(CodeCompletionProvider)setComponentState(Object)setCursor(int)setCursorPosition(int)setDiagnostics(List)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditable(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLanguage(String)setLayout(Layout)setLeadComponent(Component)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setProtectedRegionMarkers(String, String)setPullToRefresh(Runnable)setRTL(boolean)setReadOnly(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setShowLineNumbers(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTabSize(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTheme(String)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()startTextInput(TextInputClient, TextInputConfig)stopEditing(Runnable)stopTextInput(Object)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)updateTextInputState(Object, TextInputState)visibleBoundsContains(int, int)getRegisteredSyntaxHighlighter(String)registerSyntaxHighlighter(String, SyntaxHighlighter)")); index.put("com.codename1.ui.ComboBox", splitMembers("accessibilityChanged()accessibilityChanged(int)addActionListener(ActionListener)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addSelectionListener(SelectionListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)drop(Component, int, int)getAbsoluteX()getAbsoluteY()getAccessibilityItemBounds(int, Rectangle)getAccessibilityItemText(int)getAccessibilityNode()getAccessibilityText()getAccessibilityVisibleItemIndices()getActionListeners()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComboBoxImage()getComponentForm()getComponentState()getCurrentSelected()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getFixedSelection()getHeight()getHint()getHintIcon()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getItemGap()getLabelForComponent()getListSizeCalculationSampleCount()getListeners()getMaxElementHeight()getMinElementHeight()getModel()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOrientation()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPopupPlacement()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getRenderer()getRenderingPrototype()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedIndex()getSelectedItem()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()isActAsSpinnerDialog()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isCommandList()isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnoreFocusComponentWhenUnfocused()isIgnorePointerEvents()isIncludeSelectCancel()isLongPointerPressActionEnabled()isMutableRendererBackgrounds()isNumericKeyActions()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isShowingPopupDialog()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeActionListener(ActionListener)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeSelectionListener(SelectionListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)requestFocus()respondsToPointerEvents()scrollRectToVisible(Rectangle)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setActAsSpinnerDialog(boolean)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComboBoxImage(Image)setCommandList(boolean)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFireOnClick(boolean)setFixedSelection(int)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHint(String)setHint(String, Image)setHintIcon(Image)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnoreFocusComponentWhenUnfocused(boolean)setIgnorePointerEvents(boolean)setIncludeSelectCancel(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setInputOnFocus(boolean)setIsScrollVisible(boolean)setItemGap(int)setLabelForComponent(Label)setListCellRenderer(ListCellRenderer)setListSizeCalculationSampleCount(int)setLongPointerPressActionEnabled(boolean)setMaxElementHeight(int)setMinElementHeight(int)setModel(ListModel)setMutableRendererBackgrounds(boolean)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setNumericKeyActions(boolean)setOpaque(boolean)setOrientation(int)setOwner(Component)setPaintFocusBehindList(boolean)setPinchBlocksDragAndDrop(boolean)setPopupPlacement(int)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRenderer(ListCellRenderer)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollToSelected(boolean)setScrollVisible(boolean)setSelectCommandText(String)setSelectedIndex(int)setSelectedIndex(int, boolean)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)size()startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)isDefaultActAsSpinnerDialog()isDefaultIncludeSelectCancel()setDefaultActAsSpinnerDialog(boolean)setDefaultIncludeSelectCancel(boolean)")); index.put("com.codename1.ui.Command", splitMembers("actionPerformed(ActionEvent)equals(Object)getClientProperty(String)getCommandName()getDesktopMenu()getDesktopShortcutKeyChar()getDesktopShortcutModifiers()getDisabledIcon()getIcon()getIconFont()getIconGapMM()getId()getMaterialIcon()getMaterialIconSize()getPressedIcon()getRolloverIcon()hashCode()isDisposesDialog()isEnabled()putClientProperty(String, Object)setCommandName(String)setDesktopMenu(String)setDesktopShortcut(char)setDesktopShortcut(char, int)setDisabledIcon(Image)setDisposesDialog(boolean)setEnabled(boolean)setIcon(Image)setIconFont(Font)setIconGapMM(float)setMaterialIcon(char)setMaterialIconSize(float)setPressedIcon(Image)setRolloverIcon(Image)toString()create(String, Image, ActionListener)createMaterial(String, char, ActionListener)")); index.put("com.codename1.ui.CommonProgressAnimations", splitMembers("")); @@ -3437,11 +3654,14 @@ private static void fillMethodIndex20(Map index) { index.put("com.codename1.ui.Container", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)drop(Component, int, int)findDropTargetAt(int, int)findFirstFocusable()flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeAll()removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)encloseIn(Layout, Component[]...)encloseIn(Layout, Component, Object)")); index.put("com.codename1.ui.DevicePosture", splitMembers("getFoldBounds(Rectangle)getFoldOrientation()getHingeAngle()getPosture()isFoldable()isSeparating()isTableTop()getInstance()")); index.put("com.codename1.ui.Dialog", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addCommand(Command)addCommand(Command, int)addCommandListener(ActionListener)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addComponentAwaitingRelease(Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addGameKeyListener(int, ActionListener)addKeyListener(int, ActionListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addOrientationListener(ActionListener)addPasteListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addShowListener(ActionListener)addSizeChangedListener(ActionListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()checkPopGuard(PopReason)clearClientProperties()clearComponentsAwaitingRelease()configureCommands(Command[], boolean)contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)deregisterAnimated(Animation)dispatchCommand(Command, ActionEvent)dispatchPaste(ActionEvent)dispose()drop(Component, int, int)findCurrentlyEditingComponent()findDropTargetAt(int, int)findFirstFocusable()findNextFocusHorizontal(boolean)findNextFocusVertical(boolean)flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBackCommand()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBlurBackgroundRadius()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClearCommand()getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getCommand(int)getCommandCount()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getContentPane()getCurrentInputDevice()getCursor()getDefaultCommand()getDialogComponent()getDialogPosition()getDialogPreferredSize()getDialogStyle()getDialogType()getDialogUIID()getDirtyRegion()getDisabledStyle()getDragRegionStatus(int, int)getDragTransparency()getDraggedx()getDraggedy()getEditOnShow()getEditingDelegate()getFocused()getFormLayeredPane(Class, boolean)getGlassPane()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getInvisibleAreaUnderVKB()getLabelForComponent()getLayeredPane()getLayeredPane(Class, boolean)getLayeredPane(Class, int)getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getMenuBar()getMenuStyle()getName()getNativeOverlay()getNextComponent(Component)getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPopGuard()getPopupDirectionBiasPortrait()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPreviousComponent(Component)getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeArea()getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getSoftButton(int)getSoftButtonCount()getSourceCommand()getStyle()getTabIndex()getTabIterator(Component)getTensileLength()getTextSelection()getTextSelectionSupport()getTintColor()getTitle()getTitleArea()getTitleComponent()getTitleStyle()getToolbar()getTooltip()getTransitionInAnimator()getTransitionOutAnimator()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()grabAnimationLock()growOrShrink()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasMedia()invalidate()isAlwaysTensile()isAutoDispose()isBlockLead()isCellRenderer()isChildOf(Container)isCyclicFocus()isDisposeWhenPointerOutOfBounds()isDragRegion(int, int)isDraggable()isDropTarget()isEditable()isEditing()isEnableCursors()isEnabled()isFlatten()isFocusScrolling()isFocusable()isFormBottomPaddingEditingMode()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isInteractionDialogMode()isMinimizeOnBack()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollable()isScrollableX()isScrollableY()isSingleFocusMode()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTitleCentered()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)paint(Graphics)paintBackground(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)placeButtonCommands(Command[])pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)registerAnimated(Animation)releaseAnimationLock()remove()removeAll()removeAllCommands()removeAllShowListeners()removeCommand(Command)removeCommandListener(ActionListener)removeComponent(Component)removeComponentAwaitingRelease(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeGameKeyListener(int, ActionListener)removeKeyListener(int, ActionListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removeOrientationListener(ActionListener)removePasteListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeShowListener(ActionListener)removeSizeChangedListener(ActionListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAllowEnableLayoutOnPaint(boolean)setAlwaysTensile(boolean)setAutoDispose(boolean)setBackCommand(Command)setBackCommand(String, Image, ActionListener)setBgImage(Image)setBlockLead(boolean)setBlurBackgroundRadius(float)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setClearCommand(Command)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCurrentInputDevice(VirtualInputDevice)setCursor(int)setCyclicFocus(boolean)setDefaultCommand(Command)setDialogPosition(String)setDialogStyle(Style)setDialogType(int)setDialogUIID(String)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDisposeWhenPointerOutOfBounds(boolean)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditOnShow(TextArea)setEditingDelegate(Editable)setEnableCursors(boolean)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusScrolling(boolean)setFocusable(boolean)setFocused(Component)setFormBottomPaddingEditingMode(boolean)setGlassPane(Painter)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setInteractionDialogMode(boolean)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setMenuBar(MenuBar)setMenuCellRenderer(ListCellRenderer)setMenuTransitions(Transition, Transition)setMinimizeOnBack(boolean)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOverrideInvisibleAreaUnderVKB(int)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPopGuard(PopGuard)setPopupDirectionBiasPortrait(Boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPreviousForm(Form)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaChanged()setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setSourceCommand(Command)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTimeout(long)setTintColor(int)setTitle(String)setTitleCentered(boolean)setTitleComponent(Label)setTitleComponent(Label, Transition)setTitleStyle(Style)setToolBar(Toolbar)setToolbar(Toolbar)setTooltip(String)setTransitionInAnimator(Transition)setTransitionOutAnimator(Transition)setTransitions(Transition)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIIDByPopupPosition(boolean)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)show()show(int, int, int, int)show(int, int, int, int, boolean)show(int, int, int, int, boolean, boolean)showAtPosition(int, int, int, int, boolean)showBack()showDialog()showModeless()showPacked(String, boolean)showPopupDialog(Component)showPopupDialog(Rectangle)showStetched(String, boolean)showStretched(String, boolean)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)wasDisposedDueToOutOfBoundsTouch()wasDisposedDueToRotation()getDefaultBlurBackgroundRadius()getDefaultDialogPosition()getDefaultDialogType()isAutoAdjustDialogSize()isCommandsAsButtons()isDefaultDisposeWhenPointerOutOfBounds()isDefaultInteractionDialogMode()isDefaultTitleCentered()isDisableStaticDialogScrolling()setAutoAdjustDialogSize(boolean)setCommandsAsButtons(boolean)setDefaultBlurBackgroundRadius(float)setDefaultDialogPosition(String)setDefaultDialogType(int)setDefaultDisposeWhenPointerOutOfBounds(boolean)setDefaultInteractionDialogMode(boolean)setDefaultTitleCentered(boolean)setDisableStaticDialogScrolling(boolean)show(String, Component, Command[]...)show(String, String, Command[]...)show(String, String, String, String)show(String, Component, Command[], int, Image)show(String, String, int, Image, String, String)show(String, String, Command[], int, Image, long)show(String, Component, Command[], int, Image, long)show(String, String, int, Image, String, String, long)show(String, String, Command, Command[], int, Image, long)show(String, String, Command[], int, Image, long, Transition)show(String, Component, Command[], int, Image, long, Transition)show(String, String, Command, Command[], int, Image, long, Transition)show(String, Component, Command, Command[], int, Image, long, Transition)")); - index.put("com.codename1.ui.Display", splitMembers("accessibilityTreeChanged(int)addCompletionHandler(Media, Runnable)addEdtErrorHandler(ActionListener)addMessageListener(ActionListener)addPostureListener(ActionListener)addVirtualKeyboardListener(ActionListener)addWindowListener(ActionListener)announceForAccessibility(String)announceForAccessibility(Component, String)areMutableImagesFast()callSerially(Runnable)callSeriallyAndWait(Runnable)callSeriallyAndWait(Runnable, int)callSeriallyOnIdle(Runnable)canDial()canExecute(String)canForceOrientation()canInstallOnHomescreen()cancelBackgroundProcessing(String)cancelBackgroundWork(String)cancelLocalNotification(String)captureAudio(ActionListener)captureAudio(MediaRecorderBuilder, ActionListener)capturePhoto(ActionListener)captureScreen()captureVideo(ActionListener)captureVideo(VideoCaptureConstraints, ActionListener)confirmAttestation(String)consumePendingNativeCrash()convertBidiLogicalToVisual(String)convertToPixels(float)convertToPixels(float, byte)convertToPixels(int, boolean)convertToPixels(float, byte, boolean)copyToClipboard(ClipboardContent)createBackgroundMedia(String)createBackgroundMediaAsync(String)createContact(String, String, String, String, String, String)createGpuPeer(RenderView)createMedia(String, boolean, Runnable)createMediaAsync(String, boolean, Runnable)createMediaRecorder(MediaRecorderBuilder)createMediaRecorder(String)createMediaRecorder(String, String)createNotificationChannelGroup(String, String)createSFSymbolImage(String, int, float, int)createSoftWeakRef(Object)createSoundPool(int)createThread(Runnable, String)delete(String)deleteContact(String)deleteNotificationChannel(String)deregisterPush()dial(String)dismissNotification(Object)dispatchMessage(MessageEvent)downloadBytesAsFile(String, byte[])editString(Component, int, int, String)editString(Component, int, int, String, int)execute(String)execute(String, ActionListener)exists(String)exitApplication()exitFullScreen()extractHardRef(Object)fireMagnifyGesture(int, int, float)fireMouseWheelEvent(int, int, int, int, boolean, int)fireRotationGesture(int, int, float)fireVirtualKeyboardEvent(boolean)fireWindowEvent(WindowEvent)flashBacklight(int)gaussianBlurImage(Image, float)getAllContacts(boolean)getAllContacts(boolean, boolean, boolean, boolean, boolean, boolean)getAppSignerDigests()getAvailableRecordingMimeTypes()getBiometrics()getBluetooth()getBonjourPlatform()getCarBridge()getCharLocation(String, int)getClipboardContent()getCodeScanner()getColorVisionDeficiency()getCommandBehavior()getCompromiseReasons()getContactById(String)getContactById(String, boolean, boolean, boolean, boolean, boolean)getCrashReporter()getCurrent()getCurrentPointerEvent()getDatabasePath(String)getDensityStr()getDesktopSize()getDeviceDensity()getDevicePosture()getDisplayCount()getDisplayHeight()getDisplaySafeArea(Rectangle)getDisplayWidth()getDragSpeed(boolean)getDragStartPercentage()getEnabledAccessibilityServices()getFrameRate()getGameAction(int)getHealth()getImageIO()getInAppPurchase()getInAppPurchase(boolean)getInitialWindowSizeHintPercent()getInvisibleAreaUnderVKB()getKeyCode(int)getKeyboardType()getLargerTextScale()getLineSeparator()getLinkedContactIds(Contact)getLocalCalendarSource()getLocalizationManager()getLocationManager()getLongPointerPressInterval()getMediaRecorderingMimeType()getMotionSensorManager()getMsisdn()getNativeLogSnapshot()getNetworkTypePlatform()getNfc()getPasteDataFromClipboard()getPlatformName()getPlatformOverrides()getPluginSupport()getPointerButton()getPointerContactSize()getPointerPressure()getPointerTiltX()getPointerTiltY()getPointerType()getPreferredBackgroundFetchInterval(int)getPressedButtonMask()getProjectBuildHints()getProperty(String, String)getSMSSupport()getSecureStorage()getSharedJavascriptContext()getShowDuringEditBehavior()getStackTrace(Thread, Throwable)getSupportedVirtualKeyboard()getSurfaceBridge()getUdid()getUsbPlatform()getVideoIO()getVirtualKeyboardListener()getWearableBridge()getWifiDirectPlatform()getWifiPlatform()getWindowBounds()gpuRequestRender(PeerComponent)gpuSetContinuous(PeerComponent, boolean)hasCamera()hasDragOccured()hasNativeTheme()hideNotify()installNativeCrashHandler()installNativeTheme()invokeAndBlock(Runnable)invokeAndBlock(Runnable, boolean)invokeWithoutBlocking(Runnable)invokeWithoutBlockingWithResultSync(RunnableWithResultSync)isAccessibilityTreeSupported()isAccessibilityTreeUpdateRequired()isAllowMinimizing()isAltGraphKeyDown()isAltKeyDown()isAttestationSupported()isAutoFoldVKBOnFormSwitch()isBackgroundFetchSupported()isBackgroundProcessingSupported()isBackgroundWorkSupported()isBadgingSupported()isBidiAlgorithm()isBoldTextEnabled()isBuiltinSoundAvailable(String)isBuiltinSoundsEnabled()isCallDetectionSupported()isCarConnected()isClickTouchScreen()isContactsPermissionGranted()isControlKeyDown()isDarkMode()isDatabaseCustomPathSupported()isDebuggableBuild()isDesktop()isDesktopMode()isDeviceCompromised()isDifferentiateWithoutColorEnabled()isEdt()isEnableAsyncStackTraces()isExternalDisplayConnected()isFoldable()isForegroundServiceSupported()isFullScreenSupported()isGalleryTypeSupported(int)isGaussianBlurSupported()isGetAllContactsFast()isGpuSupported()isGrayscaleEnabled()isHighContrastEnabled()isInCall()isInFullScreenMode()isInTransition()isInvertColorsEnabled()isJailbrokenDevice()isLargerTextEnabled()isLockOrientation()isMetaKeyDown()isMinimized()isMultiKeyMode()isMultiTouch()isNativeCommands()isNativeInAppReviewSupported()isNativeInputSupported()isNativePickerTypeSupported(int)isNativeShareSupported()isNativeTitle()isNativeVideoPlayerControlsIncluded()isNotificationSupported()isOnOffSwitchLabelsEnabled()isOpenNativeNavigationAppSupported()isPortrait()isPrintingSupported()isPureTouch()isRTL(char)isReceiveSharedContentSupported()isReduceMotionEnabled()isReduceTransparencyEnabled()isRightMouseButtonDown()isScreenReaderEnabled()isScreenSaverDisableSupported()isScrollWheeling()isShiftKeyDown()isSimulator()isSoundPoolSupported()isSpeechRecognitionSupported()isStylusPointer()isTV()isTablet()isTextToSpeechSupported()isThirdSoftButton()isTouchScreenDevice()isVirtualKeyboardShowing()isWalletExtensionSupported()isWatch()keyPressed(int)keyReleased(int)lockOrientation(boolean)minimizeApplication()notifyPushCompletion()notifyStatusBar(String, String, String, boolean, boolean)notifyStatusBar(String, String, String, boolean, boolean, Hashtable)numAlphaLevels()numColors()onCanInstallOnHomescreen(Runnable)onEditingComplete(Component, String)openFileChooser(ActionListener, String)openGallery(ActionListener, int)openImageGallery(ActionListener)openNativeNavigationApp(String)openNativeNavigationApp(double, double)openOrCreate(String)platformUsesInputMode()playBuiltinSound(String)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int[], int[])pointerReleased(int[], int[])postMessage(MessageEvent)postureChanged()print(String, String, PrintResultListener)promptInstallOnHomescreen()refreshContacts()refreshNativeTitle()registerNotificationChannel(NotificationChannelBuilder)registerPush()registerPush(String, boolean)registerPush(Hashtable, boolean)removeCompletionHandler(Media, Runnable)removeEdtErrorHandler(ActionListener)removeMessageListener(ActionListener)removePostureListener(ActionListener)removeVirtualKeyboardListener(ActionListener)removeWindowListener(ActionListener)requestFullScreen()requestIntegrityToken(String)requestNativeInAppReview(SuccessCallback)requestNotificationPermission(NotificationPermissionCallback)requestNotificationPermission(NotificationPermissionRequest, NotificationPermissionCallback)resetAttestation()restoreMinimizedApplication()restoreToBookmark()scheduleBackgroundProcessing(String, long, boolean, boolean, Runnable)scheduleBackgroundTask(Runnable)scheduleBackgroundWork(WorkRequest)scheduleLocalNotification(LocalNotification, long, int)screenshot(SuccessCallback)sendMessage(String[], String, Message)sendSMS(String, String)sendSMS(String, String, boolean)setAllowMinimizing(boolean)setAutoFoldVKBOnFormSwitch(boolean)setBadgeNumber(int)setBidiAlgorithm(boolean)setBookmark(Runnable)setBuiltinSoundsEnabled(boolean)setCommandBehavior(int)setCrashReporter(CrashReport)setDarkMode(Boolean)setDragStartPercentage(int)setEnableAsyncStackTraces(boolean)setFramerate(int)setInitialWindowSizeHintPercent(Dimension)setInterval(int, Runnable)setLongPointerPressInterval(int)setMultiKeyMode(boolean)setNativeCommands(boolean)setNoSleep(boolean)setPollingFrequency(int)setPreferredBackgroundFetchInterval(int)setProjectBuildHint(String, String)setProperty(String, String)setPureTouch(boolean)setScreenSaverEnabled(boolean)setSecureScreen(boolean)setShowDuringEditBehavior(int)setShowVirtualKeyboard(boolean)setThirdSoftButton(boolean)setTimeout(int, Runnable)setTouchScreenDevice(boolean)setTransitionYield(int)setVirtualKeyboardListener(ActionListener)setWindowSize(int, int)share(String)share(String, String, String)share(String, String, String, Rectangle)share(String, String, String, Rectangle, ShareResultListener)shouldRenderSelection()shouldRenderSelection(Component)showNativePicker(int, Component, Object, Object)showNativeScreen(Object)showNotify()sizeChanged(int, int)startForegroundService(String, String, String, String, Task, ForegroundService)startRemoteControl()startSpeechRecognition(RecognitionOptions, RecognitionCallback)startThread(Runnable, String)stopEditing(Component)stopEditing(Component, Runnable)stopForegroundService(Object)stopRemoteControl()stopSpeechRecognition()subscribeToPushTopic(String)textToSpeechAvailableVoices()textToSpeechSpeak(String, TtsOptions)textToSpeechStop()unlockOrientation()unsubscribeFromPushTopic(String)updateForegroundServiceNotification(Object, String, String)vibrate(int)walletExtensionClear()walletExtensionSetAuthToken(String)walletExtensionSetPassEntries(boolean, WalletPassEntry[])walletExtensionSetRequiresAuthentication(boolean)deinitialize()getInstance()init(Object)isInitialized()")); + index.put("com.codename1.ui.Display", splitMembers("accessibilityTreeChanged(int)addCompletionHandler(Media, Runnable)addEdtErrorHandler(ActionListener)addMessageListener(ActionListener)addPostureListener(ActionListener)addTapjackingListener(ActionListener)addVirtualKeyboardListener(ActionListener)addWindowListener(ActionListener)announceForAccessibility(String)announceForAccessibility(Component, String)areMutableImagesFast()callSerially(Runnable)callSeriallyAndWait(Runnable)callSeriallyAndWait(Runnable, int)callSeriallyOnIdle(Runnable)canDial()canExecute(String)canForceOrientation()canInstallOnHomescreen()cancelBackgroundProcessing(String)cancelBackgroundWork(String)cancelLocalNotification(String)captureAudio(ActionListener)captureAudio(MediaRecorderBuilder, ActionListener)capturePhoto(ActionListener)captureScreen()captureVideo(ActionListener)captureVideo(VideoCaptureConstraints, ActionListener)confirmAttestation(String)consumePendingNativeCrash()convertBidiLogicalToVisual(String)convertToPixels(float)convertToPixels(float, byte)convertToPixels(int, boolean)convertToPixels(float, byte, boolean)copyToClipboard(ClipboardContent)createBackgroundMedia(String)createBackgroundMediaAsync(String)createContact(String, String, String, String, String, String)createGpuPeer(RenderView)createMedia(String, boolean, Runnable)createMediaAsync(String, boolean, Runnable)createMediaRecorder(MediaRecorderBuilder)createMediaRecorder(String)createMediaRecorder(String, String)createNotificationChannelGroup(String, String)createSFSymbolImage(String, int, float, int)createSoftWeakRef(Object)createSoundPool(int)createThread(Runnable, String)databaseIdentityForEngineFile(String)databaseManagedKeyIdentity(String)databaseRegistryIdentity(String)delete(String)deleteContact(String)deleteNotificationChannel(String)deregisterPush()dial(String)dismissNotification(Object)dispatchMessage(MessageEvent)downloadBytesAsFile(String, byte[])editString(Component, int, int, String)editString(Component, int, int, String, int)execute(String)execute(String, ActionListener)exists(String)exitApplication()exitFullScreen()extractHardRef(Object)fireMagnifyGesture(int, int, float)fireMouseWheelEvent(int, int, int, int, boolean, int)fireRotationGesture(int, int, float)fireVirtualKeyboardEvent(boolean)fireWindowEvent(WindowEvent)flashBacklight(int)gaussianBlurImage(Image, float)getAllContacts(boolean)getAllContacts(boolean, boolean, boolean, boolean, boolean, boolean)getAppSignerDigests()getAvailableRecordingMimeTypes()getBiometrics()getBluetooth()getBonjourPlatform()getCarBridge()getCharLocation(String, int)getClipboardContent()getCodeScanner()getColorVisionDeficiency()getCommandBehavior()getCompromiseReasons()getContactById(String)getContactById(String, boolean, boolean, boolean, boolean, boolean)getCrashReporter()getCurrent()getCurrentPointerEvent()getDatabasePath(String)getDensityStr()getDesktopSize()getDeviceDensity()getDevicePosture()getDisplayCount()getDisplayHeight()getDisplaySafeArea(Rectangle)getDisplayWidth()getDragSpeed(boolean)getDragStartPercentage()getEnabledAccessibilityServices()getFrameRate()getGameAction(int)getHealth()getHomeBridge()getImageIO()getInAppPurchase()getInAppPurchase(boolean)getInitialWindowSizeHintPercent()getIntentBridge()getInvisibleAreaUnderVKB()getKeyCode(int)getKeyboardType()getLargerTextScale()getLineSeparator()getLinkedContactIds(Contact)getLocalCalendarSource()getLocalizationManager()getLocationManager()getLongPointerPressInterval()getMediaRecorderingMimeType()getMotionSensorManager()getMsisdn()getNativeLogSnapshot()getNetworkTypePlatform()getNfc()getPasteDataFromClipboard()getPlatformName()getPlatformOverrides()getPluginSupport()getPointerButton()getPointerContactSize()getPointerPressure()getPointerTiltX()getPointerTiltY()getPointerType()getPreferredBackgroundFetchInterval(int)getPressedButtonMask()getProjectBuildHints()getProperty(String, String)getSMSSupport()getSecureStorage()getSharedJavascriptContext()getShowDuringEditBehavior()getStackTrace(Thread, Throwable)getSupportedVirtualKeyboard()getSurfaceBridge()getTapjackingPolicy()getUdid()getUsbPlatform()getVideoIO()getVirtualKeyboardListener()getWearableBridge()getWifiDirectPlatform()getWifiPlatform()getWindowBounds()gpuRequestRender(PeerComponent)gpuSetContinuous(PeerComponent, boolean)hasCamera()hasDragOccured()hasNativeTheme()hideNotify()installNativeCrashHandler()installNativeTheme()invokeAndBlock(Runnable)invokeAndBlock(Runnable, boolean)invokeWithoutBlocking(Runnable)invokeWithoutBlockingWithResultSync(RunnableWithResultSync)isAccessibilityTreeSupported()isAccessibilityTreeUpdateRequired()isAllowMinimizing()isAltGraphKeyDown()isAltKeyDown()isAttestationSupported()isAutoFoldVKBOnFormSwitch()isBackgroundFetchSupported()isBackgroundProcessingSupported()isBackgroundWorkSupported()isBadgingSupported()isBidiAlgorithm()isBlobQueryParameterSupported()isBoldTextEnabled()isBuiltinSoundAvailable(String)isBuiltinSoundsEnabled()isCallDetectionSupported()isCarConnected()isClickTouchScreen()isContactsPermissionGranted()isControlKeyDown()isDarkMode()isDatabaseCustomPathSupported()isDatabaseEncryptionSupported()isDatabaseFileEncrypted(String)isDatabaseManagedKeyHardwareBacked()isDebuggableBuild()isDesktop()isDesktopMode()isDeviceCompromised()isDifferentiateWithoutColorEnabled()isEdt()isEnableAsyncStackTraces()isExternalDisplayConnected()isFoldable()isForegroundServiceSupported()isFullScreenSupported()isGalleryTypeSupported(int)isGaussianBlurSupported()isGetAllContactsFast()isGpuSupported()isGrayscaleEnabled()isHideOverlayWindowsSupported()isHighContrastEnabled()isInCall()isInFullScreenMode()isInTransition()isInvertColorsEnabled()isJailbrokenDevice()isLargerTextEnabled()isLockOrientation()isMetaKeyDown()isMinimized()isMultiKeyMode()isMultiTouch()isNativeCommands()isNativeInAppReviewSupported()isNativeInputSupported()isNativePickerTypeSupported(int)isNativeShareSupported()isNativeTitle()isNativeVideoPlayerControlsIncluded()isNotificationSupported()isOnOffSwitchLabelsEnabled()isOpenNativeNavigationAppSupported()isPortrait()isPrintingSupported()isPureTouch()isRTL(char)isReceiveSharedContentSupported()isReduceMotionEnabled()isReduceTransparencyEnabled()isRelativeAttachmentNameResolvable()isRightMouseButtonDown()isScreenObscured()isScreenReaderEnabled()isScreenSaverDisableSupported()isScrollWheeling()isShiftKeyDown()isSimulator()isSoundPoolSupported()isSpeechRecognitionSupported()isStylusPointer()isTV()isTablet()isTextToSpeechSupported()isThirdSoftButton()isTouchScreenDevice()isVirtualKeyboardShowing()isWalletExtensionSupported()isWatch()keyPressed(int)keyReleased(int)lockOrientation(boolean)minimizeApplication()notifyPushCompletion()notifyStatusBar(String, String, String, boolean, boolean)notifyStatusBar(String, String, String, boolean, boolean, Hashtable)numAlphaLevels()numColors()onCanInstallOnHomescreen(Runnable)onEditingComplete(Component, String)openDatabaseConnections(String)openFileChooser(ActionListener, String)openGallery(ActionListener, int)openImageGallery(ActionListener)openNativeNavigationApp(String)openNativeNavigationApp(double, double)openOrCreate(String)openOrCreate(String, DatabaseConfig)openOrCreateForRekey(String)platformUsesInputMode()playBuiltinSound(String)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int[], int[])pointerReleased(int[], int[])postMessage(MessageEvent)postureChanged()print(String, String, PrintResultListener)promptInstallOnHomescreen()refreshContacts()refreshNativeTitle()registerNotificationChannel(NotificationChannelBuilder)registerPush()registerPush(String, boolean)registerPush(Hashtable, boolean)removeCompletionHandler(Media, Runnable)removeEdtErrorHandler(ActionListener)removeMessageListener(ActionListener)removePostureListener(ActionListener)removeTapjackingListener(ActionListener)removeVirtualKeyboardListener(ActionListener)removeWindowListener(ActionListener)requestFullScreen()requestIntegrityToken(String)requestNativeInAppReview(SuccessCallback)requestNotificationPermission(NotificationPermissionCallback)requestNotificationPermission(NotificationPermissionRequest, NotificationPermissionCallback)resetAttestation()restoreMinimizedApplication()restoreToBookmark()scheduleBackgroundProcessing(String, long, boolean, boolean, Runnable)scheduleBackgroundTask(Runnable)scheduleBackgroundWork(WorkRequest)scheduleLocalNotification(LocalNotification, long, int)screenshot(SuccessCallback)sendMessage(String[], String, Message)sendSMS(String, String)sendSMS(String, String, boolean)setAllowMinimizing(boolean)setAutoFoldVKBOnFormSwitch(boolean)setBadgeNumber(int)setBidiAlgorithm(boolean)setBookmark(Runnable)setBuiltinSoundsEnabled(boolean)setCommandBehavior(int)setCrashReporter(CrashReport)setDarkMode(Boolean)setDragStartPercentage(int)setEnableAsyncStackTraces(boolean)setFramerate(int)setHideOverlayWindows(boolean)setInitialWindowSizeHintPercent(Dimension)setInterval(int, Runnable)setLongPointerPressInterval(int)setMultiKeyMode(boolean)setNativeCommands(boolean)setNoSleep(boolean)setPollingFrequency(int)setPreferredBackgroundFetchInterval(int)setProjectBuildHint(String, String)setProperty(String, String)setPureTouch(boolean)setScreenSaverEnabled(boolean)setSecureScreen(boolean)setShowDuringEditBehavior(int)setShowVirtualKeyboard(boolean)setTapjackingProtection(TapjackingPolicy)setThirdSoftButton(boolean)setTimeout(int, Runnable)setTouchScreenDevice(boolean)setTransitionYield(int)setVirtualKeyboardListener(ActionListener)setWindowSize(int, int)share(String)share(String, String, String)share(String, String, String, Rectangle)share(String, String, String, Rectangle, ShareResultListener)shouldRenderSelection()shouldRenderSelection(Component)showNativePicker(int, Component, Object, Object)showNativeScreen(Object)showNotify()sizeChanged(int, int)startForegroundService(String, String, String, String, Task, ForegroundService)startRemoteControl()startSpeechRecognition(RecognitionOptions, RecognitionCallback)startThread(Runnable, String)stopEditing(Component)stopEditing(Component, Runnable)stopForegroundService(Object)stopRemoteControl()stopSpeechRecognition()subscribeToPushTopic(String)textToSpeechAvailableVoices()textToSpeechSpeak(String, TtsOptions)textToSpeechStop()unlockOrientation()unsubscribeFromPushTopic(String)updateForegroundServiceNotification(Object, String, String)vibrate(int)walletExtensionClear()walletExtensionSetAuthToken(String)walletExtensionSetPassEntries(boolean, WalletPassEntry[])walletExtensionSetRequiresAuthentication(boolean)deinitialize()getInstance()init(Object)isInitialized()")); index.put("com.codename1.ui.DynamicImage", splitMembers("addActionListener(ActionListener)animate()applyMask(Object)applyMask(Object, int, int)applyMaskAutoScale(Object)asyncLock(Image)createMask()dispose()fill(int, int)fireChangedEvent()flipHorizontally(boolean)flipVertically(boolean)getGraphics()getHeight()getImage()getImageName()getRGB()getRGB(int[])getRGBCached()getSVGDocument()getStyle()getWidth()isAnimation()isLocked()isOpaque()isSVG()lock()mirror()modifyAlpha(byte)modifyAlpha(byte, int)modifyAlphaWithTranslucency(byte)removeActionListener(ActionListener)requiresDrawImage()rotate(int)rotate180Degrees(boolean)rotate270Degrees(boolean)rotate90Degrees(boolean)scale(int, int)scaled(int, int)scaledHeight(int)scaledLargerRatio(int, int)scaledSmallerRatio(int, int)scaledWidth(int)setImageName(String)setStyle(Style)subImage(int, int, int, int, boolean)toRGB(RGBImage, int, int, int, int, int, int)unlock()setIcon(Label, DynamicImage)")); - index.put("com.codename1.ui.EditField", splitMembers("accessibilityChanged()accessibilityChanged(int)addActionListener(ActionListener)addContextMenuListener(ActionListener)addDataChangedListener(DataChangedListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()blur()clearClientProperties()commitText(String)contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)deleteSurroundingText(int, int)drop(Component, int, int)editorChanged()finishComposing()fireEditorEvent(String, String)getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getCaretOffset()getCaretRect()getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getColumns()getComponentForm()getComponentState()getConfig()getConstraint()getCursor()getDirtyRegion()getDisabledStyle()getDocument()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEditingState()getHeight()getHint()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getRows()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSelectionEnd()getSelectionStart()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getText()getTextLength()getTextRange(int, int)getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUndoManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasSelection()inputFocusGained()inputFocusLost()insertText(String)isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditableState()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSingleLineTextArea()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTextInputSupported()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)moveCaret(int, boolean)offsetAtPoint(int, int)onEditorAction(int)onKeyCommand(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)performRedo()performUndo()pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)rectForOffset(int)refreshTheme()refreshTheme(boolean)remove()removeActionListener(ActionListener)removeContextMenuListener(ActionListener)removeDataChangedListener(DataChangedListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replaceRange(int, int, String)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)selectAll()selectionRects(int, int)setAccessibilityText(String)setActionType(int)setAlwaysTensile(boolean)setBackgroundColor(int)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setColumns(int)setComponentState(Object)setComposingText(String, int)setConstraint(int)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditable(boolean)setEditableState(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontSizeDips(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHint(String)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setRows(int)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setSelectionColor(int)setSelectionRange(int, int)setShouldCalcPreferredSize(boolean)setSingleLineTextArea(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextColor(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()startTextInput(TextInputClient, TextInputConfig)stopEditing(Runnable)stopTextInput(Object)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTextInputState(Object, TextInputState)visibleBoundsContains(int, int)")); + index.put("com.codename1.ui.EditField", splitMembers("accessibilityChanged()accessibilityChanged(int)addActionListener(ActionListener)addContextMenuListener(ActionListener)addDataChangedListener(DataChangedListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()blur()clearClientProperties()commitText(String)contains(int, int)containsOrOwns(int, int)copySelection()createStyleAnimation(String, int)cutSelection()deleteBackward()deleteSurroundingText(int, int)drop(Component, int, int)editorChanged()finishComposing()fireEditorEvent(String, String)getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getCaretOffset()getCaretRect()getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getColumns()getComponentForm()getComponentState()getConfig()getConstraint()getCursor()getDirtyRegion()getDisabledStyle()getDocument()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEditingState()getHeight()getHint()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getRows()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSelectionEnd()getSelectionStart()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getText()getTextLength()getTextRange(int, int)getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUndoManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasSelection()inputFocusGained()inputFocusLost()insertText(String)isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditableState()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSingleLineTextArea()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTextInputSupported()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)moveCaret(int, boolean)offsetAtPoint(int, int)onEditorAction(int)onKeyCommand(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pasteClipboard()performRedo()performUndo()pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)rectForOffset(int)refreshTheme()refreshTheme(boolean)remove()removeActionListener(ActionListener)removeContextMenuListener(ActionListener)removeDataChangedListener(DataChangedListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replaceRange(int, int, String)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)selectAll()selectionRects(int, int)setAccessibilityText(String)setActionType(int)setAlwaysTensile(boolean)setBackgroundColor(int)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setColumns(int)setComponentState(Object)setComposingText(String, int)setConstraint(int)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditable(boolean)setEditableState(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontSizeDips(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHint(String)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setRows(int)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setSelectionColor(int)setSelectionRange(int, int)setShouldCalcPreferredSize(boolean)setSingleLineTextArea(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextColor(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()startTextInput(TextInputClient, TextInputConfig)stopEditing(Runnable)stopTextInput(Object)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTextInputState(Object, TextInputState)visibleBoundsContains(int, int)")); index.put("com.codename1.ui.Editable", splitMembers("isEditable()isEditing()startEditingAsync()stopEditing(Runnable)")); index.put("com.codename1.ui.EncodedImage", splitMembers("addActionListener(ActionListener)animate()applyMask(Object)applyMask(Object, int, int)applyMaskAutoScale(Object)asyncLock(Image)createMask()dispose()fill(int, int)fireChangedEvent()flipHorizontally(boolean)flipVertically(boolean)getGraphics()getHeight()getImage()getImageData()getImageName()getRGB()getRGB(int[])getRGBCached()getSVGDocument()getWidth()isAnimation()isDisposed()isLocked()isOpaque()isSVG()lock()mirror()modifyAlpha(byte)modifyAlpha(byte, int)modifyAlphaWithTranslucency(byte)removeActionListener(ActionListener)requiresDrawImage()rotate(int)rotate180Degrees(boolean)rotate270Degrees(boolean)rotate90Degrees(boolean)scale(int, int)scaled(int, int)scaledEncoded(int, int)scaledHeight(int)scaledLargerRatio(int, int)scaledSmallerRatio(int, int)scaledWidth(int)setImageName(String)subImage(int, int, int, int, boolean)toRGB(RGBImage, int, int, int, int, int, int)unlock()create(String)create(byte[])create(byte[], int, int, boolean)createFromImage(Image, boolean)createFromRGB(int[], int, int, boolean)createMulti(int[], byte[][])")); + } + + private static void fillMethodIndex22(Map index) { index.put("com.codename1.ui.Font", splitMembers("addContrast(byte)charWidth(char)charsWidth(char[], int, int)derive(float, int)derive(float, int, byte)deriveLetterSpacing(float)equals(Object)getAscent()getCharset()getDescent()getFace()getHeight()getNativeFont()getPixelSize()getSize()getStyle()hashCode()isTTFNativeFont()stringWidth(String)substringWidth(String, int, int)clearBitmapCache()clearDerivedFontCache()create(String)createBitmapFont(Image, int[], int[], String)createBitmapFont(String, Image, int[], int[], String)createSystemFont(int, int, int)createTrueTypeFont(String)createTrueTypeFont(String, float)createTrueTypeFont(String, String)createTrueTypeFont(String, float, byte)getBitmapFont(String)getDefaultFont()isBitmapFontEnabled()isCreationByStringSupported()isNativeFontSchemeSupported()isTrueTypeFileSupported()setBitmapFontEnabled(boolean)setDefaultFont(Font)")); index.put("com.codename1.ui.FontImage", splitMembers("addActionListener(ActionListener)animate()applyMask(Object)applyMask(Object, int, int)applyMaskAutoScale(Object)asyncLock(Image)createMask()dispose()fill(int, int)fireChangedEvent()flipHorizontally(boolean)flipVertically(boolean)getFont()getGraphics()getHeight()getImage()getImageName()getPadding()getRGB()getRGB(int[])getRGBCached()getSVGDocument()getText()getWidth()isAnimation()isLocked()isOpaque()isSVG()lock()mirror()modifyAlpha(byte)modifyAlpha(byte, int)modifyAlphaWithTranslucency(byte)removeActionListener(ActionListener)requiresDrawImage()rotate(int)rotate180Degrees(boolean)rotate270Degrees(boolean)rotate90Degrees(boolean)rotateAnimation()scale(int, int)scaled(int, int)scaledHeight(int)scaledLargerRatio(int, int)scaledSmallerRatio(int, int)scaledWidth(int)setBgTransparency(int)setFgAlpha(int)setImageName(String)setPadding(int)subImage(int, int, int, int, boolean)toEncodedImage()toImage()toRGB(RGBImage, int, int, int, int, int, int)unlock()create(String, Style)create(String, Style, Font)createFixed(String, Font, int, int, int)createFixed(String, Font, int, int, int, int)createMaterial(char, Style)createMaterial(char, Style, float)createMaterial(char, String, float)createSFOrMaterial(char, Style, float)getDefaultPadding()getDefaultSize()getMaterialDesignFont()setDefaultPadding(int)setDefaultSize(float)setFontIcon(SpanButton, Font, char)setFontIcon(Label, Font, char)setFontIcon(MultiButton, Font, char, float)setFontIcon(SpanButton, Font, char, float)setFontIcon(SpanLabel, Font, char, float)setFontIcon(Label, Font, char, float)setFontIcon(Label, Font, char[], float)setFontIcon(Command, Font, char, String, float)setIcon(IconHolder, char, float)setIcon(IconHolder, Font, char, float)setIcon(IconHolder, Font, char[], float)setMaterialIcon(MultiButton, char)setMaterialIcon(SpanButton, char)setMaterialIcon(SpanLabel, char)setMaterialIcon(Label, char)setMaterialIcon(IconHolder, char)setMaterialIcon(MultiButton, char, float)setMaterialIcon(SpanButton, char, float)setMaterialIcon(SpanLabel, char, float)setMaterialIcon(Command, char, String)setMaterialIcon(Label, char, float)setMaterialIcon(Label, char[], float)setMaterialIcon(Component, char, float)setMaterialIcon(Component, char[], float)setMaterialIcon(Command, char, String, float)setMaterialIcon(Label, Font, char, float)")); index.put("com.codename1.ui.Form", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addCommand(Command)addCommand(Command, int)addCommandListener(ActionListener)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addComponentAwaitingRelease(Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addGameKeyListener(int, ActionListener)addKeyListener(int, ActionListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addOrientationListener(ActionListener)addPasteListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addShowListener(ActionListener)addSizeChangedListener(ActionListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()checkPopGuard(PopReason)clearClientProperties()clearComponentsAwaitingRelease()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)deregisterAnimated(Animation)dispatchCommand(Command, ActionEvent)dispatchPaste(ActionEvent)drop(Component, int, int)findCurrentlyEditingComponent()findDropTargetAt(int, int)findFirstFocusable()findNextFocusHorizontal(boolean)findNextFocusVertical(boolean)flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBackCommand()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClearCommand()getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getCommand(int)getCommandCount()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getContentPane()getCurrentInputDevice()getCursor()getDefaultCommand()getDirtyRegion()getDisabledStyle()getDragRegionStatus(int, int)getDragTransparency()getDraggedx()getDraggedy()getEditOnShow()getEditingDelegate()getFocused()getFormLayeredPane(Class, boolean)getGlassPane()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getInvisibleAreaUnderVKB()getLabelForComponent()getLayeredPane()getLayeredPane(Class, boolean)getLayeredPane(Class, int)getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getMenuBar()getMenuStyle()getName()getNativeOverlay()getNextComponent(Component)getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPopGuard()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPreviousComponent(Component)getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeArea()getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getSoftButton(int)getSoftButtonCount()getSourceCommand()getStyle()getTabIndex()getTabIterator(Component)getTensileLength()getTextSelection()getTextSelectionSupport()getTintColor()getTitle()getTitleArea()getTitleComponent()getTitleStyle()getToolbar()getTooltip()getTransitionInAnimator()getTransitionOutAnimator()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()grabAnimationLock()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasMedia()invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isCyclicFocus()isDragRegion(int, int)isDraggable()isDropTarget()isEditable()isEditing()isEnableCursors()isEnabled()isFlatten()isFocusScrolling()isFocusable()isFormBottomPaddingEditingMode()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isMinimizeOnBack()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollable()isScrollableX()isScrollableY()isSingleFocusMode()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)paint(Graphics)paintBackground(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)registerAnimated(Animation)releaseAnimationLock()remove()removeAll()removeAllCommands()removeAllShowListeners()removeCommand(Command)removeCommandListener(ActionListener)removeComponent(Component)removeComponentAwaitingRelease(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeGameKeyListener(int, ActionListener)removeKeyListener(int, ActionListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removeOrientationListener(ActionListener)removePasteListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeShowListener(ActionListener)removeSizeChangedListener(ActionListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAllowEnableLayoutOnPaint(boolean)setAlwaysTensile(boolean)setBackCommand(Command)setBackCommand(String, Image, ActionListener)setBgImage(Image)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setClearCommand(Command)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCurrentInputDevice(VirtualInputDevice)setCursor(int)setCyclicFocus(boolean)setDefaultCommand(Command)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditOnShow(TextArea)setEditingDelegate(Editable)setEnableCursors(boolean)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusScrolling(boolean)setFocusable(boolean)setFocused(Component)setFormBottomPaddingEditingMode(boolean)setGlassPane(Painter)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setMenuBar(MenuBar)setMenuCellRenderer(ListCellRenderer)setMenuTransitions(Transition, Transition)setMinimizeOnBack(boolean)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOverrideInvisibleAreaUnderVKB(int)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPopGuard(PopGuard)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaChanged()setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setSourceCommand(Command)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTintColor(int)setTitle(String)setTitleComponent(Label)setTitleComponent(Label, Transition)setTitleStyle(Style)setToolBar(Toolbar)setToolbar(Toolbar)setTooltip(String)setTransitionInAnimator(Transition)setTransitionOutAnimator(Transition)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)show()showBack()startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)")); @@ -3480,9 +3700,6 @@ private static void fillMethodIndex20(Map index) { index.put("com.codename1.ui.RichTextFormat", splitMembers("")); index.put("com.codename1.ui.SelectableIconHolder", splitMembers("getDisabledIcon()getGap()getIcon()getIconFromState()getIconStyleComponent()getIconUIID()getPressedIcon()getRolloverIcon()getRolloverPressedIcon()getTextPosition()setDisabledIcon(Image)setFontIcon(Font, char, float)setGap(int)setIcon(Image)setIconUIID(String)setMaterialIcon(char, float)setPressedIcon(Image)setRolloverIcon(Image)setRolloverPressedIcon(Image)setTextPosition(int)")); index.put("com.codename1.ui.Sheet", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addBackListener(ActionListener)addCloseListener(ActionListener)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)back()back(int)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)drop(Component, int, int)findDropTargetAt(int, int)findFirstFocusable()finish(Object)flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getCommandsContainer()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getContentPane()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getParentSheet()getPosition()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTitle()getTitleComponent()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hideBackButton()invalidate()isAllowClose()isAlwaysTensile()isAncestorSheetOf(Sheet)isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isSurface()isSwipeToDismissEnabled()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeAll()removeBackListener(ActionListener)removeCloseListener(ActionListener)removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAllowClose(boolean)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPosition(String)setPosition(String, String)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setSwipeToDismissEnabled(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTitle(String)setTitleComponent(Component)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)show()show(int)showBackButton()showForResult()showForResult(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)findContainingSheet(Component)getCurrentSheet()isSheetVisibleAt(int, int)")); - } - - private static void fillMethodIndex21(Map index) { index.put("com.codename1.ui.SideMenuBar", splitMembers("accessibilityChanged()accessibilityChanged(int)actionPerformed(ActionEvent)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addCommand(Command)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()closeMenu()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)drop(Component, int, int)findCommandComponent(Command)findDropTargetAt(int, int)findFirstFocusable()flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBackCommand()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClearCommand()getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getCommand(int)getCommandBehavior()getCommandCount()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getCursor()getDefaultCommand()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getMenuStyle()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getParentForm()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommand()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()handlesKeycode(int)hasFixedPreferredSize()hasFocus()invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isMenuOpen()isMenuShowing()isMinimizeOnBack()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)openMenu(String)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeAll()removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeEmptySoftbuttons()removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setBackCommand(Command)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setClearCommand(Command)setCloudBoundProperty(String)setCloudDestinationProperty(String)setCommandUIID(Command, String)setComponentState(Object)setCursor(int)setDefaultCommand(Command)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setMenuCellRenderer(ListCellRenderer)setMinimizeOnBack(boolean)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommand(Command)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTooltip(String)setTransitions(Transition, Transition)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)showMenu()startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)closeCurrentMenu()closeCurrentMenu(Runnable)isShowing()")); index.put("com.codename1.ui.Slider", splitMembers("accessibilityChanged()accessibilityChanged(int)addActionListener(ActionListener)addContextMenuListener(ActionListener)addDataChangedListener(DataChangedListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)deinitialize()drop(Component, int, int)getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAlignment()getAllStyles()getAnimationManager()getBadgeStyleComponent()getBadgeText()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getFontIcon()getFontIconSize()getGap()getHeight()getIcon()getIconFont()getIconStyleComponent()getIconUIID()getIncrements()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getMask()getMaskName()getMaskedIcon()getMaterialIcon()getMaterialIconSize()getMaxAutoSize()getMaxValue()getMinAutoSize()getMinValue()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getProgress()getProgress(ActionEvent)getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getShiftMillimeters()getShiftMillimetersF()getShiftText()getSideGap()getSliderEmptySelectedStyle()getSliderEmptyUnselectedStyle()getSliderFullSelectedStyle()getSliderFullUnselectedStyle()getStringWidth(Font)getStyle()getTabIndex()getTensileLength()getText()getTextPosition()getTextSelectionSupport()getThumbImage()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVerticalAlignment()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()initComponent()isAlwaysTensile()isAutoSizeMode()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isEndsWith3Points()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isInfinite()isLegacyRenderer()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRenderPercentageOnTop()isRenderValueOnTop()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isShouldLocalize()isShowEvenIfBlank()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTextSelectionEnabled()isTickerEnabled()isTickerRunning()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVertical()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeActionListener(ActionListener)removeContextMenuListener(ActionListener)removeDataChangedListener(DataChangedListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlignment(int)setAlwaysTensile(boolean)setAutoSizeMode(boolean)setBadgeText(String)setBadgeUIID(String)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditable(boolean)setEditingDelegate(Editable)setEnabled(boolean)setEndsWith3Points(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontIcon(char)setFontIcon(Font, char)setFontIcon(Font, char, float)setGap(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIcon(Image)setIconUIID(String)setIgnorePointerEvents(boolean)setIncrements(int)setInfinite(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLegacyRenderer(boolean)setMask(Object)setMaskName(String)setMaterialIcon(char)setMaterialIcon(char, float)setMaxAutoSize(float)setMaxValue(int)setMinAutoSize(float)setMinValue(int)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setProgress(int)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRenderPercentageOnTop(boolean)setRenderValueOnTop(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShiftMillimeters(float)setShiftMillimeters(int)setShiftText(int)setShouldCalcPreferredSize(boolean)setShouldLocalize(boolean)setShowEvenIfBlank(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextPosition(int)setTextSelectionEnabled(boolean)setThumbImage(Image)setTickerEnabled(boolean)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVertical(boolean)setVerticalAlignment(int)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)shouldTickerStart()startEditingAsync()startTicker()startTicker(long, boolean)stopEditing(Runnable)stopTicker()stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)createInfinite()")); index.put("com.codename1.ui.Stroke", splitMembers("equals(Object)getCapStyle()getJoinStyle()getLineWidth()getMiterLimit()hashCode()setCapStyle(int)setJoinStyle(int)setLineWidth(float)setMiterLimit(float)setStroke(Stroke)toString()")); @@ -3509,6 +3726,9 @@ private static void fillMethodIndex21(Map index) { index.put("com.codename1.ui.UIFragment.DefaultComponentFactory", splitMembers("newComponent(Element)newConstraint(Container, Element, Component, Element)")); index.put("com.codename1.ui.URLImage", splitMembers("addActionListener(ActionListener)animate()applyMask(Object)applyMask(Object, int, int)applyMaskAutoScale(Object)asyncLock(Image)createMask()dispose()fetch()fill(int, int)fireChangedEvent()flipHorizontally(boolean)flipVertically(boolean)getGraphics()getHeight()getImage()getImageData()getImageName()getRGB()getRGB(int[])getRGBCached()getSVGDocument()getWidth()isAnimation()isDisposed()isLocked()isOpaque()isSVG()lock()mirror()modifyAlpha(byte)modifyAlpha(byte, int)modifyAlphaWithTranslucency(byte)removeActionListener(ActionListener)requiresDrawImage()rotate(int)rotate180Degrees(boolean)rotate270Degrees(boolean)rotate90Degrees(boolean)scale(int, int)scaled(int, int)scaledEncoded(int, int)scaledHeight(int)scaledLargerRatio(int, int)scaledSmallerRatio(int, int)scaledWidth(int)setImageName(String)subImage(int, int, int, int, boolean)toRGB(RGBImage, int, int, int, int, int, int)unlock()createCachedImage(String, String, Image, int)createMaskAdapter(Image)createMaskAdapter(Object)createToFileSystem(EncodedImage, String, String, ImageAdapter)createToStorage(EncodedImage, String, String)createToStorage(EncodedImage, String, String, ImageAdapter)createToStorage(EncodedImage, String, String, ImageAdapter, RequestDecorator)getDefaultRequestDecorator()getExceptionHandler()setDefaultBearerToken(String)setDefaultRequestDecorator(RequestDecorator)setExceptionHandler(ErrorCallback)")); index.put("com.codename1.ui.URLImage.ErrorCallback", splitMembers("onError(URLImage, Exception)")); + } + + private static void fillMethodIndex23(Map index) { index.put("com.codename1.ui.URLImage.ImageAdapter", splitMembers("adaptImage(EncodedImage, EncodedImage)isAsyncAdapter()")); index.put("com.codename1.ui.URLImage.RequestDecorator", splitMembers("decorate(ConnectionRequest)")); index.put("com.codename1.ui.VirtualInputDevice", splitMembers("")); @@ -3546,13 +3766,10 @@ private static void fillMethodIndex21(Map index) { index.put("com.codename1.ui.css.CSSThemeCompiler", splitMembers("compile(String, MutableResource, String)")); index.put("com.codename1.ui.css.CSSThemeCompiler.CSSSyntaxException", splitMembers("")); index.put("com.codename1.ui.editor.CodePureEditor", splitMembers("cmd(String, String)getView()query(String, String)")); - index.put("com.codename1.ui.editor.CodeView", splitMembers("accessibilityChanged()accessibilityChanged(int)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()blur()clearClientProperties()commitText(String)contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)deleteSurroundingText(int, int)drop(Component, int, int)finishComposing()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getCaretOffset()getCaretRect()getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getConfig()getCursor()getDirtyRegion()getDisabledStyle()getDocument()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEditingState()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLanguage()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSelectionEnd()getSelectionStart()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getText()getTextLength()getTextRange(int, int)getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUndoManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasSelection()inputFocusGained()inputFocusLost()insertText(String)isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditableState()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)moveCaret(int, boolean)offsetAtPoint(int, int)onEditorAction(int)onKeyCommand(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)performRedo()performUndo()pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)rectForOffset(int)refreshTheme()refreshTheme(boolean)remove()removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replaceRange(int, int, String)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)selectAll()selectionRects(int, int)setAccessibilityText(String)setAlwaysTensile(boolean)setBackgroundColor(int)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setCompletionEnabled(boolean)setComponentState(Object)setComposingText(String, int)setCursor(int)setDiagnostics(List)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditableState(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontSizeDips(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLanguage(String)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setSelectionColor(int)setSelectionRange(int, int)setShouldCalcPreferredSize(boolean)setShowLineNumbers(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTabSize(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextColor(int)setTheme(String)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)showCompletions(int, List)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)")); - } - - private static void fillMethodIndex22(Map index) { + index.put("com.codename1.ui.editor.CodeView", splitMembers("accessibilityChanged()accessibilityChanged(int)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()blur()clearClientProperties()commitText(String)contains(int, int)containsOrOwns(int, int)copySelection()createStyleAnimation(String, int)cutSelection()deleteBackward()deleteSurroundingText(int, int)drop(Component, int, int)finishComposing()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getCaretOffset()getCaretRect()getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getConfig()getCursor()getDirtyRegion()getDisabledStyle()getDocument()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEditingState()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLanguage()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSelectionEnd()getSelectionStart()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getText()getTextLength()getTextRange(int, int)getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUndoManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasSelection()inputFocusGained()inputFocusLost()insertText(String)isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditableState()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)moveCaret(int, boolean)offsetAtPoint(int, int)onEditorAction(int)onKeyCommand(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pasteClipboard()performRedo()performUndo()pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)rectForOffset(int)refreshTheme()refreshTheme(boolean)remove()removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replaceRange(int, int, String)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)selectAll()selectionRects(int, int)setAccessibilityText(String)setAlwaysTensile(boolean)setBackgroundColor(int)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setCompletionEnabled(boolean)setComponentState(Object)setComposingText(String, int)setCursor(int)setDiagnostics(List)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditableState(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontSizeDips(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLanguage(String)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setProtectedRegionMarkers(String, String)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setSelectionColor(int)setSelectionRange(int, int)setShouldCalcPreferredSize(boolean)setShowLineNumbers(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTabSize(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextColor(int)setTheme(String)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)showCompletions(int, List)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)")); index.put("com.codename1.ui.editor.EditorDocument", splitMembers("charAt(int)clamp(int)columnOfOffset(int)delete(int, int)getLineCount()getLineEnd(int)getLineStart(int)getLineText(int)getText()insert(int, String)length()lineOfOffset(int)setText(String)substring(int, int)normalizeText(String)")); index.put("com.codename1.ui.editor.EditorHost", splitMembers("editorChanged()fireEditorEvent(String, String)isTextInputSupported()startTextInput(TextInputClient, TextInputConfig)stopTextInput(Object)updateTextInputState(Object, TextInputState)")); - index.put("com.codename1.ui.editor.EditorView", splitMembers("accessibilityChanged()accessibilityChanged(int)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()blur()clearClientProperties()commitText(String)contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)deleteSurroundingText(int, int)drop(Component, int, int)finishComposing()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getCaretOffset()getCaretRect()getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getConfig()getCursor()getDirtyRegion()getDisabledStyle()getDocument()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEditingState()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSelectionEnd()getSelectionStart()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getText()getTextLength()getTextRange(int, int)getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUndoManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasSelection()inputFocusGained()inputFocusLost()insertText(String)isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditableState()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)moveCaret(int, boolean)offsetAtPoint(int, int)onEditorAction(int)onKeyCommand(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)performRedo()performUndo()pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)rectForOffset(int)refreshTheme()refreshTheme(boolean)remove()removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replaceRange(int, int, String)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)selectAll()selectionRects(int, int)setAccessibilityText(String)setAlwaysTensile(boolean)setBackgroundColor(int)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setComposingText(String, int)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditableState(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontSizeDips(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setSelectionColor(int)setSelectionRange(int, int)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextColor(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)")); + index.put("com.codename1.ui.editor.EditorView", splitMembers("accessibilityChanged()accessibilityChanged(int)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)bindProperty(String, BindTarget)blocksSideSwipe()blur()clearClientProperties()commitText(String)contains(int, int)containsOrOwns(int, int)copySelection()createStyleAnimation(String, int)cutSelection()deleteBackward()deleteSurroundingText(int, int)drop(Component, int, int)finishComposing()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getCaretOffset()getCaretRect()getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getConfig()getCursor()getDirtyRegion()getDisabledStyle()getDocument()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEditingState()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSelectionEnd()getSelectionStart()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getText()getTextLength()getTextRange(int, int)getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUndoManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasSelection()inputFocusGained()inputFocusLost()insertText(String)isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditableState()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)moveCaret(int, boolean)offsetAtPoint(int, int)onEditorAction(int)onKeyCommand(int, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pasteClipboard()performRedo()performUndo()pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)rectForOffset(int)refreshTheme()refreshTheme(boolean)remove()removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replaceRange(int, int, String)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)selectAll()selectionRects(int, int)setAccessibilityText(String)setAlwaysTensile(boolean)setBackgroundColor(int)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setComposingText(String, int)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditableState(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontSizeDips(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setSelectionColor(int)setSelectionRange(int, int)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextColor(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)")); index.put("com.codename1.ui.editor.HtmlImporter", splitMembers("parse(String)")); index.put("com.codename1.ui.editor.HtmlImporter.Result", splitMembers("getBlocks()getImageSources()getLinks()getStyles()getText()hasBlockContent()")); index.put("com.codename1.ui.editor.HtmlSerializer", splitMembers("serialize(EditorDocument, InlineStyles, RichBlocks, List, List)")); @@ -3567,7 +3784,7 @@ private static void fillMethodIndex22(Map index) { index.put("com.codename1.ui.editor.RichRunPainter", splitMembers("fontFor(int, boolean, boolean)getBaseSizePx()paintRun(Graphics, String, TextStyle, Font, int, int, int)runFont(int, TextStyle)runPx(int, TextStyle)setBaseFont(Font)setBaseSizePx(int)setTextColor(int)headingScale(int)isHeading(int)sizeLevelScale(int)")); index.put("com.codename1.ui.editor.RichTextImporter", splitMembers("convert(String, RichTextFormat, RichTextFormat)fromHtml(String, RichTextFormat)parse(String, RichTextFormat)toHtml(String, RichTextFormat)")); index.put("com.codename1.ui.editor.RichTextSerializer", splitMembers("serialize(EditorDocument, InlineStyles, RichBlocks, List, List, RichTextFormat)")); - index.put("com.codename1.ui.editor.RichView", splitMembers("accessibilityChanged()accessibilityChanged(int)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)applyLink(String)bindProperty(String, BindTarget)blocksSideSwipe()blur()clearClientProperties()commitText(String)contains(int, int)containsOrOwns(int, int)createStyleAnimation(String, int)deleteSurroundingText(int, int)drop(Component, int, int)finishComposing()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBlocks()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getCaretOffset()getCaretRect()getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getConfig()getCursor()getDirtyRegion()getDisabledStyle()getDocument()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEditingState()getHeight()getImageSources()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLinkRuns()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSelectionEnd()getSelectionStart()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getText()getTextLength()getTextRange(int, int)getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUndoManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasSelection()importContent(String, List, List, List, List, List)indentBlocks()inputFocusGained()inputFocusLost()insertContent(String, List, List, List, List, List, boolean)insertImageObject(Image, String)insertText(String)isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditableState()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)moveCaret(int, boolean)offsetAtPoint(int, int)onEditorAction(int)onKeyCommand(int, int)outdentBlocks()paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)performRedo()performUndo()pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)queryState(String)rectForOffset(int)refreshTheme()refreshTheme(boolean)remove()removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeFormat()removeLinkStyle()removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replaceRange(int, int, String)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)selectAll()selectionRects(int, int)setAccessibilityText(String)setAlign(int)setAlwaysTensile(boolean)setBackgroundColor(int)setBlockFormat(String)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setComposingText(String, int)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditableState(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontSizeDips(int)setFontSizeLevel(int)setForeColor(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHighlight(int)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setList(int)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPlaceholder(String)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setSelectionColor(int)setSelectionRange(int, int)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextColor(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()toggleBold()toggleItalic()toggleStrike()toggleUnderline()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)")); + index.put("com.codename1.ui.editor.RichView", splitMembers("accessibilityChanged()accessibilityChanged(int)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()announceForAccessibility(String)applyLink(String)bindProperty(String, BindTarget)blocksSideSwipe()blur()clearClientProperties()commitText(String)contains(int, int)containsOrOwns(int, int)copySelection()createStyleAnimation(String, int)cutSelection()deleteBackward()deleteSurroundingText(int, int)drop(Component, int, int)finishComposing()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBlocks()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getCaretOffset()getCaretRect()getClientProperty(String)getCloudBoundProperty()getCloudDestinationProperty()getComponentForm()getComponentState()getConfig()getCursor()getDirtyRegion()getDisabledStyle()getDocument()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEditingState()getHeight()getImageSources()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLinkRuns()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSelectionEnd()getSelectionStart()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getText()getTextLength()getTextRange(int, int)getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUndoManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()hasSelection()importContent(String, List, List, List, List, List)indentBlocks()inputFocusGained()inputFocusLost()insertContent(String, List, List, List, List, List, boolean)insertImageObject(Image, String)insertText(String)isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditableState()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()keyPressed(int)keyReleased(int)keyRepeated(int)longPointerPress(int, int)moveCaret(int, boolean)offsetAtPoint(int, int)onEditorAction(int)onKeyCommand(int, int)outdentBlocks()paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pasteClipboard()performRedo()performUndo()pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)queryState(String)rectForOffset(int)refreshTheme()refreshTheme(boolean)remove()removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeFormat()removeLinkStyle()removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replaceRange(int, int, String)requestFocus()respondsToPointerEvents()scrollRectToVisible(int, int, int, int, Component)selectAll()selectionRects(int, int)setAccessibilityText(String)setAlign(int)setAlwaysTensile(boolean)setBackgroundColor(int)setBlockFormat(String)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setComposingText(String, int)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditableState(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setFontSizeDips(int)setFontSizeLevel(int)setForeColor(int)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHighlight(int)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setList(int)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPlaceholder(String)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setScrollAnimationSpeed(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setSelectCommandText(String)setSelectedStyle(Style)setSelectionColor(int)setSelectionRange(int, int)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setText(String)setTextColor(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()toggleBold()toggleItalic()toggleStrike()toggleUnderline()unbindProperty(String, BindTarget)visibleBoundsContains(int, int)")); index.put("com.codename1.ui.editor.SyntaxHighlightResult", splitMembers("")); index.put("com.codename1.ui.editor.SyntaxHighlighter", splitMembers("tokenize(String, int)")); index.put("com.codename1.ui.editor.SyntaxToken", splitMembers("")); @@ -3576,6 +3793,9 @@ private static void fillMethodIndex22(Map index) { index.put("com.codename1.ui.editor.Tokenizer", splitMembers("tokenize(String, int)")); index.put("com.codename1.ui.editor.UndoManager", splitMembers("breakRun()canRedo()canUndo()clear()record(int, String, String)redo(EditorDocument)undo(EditorDocument)")); index.put("com.codename1.ui.events.ActionEvent", splitMembers("consume()getActualComponent()getCommand()getComponent()getDraggedComponent()getDropTarget()getEventType()getKeyEvent()getPointerEvent()getProgress()getSource()getX()getY()isConsumed()isLongEvent()isPointerPressedDuringDrag()setPointerEvent(PointerEvent)setPointerPressedDuringDrag(boolean)")); + } + + private static void fillMethodIndex24(Map index) { index.put("com.codename1.ui.events.ActionEvent.Type", splitMembers("")); index.put("com.codename1.ui.events.ActionListener", splitMembers("actionPerformed(ActionEvent)")); index.put("com.codename1.ui.events.ActionSource", splitMembers("addActionListener(ActionListener)removeActionListener(ActionListener)")); @@ -3614,9 +3834,6 @@ private static void fillMethodIndex22(Map index) { index.put("com.codename1.ui.html.HTMLParser", splitMembers("addCharEntitiesRange(String[], int)addCharEntity(String, int)isCaseSensitive()setCaseSensitive(boolean)setIncludeWhitespacesBetweenTags(boolean)setParserCallback(ParserCallback)")); index.put("com.codename1.ui.html.HTMLUtils", splitMembers("convertCharEntity(String, boolean, Hashtable)convertHTMLCharEntity(String)convertXMLCharEntity(String)encodeString(String)")); index.put("com.codename1.ui.html.IOCallback", splitMembers("")); - } - - private static void fillMethodIndex23(Map index) { index.put("com.codename1.ui.layouts.BorderLayout", splitMembers("addLayoutComponent(Object, Component, Container)cloneConstraint(Object)defineLandscapeSwap(String, String)equals(Object)getCenter()getCenterBehavior()getComponentConstraint(Component)getEast()getLandscapeSwap(String)getNorth()getOverlay()getPreferredSize(Container)getSouth()getWest()hashCode()isAbsoluteCenter()isConstraintTracking()isOverlapSupported()isScaleEdges()layoutContainer(Container)obscuresPotential(Container)overridesTabIndices(Container)removeLayoutComponent(Component)setAbsoluteCenter(boolean)setCenterBehavior(int)setScaleEdges(boolean)toString()updateTabIndices(Container, int)absolute()center()center(Component)centerAbsolute(Component)centerAbsoluteEastWest(Component, Component, Component)centerCenter(Component)centerCenterEastWest(Component, Component, Component)centerEastWest(Component, Component, Component)centerTotalBelow(Component)centerTotalBelowEastWest(Component, Component, Component)east(Component)north(Component)south(Component)totalBelow()west(Component)")); index.put("com.codename1.ui.layouts.BoxLayout", splitMembers("addLayoutComponent(Object, Component, Container)cloneConstraint(Object)equals(Object)getAlign()getAxis()getComponentConstraint(Component)getPreferredSize(Container)hashCode()isConstraintTracking()isOverlapSupported()layoutContainer(Container)obscuresPotential(Container)overridesTabIndices(Container)removeLayoutComponent(Component)setAlign(int)toString()updateTabIndices(Container, int)encloseX(Component[]...)encloseXCenter(Component[]...)encloseXNoGrow(Component[]...)encloseXRight(Component[]...)encloseY(Component[]...)encloseYBottom(Component[]...)encloseYBottomLast(Component[]...)encloseYCenter(Component[]...)x()xCenter()xRight()y()yBottom()yCenter()yLast()")); index.put("com.codename1.ui.layouts.CoordinateLayout", splitMembers("addLayoutComponent(Object, Component, Container)cloneConstraint(Object)equals(Object)getComponentConstraint(Component)getPreferredSize(Container)hashCode()isConstraintTracking()isOverlapSupported()layoutContainer(Container)obscuresPotential(Container)overridesTabIndices(Container)removeLayoutComponent(Component)updateTabIndices(Container, int)")); @@ -3643,6 +3860,9 @@ private static void fillMethodIndex23(Map index) { index.put("com.codename1.ui.layouts.mig.LayoutCallback", splitMembers("correctBounds(ComponentWrapper)getPosition(ComponentWrapper)getSize(ComponentWrapper)")); index.put("com.codename1.ui.layouts.mig.LayoutUtil", splitMembers("getDesignTimeEmptySize()getGlobalDebugMillis()getSerializedObject(Object)getSizeSafe(int[], int)getVersion()isDesignTime(ContainerWrapper)isLeftToRight(LC, ContainerWrapper)setDesignTime(ContainerWrapper, boolean)setDesignTimeEmptySize(int)setGlobalDebugMillis(int)setSerializedObject(Object, Object)")); index.put("com.codename1.ui.layouts.mig.LinkHandler", splitMembers("clearBounds(Object, String)clearWeakReferencesNow()getValue(Object, String, int)setBounds(Object, String, int, int, int, int)")); + } + + private static void fillMethodIndex25(Map index) { index.put("com.codename1.ui.layouts.mig.MigLayout", splitMembers("addLayoutCallback(LayoutCallback)addLayoutComponent(Component, Object)addLayoutComponent(Object, Component, Container)cloneConstraint(Object)equals(Object)getColumnConstraints()getComponentConstraint(Component)getComponentConstraints(Component)getConstraintMap()getLayoutAlignmentX(Container)getLayoutAlignmentY(Container)getLayoutConstraints()getPreferredSize(Container)getRowConstraints()hashCode()invalidateLayout(Container)isConstraintTracking()isManagingComponent(Component)isOverlapSupported()layoutContainer(Container)maximumLayoutSize(Container)minimumLayoutSize(Container)obscuresPotential(Container)overridesTabIndices(Container)preferredLayoutSize(Container)removeLayoutCallback(LayoutCallback)removeLayoutComponent(Component)setColumnConstraints(Object)setComponentConstraints(Component, Object)setConstraintMap(Map)setLayoutConstraints(Object)setRowConstraints(Object)updateTabIndices(Container, int)findType(Class, Component)")); index.put("com.codename1.ui.layouts.mig.PlatformDefaults", splitMembers("invalidate()getButtonOrder()getCurrentPlatform()getDefaultDPI()getDefaultHorizontalUnit()getDefaultRowAlignmentBaseline()getDefaultVerticalUnit()getDefaultVisualPadding(String)getDialogInsets(int)getGapProvider()getGridGapX()getGridGapY()getHorizontalScaleFactor()getLabelAlignPercentage()getLogicalPixelBase()getMinimumButtonWidth()getModCount()getPanelInsets(int)getPlatform()getPlatformDPI(int)getUnitValueX(String)getUnitValueY(String)getVerticalScaleFactor()setButtonOrder(String)setDefaultDPI(Integer)setDefaultHorizontalUnit(int)setDefaultRowAlignmentBaseline(boolean)setDefaultVerticalUnit(int)setDefaultVisualPadding(String, int[])setDialogInsets(UnitValue, UnitValue, UnitValue, UnitValue)setGapProvider(InCellGapProvider)setGridCellGap(UnitValue, UnitValue)setHorizontalScaleFactor(Float)setIndentGap(UnitValue, UnitValue)setLogicalPixelBase(int)setMinimumButtonWidth(UnitValue)setPanelInsets(UnitValue, UnitValue, UnitValue, UnitValue)setParagraphGap(UnitValue, UnitValue)setPlatform(int)setRelatedGap(UnitValue, UnitValue)setUnitValue(String[], UnitValue, UnitValue)setUnrelatedGap(UnitValue, UnitValue)setVerticalScaleFactor(Float)")); index.put("com.codename1.ui.layouts.mig.UnitConverter", splitMembers("convertToPixels(float, String, boolean, float, ContainerWrapper, ComponentWrapper)")); @@ -3681,9 +3901,6 @@ private static void fillMethodIndex23(Map index) { index.put("com.codename1.ui.plaf.StyleParser.StyleInfo", splitMembers("getAlignment()getAlignmentAsString()getBgColor()getBgImage()getBgType()getBgTypeAsString()getBorder()getFgColor()getFont()getMargin()getOpacity()getPadding()getTextDecoration()getTextDecorationAsString()getTransparency()setAlignment(int)setAlignment(String)setBgColor(String)setBgImage(String)setBgType(Integer)setBgType(String)setBorder(String)setFgColor(String)setFont(String)setFontName(String)setFontSize(String)setMargin(String)setOpacity(String)setPadding(String)setTransparency(String)toStyleString()")); index.put("com.codename1.ui.plaf.UIManager", splitMembers("addThemeProps(Hashtable)addThemeRefreshListener(ActionListener)getBundle()getComponentCustomStyle(String, String)getComponentSelectedStyle(String)getComponentStyle(String)getIconUIIDFor(String)getLookAndFeel()getResourceBundle()getThemeConstant(String, int)getThemeConstant(String, String)getThemeImageConstant(String)getThemeMaskConstant(String)getThemeName()isThemeConstant(String)isThemeConstant(String, boolean)isUseLargerTextScale()localize(String, String)parseComponentCustomStyle(Resources, String, String, String, String[]...)parseComponentSelectedStyle(Resources, String, String, String[]...)parseComponentStyle(Resources, String, String, String[]...)refreshTheme()removeThemeRefreshListener(ActionListener)setBundle(Map)setComponentSelectedStyle(String, Style)setComponentStyle(String, Style)setComponentStyle(String, Style, String)setLookAndFeel(LookAndFeel)setResourceBundle(Hashtable)setThemeProps(Hashtable)setUseLargerTextScale(boolean)wasThemeInstalled()zoomFonts(float)createInstance()getInstance()initFirstTheme(String)initNamedTheme(String, String)")); index.put("com.codename1.ui.scene.Bounds", splitMembers("getDepth()getHeight()getMinX()getMinY()getMinZ()getWidth()setDepth(double)setHeight(double)setMinX(double)setMinY(double)setMinZ(double)setWidth(double)")); - } - - private static void fillMethodIndex24(Map index) { index.put("com.codename1.ui.scene.Camera", splitMembers("getTransform()")); index.put("com.codename1.ui.scene.Node", splitMembers("add(Node)addTags(String[]...)contains(int, int)findNodesWithTag(String)getBoundsInScene(Rectangle2D)getChildAt(int)getChildCount()getChildNodes()getLocalToParentTransform()getLocalToSceneTransform()getLocalToScreenTransform()getRenderer()getScene()getStyle()hasChildren()hasTag(String)isNeedsLayout()remove(Node)removeAll()removeTags(String[]...)render(Graphics)renderChildren(Graphics)setNeedsLayout(boolean)setRenderAsImage(boolean)setRenderer(NodePainter)setStyle(Style)")); index.put("com.codename1.ui.scene.NodePainter", splitMembers("paint(Graphics, Rectangle, Node)")); @@ -3710,6 +3927,9 @@ private static void fillMethodIndex24(Map index) { index.put("com.codename1.ui.tree.Tree", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLeafListener(ActionListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()collapsePath(Object[]...)contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)drop(Component, int, int)expandPath(Object[]...)expandPath(boolean, Object[]...)findDropTargetAt(int, int)findFirstFocusable()findNodeComponent(Object)findNodeComponent(Object, Component)flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getModel()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getParentComponent(Component)getParentNode(Component)getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedItem()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTooltip()getTreeState()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isMultilineMode()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshNode(Component)refreshTheme()refreshTheme(boolean)remove()removeAll()removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLeafListener(ActionListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setModel(TreeModel)setMultilineMode(boolean)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTooltip(String)setTraversable(boolean)setTreeState(TreeState)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)setFolderIcon(Image)setFolderOpenIcon(Image)setNodeIcon(Image)")); index.put("com.codename1.ui.tree.Tree.TreeState", splitMembers("")); index.put("com.codename1.ui.tree.TreeModel", splitMembers("getChildren(Object)isLeaf(Object)")); + } + + private static void fillMethodIndex26(Map index) { index.put("com.codename1.ui.util.Effects", splitMembers("dropshadow(Image, int, float)dropshadow(Image, int, float, int, int)gaussianBlurImage(Image, float)growShrink(Component, int)isGaussianBlurSupported()reflectionImage(Image)reflectionImage(Image, float, int)reflectionImage(Image, float, int, int)squareShadow(int, int, int, float)verticalPerspective(Image, float, float, float)")); index.put("com.codename1.ui.util.EmbeddedContainer", splitMembers("accessibilityChanged()accessibilityChanged(int)add(Component)add(Image)add(String)add(Object, Component)add(Object, String)add(Object, Image)addAll(Component[]...)addComponent(Component)addComponent(int, Component)addComponent(int, Object, Component)addContextMenuListener(ActionListener)addDragFinishedListener(ActionListener)addDragOverListener(ActionListener)addDropListener(ActionListener)addFocusListener(FocusListener)addLongPressListener(ActionListener)addMouseWheelListener(ActionListener)addPointerDraggedListener(ActionListener)addPointerPressedListener(ActionListener)addPointerReleasedListener(ActionListener)addPullToRefresh(Runnable)addScrollListener(ScrollListener)addStateChangeListener(ActionListener)addStylusListener(ActionListener)animate()animateHierarchy(int)animateHierarchyAndWait(int)animateHierarchyFade(int, int)animateHierarchyFadeAndWait(int, int)animateLayout(int)animateLayoutAndWait(int)animateLayoutFade(int, int)animateLayoutFadeAndWait(int, int)animateUnlayout(int, int, Runnable)animateUnlayoutAndWait(int, int)announceForAccessibility(String)applyRTL(boolean)bindProperty(String, BindTarget)blocksSideSwipe()clearClientProperties()contains(Component)contains(int, int)containsOrOwns(int, int)createAnimateHierarchy(int)createAnimateHierarchyFade(int, int)createAnimateLayout(int)createAnimateLayoutFade(int, int)createAnimateLayoutFadeAndWait(int, int)createAnimateUnlayout(int, int, Runnable)createReplaceTransition(Component, Component, Transition)createStyleAnimation(String, int)drop(Component, int, int)findDropTargetAt(int, int)findFirstFocusable()flushReplace()forceRevalidate()getAbsoluteX()getAbsoluteY()getAccessibilityNode()getAccessibilityText()getAllStyles()getAnimationManager()getBaseline(int, int)getBaselineResizeBehavior()getBindablePropertyNames()getBindablePropertyTypes()getBottomGap()getBoundPropertyValue(String)getBounds(Rectangle)getChildrenAsList(boolean)getClientProperty(String)getClosestComponentTo(int, int)getCloudBoundProperty()getCloudDestinationProperty()getComponentAt(int)getComponentAt(int, int)getComponentCount()getComponentForm()getComponentIndex(Component)getComponentState()getCursor()getDirtyRegion()getDisabledStyle()getDragTransparency()getDraggedx()getDraggedy()getEditingDelegate()getEmbed()getHeight()getInlineAllStyles()getInlineDisabledStyles()getInlinePressedStyles()getInlineSelectedStyles()getInlineStylesTheme()getInlineUnselectedStyles()getInnerHeight()getInnerPreferredH()getInnerPreferredW()getInnerWidth()getInnerX()getInnerY()getLabelForComponent()getLayout()getLayoutHeight()getLayoutWidth()getLeadComponent()getLeadParent()getName()getNativeOverlay()getNextFocusDown()getNextFocusLeft()getNextFocusRight()getNextFocusUp()getOuterHeight()getOuterPreferredH()getOuterPreferredW()getOuterWidth()getOuterX()getOuterY()getOwner()getParent()getPreferredH()getPreferredSize()getPreferredSizeStr()getPreferredTabIndex()getPreferredW()getPressedStyle()getPropertyNames()getPropertyTypeNames()getPropertyTypes()getPropertyValue(String)getResponderAt(int, int)getSafeAreaRoot()getSameHeight()getSameWidth()getScrollAnimationSpeed()getScrollDimension()getScrollIncrement()getScrollOpacity()getScrollOpacityChangeSpeed()getScrollX()getScrollY()getScrollable()getSelectCommandText()getSelectedRect()getSelectedStyle()getSemantics()getSideGap()getStyle()getTabIndex()getTensileLength()getTextSelectionSupport()getTooltip()getUIID()getUIManager()getUnselectedStyle()getVisibleBounds(Rectangle)getWidth()getX()getY()growShrink(int)handlesInput()hasFixedPreferredSize()hasFocus()invalidate()isAlwaysTensile()isBlockLead()isCellRenderer()isChildOf(Container)isDraggable()isDropTarget()isEditable()isEditing()isEnabled()isFlatten()isFocusable()isGrabsPointerEvents()isHScrollThumbGrabbed()isHScrollThumbHover()isHidden()isHidden(boolean)isHideInLandscape()isHideInPortrait()isIgnorePointerEvents()isOpaque()isOwnedBy(Component)isPinchBlocksDragAndDrop()isRTL()isRippleEffect()isSafeArea()isSafeAreaRoot()isScrollVisible()isScrollableX()isScrollableY()isSmoothScrolling()isSnapToGrid()isSurface()isTactileTouch()isTensileDragEnabled()isTraversable()isVScrollThumbGrabbed()isVScrollThumbHover()isVisible()iterator()iterator(boolean)keyPressed(int)keyReleased(int)keyRepeated(int)layoutContainer()longPointerPress(int, int)morph(Component, Component, int, Runnable)morphAndWait(Component, Component, int)paint(Graphics)paintBackgrounds(Graphics)paintComponent(Graphics)paintComponent(Graphics, boolean)paintComponentBackground(Graphics)paintIntersectingComponentsAbove(Graphics)paintLock(boolean)paintLockRelease()paintRippleOverlay(Graphics, int, int, int)paintShadows(Graphics, int, int)pointerDragged(int, int)pointerDragged(int[], int[])pointerHover(int[], int[])pointerHoverPressed(int[], int[])pointerHoverReleased(int[], int[])pointerPressed(int, int)pointerPressed(int[], int[])pointerReleased(int, int)pointerReleased(int[], int[])putClientProperty(String, Object)refreshTheme()refreshTheme(boolean)remove()removeAll()removeComponent(Component)removeContextMenuListener(ActionListener)removeDragFinishedListener(ActionListener)removeDragOverListener(ActionListener)removeDropListener(ActionListener)removeFocusListener(FocusListener)removeLongPressListener(ActionListener)removeMouseWheelListener(ActionListener)removePointerDraggedListener(ActionListener)removePointerPressedListener(ActionListener)removePointerReleasedListener(ActionListener)removeScrollListener(ScrollListener)removeStateChangeListener(ActionListener)removeStylusListener(ActionListener)repaint()repaint(int, int, int, int)replace(Component, Component, Transition)replace(Component, Component, Transition, Runnable, int)replaceAndWait(Component, Component, Transition)replaceAndWait(Component, Component, Transition, int)replaceAndWait(Component, Component, Transition, boolean)requestFocus()respondsToPointerEvents()revalidate()revalidateLater()revalidateWithAnimationSafety()scrollComponentToVisible(Component)scrollRectToVisible(int, int, int, int, Component)setAccessibilityText(String)setAlwaysTensile(boolean)setBlockLead(boolean)setBoundPropertyValue(String, Object)setCellRenderer(boolean)setCloudBoundProperty(String)setCloudDestinationProperty(String)setComponentState(Object)setCursor(int)setDirtyRegion(Rectangle)setDisabledStyle(Style)setDragTransparency(byte)setDraggable(boolean)setDropTarget(boolean)setEditingDelegate(Editable)setEmbed(String)setEnabled(boolean)setFlatten(boolean)setFocus(boolean)setFocusable(boolean)setGrabsPointerEvents(boolean)setHandlesInput(boolean)setHeight(int)setHidden(boolean)setHidden(boolean, boolean)setHideInLandscape(boolean)setHideInPortrait(boolean)setHorizontalScrollBounds(int, int, int, int, int, int, int, int)setIgnorePointerEvents(boolean)setInlineAllStyles(String)setInlineDisabledStyles(String)setInlinePressedStyles(String)setInlineSelectedStyles(String)setInlineStylesTheme(Resources)setInlineUnselectedStyles(String)setIsScrollVisible(boolean)setLabelForComponent(Label)setLayout(Layout)setLeadComponent(Component)setName(String)setNextFocusDown(Component)setNextFocusLeft(Component)setNextFocusRight(Component)setNextFocusUp(Component)setOpaque(boolean)setOwner(Component)setPinchBlocksDragAndDrop(boolean)setPreferredH(int)setPreferredSize(Dimension)setPreferredSizeStr(String)setPreferredTabIndex(int)setPreferredW(int)setPressedStyle(Style)setPropertyValue(String, Object)setPullToRefresh(Runnable)setRTL(boolean)setRippleEffect(boolean)setSafeArea(boolean)setSafeAreaRoot(boolean)setScrollAnimationSpeed(int)setScrollIncrement(int)setScrollOpacityChangeSpeed(int)setScrollSize(Dimension)setScrollVisible(boolean)setScrollable(boolean)setScrollableX(boolean)setScrollableY(boolean)setSelectCommandText(String)setSelectedStyle(Style)setShouldCalcPreferredSize(boolean)setSize(Dimension)setSmoothScrolling(boolean)setSnapToGrid(boolean)setTabIndex(int)setTactileTouch(boolean)setTensileDragEnabled(boolean)setTensileLength(int)setTooltip(String)setTraversable(boolean)setUIID(String)setUIID(String, String)setUIManager(UIManager)setUnselectedStyle(Style)setVerticalScrollBounds(int, int, int, int, int, int, int, int)setVisible(boolean)setWidth(int)setX(int)setY(int)startEditingAsync()stopEditing(Runnable)stripMarginAndPadding()styleChanged(String, Style)toImage()toString()unbindProperty(String, BindTarget)updateTabIndices(int)visibleBoundsContains(int, int)")); index.put("com.codename1.ui.util.EventDispatcher", splitMembers("addListener(Object)fireActionEvent(ActionEvent)fireBindTargetChange(Component, String, Object, Object)fireDataChangeEvent(int, int)fireFocus(Component)fireScrollEvent(int, int, int, int)fireSelectionEvent(int, int)fireStyleChangeEvent(String, Style)getListenerCollection()getListenerVector()hasListeners()isBlocking()removeListener(Object)setBlocking(boolean)setFireStyleEventsOnNonEDT(boolean)")); @@ -3748,9 +3968,6 @@ private static void fillMethodIndex24(Map index) { index.put("com.codename1.util.EasyThread.ErrorListener", splitMembers("")); index.put("com.codename1.util.FailureCallback", splitMembers("")); index.put("com.codename1.util.LazyValue", splitMembers("")); - } - - private static void fillMethodIndex25(Map index) { index.put("com.codename1.util.MathUtil", splitMembers("")); index.put("com.codename1.util.OnComplete", splitMembers("")); index.put("com.codename1.util.RunnableWithResult", splitMembers("")); @@ -3777,6 +3994,9 @@ private static void fillMethodIndex25(Map index) { index.put("com.codename1.util.regex.StringReader", splitMembers("")); index.put("com.codename1.vr.HeadTracker", splitMembers("")); index.put("com.codename1.vr.Media360View", splitMembers("")); + } + + private static void fillMethodIndex27(Map index) { index.put("com.codename1.vr.OrientationFilter", splitMembers("")); index.put("com.codename1.vr.TextureSource", splitMembers("")); index.put("com.codename1.vr.VRCameraRig", splitMembers("")); @@ -3785,6 +4005,7 @@ private static void fillMethodIndex25(Map index) { index.put("com.codename1.vr.VRSettings", splitMembers("")); index.put("com.codename1.vr.VRView", splitMembers("")); index.put("com.codename1.wearable.WearableConnection", splitMembers("")); + index.put("com.codename1.wearable.WearableConnection.DroppedDeliveryHandler", splitMembers("")); index.put("com.codename1.wearable.WearableDataListener", splitMembers("")); index.put("com.codename1.wearable.WearableMessage", splitMembers("")); index.put("com.codename1.wearable.WearableMessageListener", splitMembers("")); @@ -3815,9 +4036,6 @@ private static void fillMethodIndex25(Map index) { index.put("java.io.FileNotFoundException", splitMembers("")); index.put("java.io.Flushable", splitMembers("")); index.put("java.io.IOException", splitMembers("")); - } - - private static void fillMethodIndex26(Map index) { index.put("java.io.InputStream", splitMembers("")); index.put("java.io.InputStreamReader", splitMembers("")); index.put("java.io.InterruptedIOException", splitMembers("")); @@ -3843,6 +4061,9 @@ private static void fillMethodIndex26(Map index) { index.put("java.lang.Character", splitMembers("")); index.put("java.lang.Class", splitMembers("")); index.put("java.lang.ClassCastException", splitMembers("")); + } + + private static void fillMethodIndex28(Map index) { index.put("java.lang.ClassLoader", splitMembers("")); index.put("java.lang.ClassNotFoundException", splitMembers("")); index.put("java.lang.CloneNotSupportedException", splitMembers("")); @@ -3882,9 +4103,6 @@ private static void fillMethodIndex26(Map index) { index.put("java.lang.SafeVarargs", splitMembers("")); index.put("java.lang.SecurityException", splitMembers("")); index.put("java.lang.Short", splitMembers("")); - } - - private static void fillMethodIndex27(Map index) { index.put("java.lang.StackTraceElement", splitMembers("")); index.put("java.lang.String", splitMembers("")); index.put("java.lang.StringBuffer", splitMembers("")); @@ -3910,6 +4128,9 @@ private static void fillMethodIndex27(Map index) { index.put("java.text.DateFormat", splitMembers("")); index.put("java.text.DateFormatSymbols", splitMembers("")); index.put("java.text.Format", splitMembers("")); + } + + private static void fillMethodIndex29(Map index) { index.put("java.text.ParseException", splitMembers("")); index.put("java.text.SimpleDateFormat", splitMembers("")); index.put("java.time.Clock", splitMembers("")); @@ -3949,9 +4170,6 @@ private static void fillMethodIndex27(Map index) { index.put("java.util.Dictionary", splitMembers("")); index.put("java.util.EmptyStackException", splitMembers("")); index.put("java.util.Enumeration", splitMembers("")); - } - - private static void fillMethodIndex28(Map index) { index.put("java.util.EventListener", splitMembers("")); index.put("java.util.HashMap", splitMembers("")); index.put("java.util.HashSet", splitMembers("")); @@ -3977,6 +4195,9 @@ private static void fillMethodIndex28(Map index) { index.put("java.util.Random", splitMembers("")); index.put("java.util.RandomAccess", splitMembers("")); index.put("java.util.Set", splitMembers("")); + } + + private static void fillMethodIndex30(Map index) { index.put("java.util.SortedMap", splitMembers("")); index.put("java.util.SortedSet", splitMembers("")); index.put("java.util.Stack", splitMembers("")); @@ -4037,6 +4258,8 @@ private static Map buildFieldIndex() { fillFieldIndex26(index); fillFieldIndex27(index); fillFieldIndex28(index); + fillFieldIndex29(index); + fillFieldIndex30(index); return index; } @@ -4122,24 +4345,31 @@ private static void fillFieldIndex1(Map index) { index.put("com.codename1.ai.language.Translator", splitMembers("")); index.put("com.codename1.ai.language.Translator.Session", splitMembers("")); index.put("com.codename1.ai.vision.Barcode", splitMembers("")); + index.put("com.codename1.ai.vision.BarcodeFormat", splitMembers("")); index.put("com.codename1.ai.vision.BarcodeScanner", splitMembers("")); + index.put("com.codename1.ai.vision.CodeScanner", splitMembers("")); + index.put("com.codename1.ai.vision.CodeScannerOptions", splitMembers("")); index.put("com.codename1.ai.vision.DocumentScanResult", splitMembers("")); index.put("com.codename1.ai.vision.DocumentScanner", splitMembers("")); index.put("com.codename1.ai.vision.Face", splitMembers("")); index.put("com.codename1.ai.vision.FaceDetector", splitMembers("")); + index.put("com.codename1.ai.vision.FaceLandmarks", splitMembers("")); index.put("com.codename1.ai.vision.ImageLabel", splitMembers("")); index.put("com.codename1.ai.vision.ImageLabeler", splitMembers("")); index.put("com.codename1.ai.vision.Pose", splitMembers("")); index.put("com.codename1.ai.vision.Pose.Landmark", splitMembers("")); index.put("com.codename1.ai.vision.PoseDetector", splitMembers("")); + index.put("com.codename1.ai.vision.PoseLandmarks", splitMembers("")); index.put("com.codename1.ai.vision.SegmentationMask", splitMembers("")); index.put("com.codename1.ai.vision.SelfieSegmenter", splitMembers("")); index.put("com.codename1.ai.vision.TextRecognitionResult", splitMembers("")); index.put("com.codename1.ai.vision.TextRecognitionResult.TextBlock", splitMembers("")); index.put("com.codename1.ai.vision.TextRecognizer", splitMembers("")); + index.put("com.codename1.ai.vision.TextScript", splitMembers("")); index.put("com.codename1.ai.vision.VisionAnalyzer", splitMembers("")); index.put("com.codename1.ai.vision.VisionBackend", splitMembers("")); index.put("com.codename1.ai.vision.VisionBackends", splitMembers("")); + index.put("com.codename1.ai.vision.VisionCameraView", splitMembers("")); index.put("com.codename1.ai.vision.VisionException", splitMembers("")); index.put("com.codename1.ai.vision.VisionFeature", splitMembers("")); index.put("com.codename1.ai.vision.VisionImage", splitMembers("")); @@ -4165,16 +4395,17 @@ private static void fillFieldIndex1(Map index) { index.put("com.codename1.analytics.ConsentMode", splitMembers("")); index.put("com.codename1.analytics.FirebaseAnalyticsProvider", splitMembers("")); index.put("com.codename1.analytics.FirebaseAnalyticsProvider.Bridge", splitMembers("")); + } + + private static void fillFieldIndex2(Map index) { index.put("com.codename1.analytics.GoogleAnalyticsProvider", splitMembers("")); index.put("com.codename1.analytics.LegacyAnalyticsProviderAdapter", splitMembers("")); index.put("com.codename1.analytics.LoggingAnalyticsProvider", splitMembers("")); index.put("com.codename1.analytics.MatomoAnalyticsProvider", splitMembers("")); + index.put("com.codename1.annotations.AppIntent", splitMembers("")); index.put("com.codename1.annotations.Async", splitMembers("")); index.put("com.codename1.annotations.Async.Execute", splitMembers("")); index.put("com.codename1.annotations.Async.Schedule", splitMembers("")); - } - - private static void fillFieldIndex2(Map index) { index.put("com.codename1.annotations.Bind", splitMembers("")); index.put("com.codename1.annotations.Bindable", splitMembers("")); index.put("com.codename1.annotations.Column", splitMembers("")); @@ -4184,9 +4415,17 @@ private static void fillFieldIndex2(Map index) { index.put("com.codename1.annotations.DisableNullChecksAndArrayBoundsChecks", splitMembers("")); index.put("com.codename1.annotations.Email", splitMembers("")); index.put("com.codename1.annotations.Entity", splitMembers("")); + index.put("com.codename1.annotations.EntityId", splitMembers("")); + index.put("com.codename1.annotations.EntityImage", splitMembers("")); + index.put("com.codename1.annotations.EntityQuery", splitMembers("")); + index.put("com.codename1.annotations.EntityQuery.Kind", splitMembers("")); + index.put("com.codename1.annotations.EntitySubtitle", splitMembers("")); + index.put("com.codename1.annotations.EntityTitle", splitMembers("")); index.put("com.codename1.annotations.ExistIn", splitMembers("")); index.put("com.codename1.annotations.Fused", splitMembers("")); index.put("com.codename1.annotations.Id", splitMembers("")); + index.put("com.codename1.annotations.IntentEntity", splitMembers("")); + index.put("com.codename1.annotations.IntentParam", splitMembers("")); index.put("com.codename1.annotations.JsonIgnore", splitMembers("")); index.put("com.codename1.annotations.JsonProperty", splitMembers("")); index.put("com.codename1.annotations.Length", splitMembers("")); @@ -4203,9 +4442,29 @@ private static void fillFieldIndex2(Map index) { index.put("com.codename1.annotations.XmlElement", splitMembers("")); index.put("com.codename1.annotations.XmlRoot", splitMembers("")); index.put("com.codename1.annotations.XmlTransient", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Android", splitMembers("")); + index.put("com.codename1.annotations.buildhints.AndroidThemeMode", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Build", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Desktop", splitMembers("")); + index.put("com.codename1.annotations.buildhints.DesktopTitleBar", splitMembers("")); + index.put("com.codename1.annotations.buildhints.HardenControlFlow", splitMembers("")); + index.put("com.codename1.annotations.buildhints.HardenLevel", splitMembers("")); + index.put("com.codename1.annotations.buildhints.HardenStrings", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Hardening", splitMembers("")); + index.put("com.codename1.annotations.buildhints.InstallLocation", splitMembers("")); + index.put("com.codename1.annotations.buildhints.Ios", splitMembers("")); + index.put("com.codename1.annotations.buildhints.IosDependencyManager", splitMembers("")); + index.put("com.codename1.annotations.buildhints.IosPrivacy", splitMembers("")); + index.put("com.codename1.annotations.buildhints.IosProjectType", splitMembers("")); + index.put("com.codename1.annotations.buildhints.IosThemeMode", splitMembers("")); + index.put("com.codename1.annotations.buildhints.NativeThemeMode", splitMembers("")); + index.put("com.codename1.annotations.buildhints.OnDeviceDebug", splitMembers("")); index.put("com.codename1.annotations.graphql.GraphQLClient", splitMembers("")); index.put("com.codename1.annotations.graphql.Mutation", splitMembers("")); index.put("com.codename1.annotations.graphql.Query", splitMembers("")); + } + + private static void fillFieldIndex3(Map index) { index.put("com.codename1.annotations.graphql.Subscription", splitMembers("")); index.put("com.codename1.annotations.graphql.Var", splitMembers("")); index.put("com.codename1.annotations.grpc.GrpcClient", splitMembers("")); @@ -4239,9 +4498,6 @@ private static void fillFieldIndex2(Map index) { index.put("com.codename1.ar.ARHitResult.Type", splitMembers("")); index.put("com.codename1.ar.ARImageAnchor", splitMembers("")); index.put("com.codename1.ar.ARLightEstimate", splitMembers("")); - } - - private static void fillFieldIndex3(Map index) { index.put("com.codename1.ar.ARModel", splitMembers("")); index.put("com.codename1.ar.ARNode", splitMembers("")); index.put("com.codename1.ar.ARPlane", splitMembers("")); @@ -4273,6 +4529,9 @@ private static void fillFieldIndex3(Map index) { index.put("com.codename1.binding.Binding", splitMembers("")); index.put("com.codename1.binding.NotifiableBinding", splitMembers("")); index.put("com.codename1.bluetooth.AdapterState", splitMembers("")); + } + + private static void fillFieldIndex4(Map index) { index.put("com.codename1.bluetooth.AdapterStateListener", splitMembers("")); index.put("com.codename1.bluetooth.Bluetooth", splitMembers("")); index.put("com.codename1.bluetooth.BluetoothDevice", splitMembers("")); @@ -4306,9 +4565,6 @@ private static void fillFieldIndex3(Map index) { index.put("com.codename1.bluetooth.le.L2capServer", splitMembers("")); index.put("com.codename1.bluetooth.le.ScanFilter", splitMembers("")); index.put("com.codename1.bluetooth.le.ScanListener", splitMembers("")); - } - - private static void fillFieldIndex4(Map index) { index.put("com.codename1.bluetooth.le.ScanMode", splitMembers("")); index.put("com.codename1.bluetooth.le.ScanResult", splitMembers("")); index.put("com.codename1.bluetooth.le.ScanSettings", splitMembers("")); @@ -4340,6 +4596,9 @@ private static void fillFieldIndex4(Map index) { index.put("com.codename1.calendar.CalendarCache", splitMembers("")); index.put("com.codename1.calendar.CalendarCapabilities", splitMembers("")); index.put("com.codename1.calendar.CalendarCapability", splitMembers("")); + } + + private static void fillFieldIndex5(Map index) { index.put("com.codename1.calendar.CalendarChange", splitMembers("")); index.put("com.codename1.calendar.CalendarChange.ChangeType", splitMembers("")); index.put("com.codename1.calendar.CalendarChange.EntityType", splitMembers("")); @@ -4373,9 +4632,6 @@ private static void fillFieldIndex4(Map index) { index.put("com.codename1.calendar.CalendarTokenProvider", splitMembers("")); index.put("com.codename1.calendar.DefaultCalendarHttpTransport", splitMembers("")); index.put("com.codename1.calendar.FreeBusyInterval", splitMembers("")); - } - - private static void fillFieldIndex5(Map index) { index.put("com.codename1.calendar.GoogleCalendarSource", splitMembers("")); index.put("com.codename1.calendar.ICalendarCodec", splitMembers("")); index.put("com.codename1.calendar.LocalCalendarSource", splitMembers("")); @@ -4407,6 +4663,9 @@ private static void fillFieldIndex5(Map index) { index.put("com.codename1.car.CarActionListener", splitMembers("")); index.put("com.codename1.car.CarActionStrip", splitMembers("")); index.put("com.codename1.car.CarApplication", splitMembers("")); + } + + private static void fillFieldIndex6(Map index) { index.put("com.codename1.car.CarColor", splitMembers("")); index.put("com.codename1.car.CarConnectionListener", splitMembers("")); index.put("com.codename1.car.CarContext", splitMembers("")); @@ -4440,9 +4699,6 @@ private static void fillFieldIndex5(Map index) { index.put("com.codename1.charts.models.Point", splitMembers("")); index.put("com.codename1.charts.models.RangeCategorySeries", splitMembers("")); index.put("com.codename1.charts.models.SeriesSelection", splitMembers("")); - } - - private static void fillFieldIndex6(Map index) { index.put("com.codename1.charts.models.TimeSeries", splitMembers("")); index.put("com.codename1.charts.models.XYMultipleSeriesDataset", splitMembers("")); index.put("com.codename1.charts.models.XYSeries", splitMembers("")); @@ -4474,6 +4730,9 @@ private static void fillFieldIndex6(Map index) { index.put("com.codename1.charts.views.CubicLineChart", splitMembers("")); index.put("com.codename1.charts.views.DialChart", splitMembers("")); index.put("com.codename1.charts.views.DoughnutChart", splitMembers("")); + } + + private static void fillFieldIndex7(Map index) { index.put("com.codename1.charts.views.LineChart", splitMembers("")); index.put("com.codename1.charts.views.PieChart", splitMembers("")); index.put("com.codename1.charts.views.PieMapper", splitMembers("")); @@ -4507,9 +4766,6 @@ private static void fillFieldIndex6(Map index) { index.put("com.codename1.components.FileTreeModel", splitMembers("")); index.put("com.codename1.components.FloatingActionButton", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSTATE_DEFAULTSTATE_PRESSEDSTATE_ROLLOVERSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.components.FloatingHint", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); - } - - private static void fillFieldIndex7(Map index) { index.put("com.codename1.components.ImageViewer", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORIMAGE_FILLIMAGE_FITLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.components.InfiniteProgress", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.components.InfiniteScrollAdapter", splitMembers("")); @@ -4541,13 +4797,19 @@ private static void fillFieldIndex7(Map index) { index.put("com.codename1.components.ToastBar", splitMembers("")); index.put("com.codename1.components.WebBrowser", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.contacts.Address", splitMembers("")); + } + + private static void fillFieldIndex8(Map index) { index.put("com.codename1.contacts.Contact", splitMembers("")); index.put("com.codename1.contacts.ContactsManager", splitMembers("")); index.put("com.codename1.contacts.ContactsModel", splitMembers("")); index.put("com.codename1.crash.CrashProtection", splitMembers("")); index.put("com.codename1.crash.PiiScrubber", splitMembers("")); index.put("com.codename1.db.Cursor", splitMembers("")); + index.put("com.codename1.db.CursorExt", splitMembers("")); index.put("com.codename1.db.Database", splitMembers("")); + index.put("com.codename1.db.DatabaseConfig", splitMembers("")); + index.put("com.codename1.db.DatabaseEncryptionException", splitMembers("")); index.put("com.codename1.db.Row", splitMembers("")); index.put("com.codename1.db.RowExt", splitMembers("")); index.put("com.codename1.db.ThreadSafeDatabase", splitMembers("")); @@ -4574,9 +4836,6 @@ private static void fillFieldIndex7(Map index) { index.put("com.codename1.gaming.VirtualButton", splitMembers("")); index.put("com.codename1.gaming.VirtualJoystick", splitMembers("")); index.put("com.codename1.gaming.VoiceListener", splitMembers("")); - } - - private static void fillFieldIndex8(Map index) { index.put("com.codename1.gaming.level.AssetCatalog", splitMembers("")); index.put("com.codename1.gaming.level.AssetDef", splitMembers("")); index.put("com.codename1.gaming.level.AssetDef.Kind", splitMembers("")); @@ -4605,6 +4864,9 @@ private static void fillFieldIndex8(Map index) { index.put("com.codename1.gaming.level.TileLayer", splitMembers("")); index.put("com.codename1.gaming.physics.BodyType", splitMembers("")); index.put("com.codename1.gaming.physics.ContactListener", splitMembers("")); + } + + private static void fillFieldIndex9(Map index) { index.put("com.codename1.gaming.physics.PhysicsBody", splitMembers("")); index.put("com.codename1.gaming.physics.PhysicsContact", splitMembers("")); index.put("com.codename1.gaming.physics.PhysicsJoint", splitMembers("")); @@ -4641,9 +4903,6 @@ private static void fillFieldIndex8(Map index) { index.put("com.codename1.gaming.physics.box2d.collision.TimeOfImpact.TOIOutput", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.collision.TimeOfImpact.TOIOutputState", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.collision.WorldManifold", splitMembers("")); - } - - private static void fillFieldIndex9(Map index) { index.put("com.codename1.gaming.physics.box2d.collision.broadphase.BroadPhase", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.collision.broadphase.BroadPhaseStrategy", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.collision.broadphase.DynamicTree", splitMembers("")); @@ -4672,6 +4931,9 @@ private static void fillFieldIndex9(Map index) { index.put("com.codename1.gaming.physics.box2d.common.Vec3", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.Body", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.BodyDef", splitMembers("")); + } + + private static void fillFieldIndex10(Map index) { index.put("com.codename1.gaming.physics.box2d.dynamics.BodyType", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.ContactManager", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.Filter", splitMembers("")); @@ -4708,9 +4970,6 @@ private static void fillFieldIndex9(Map index) { index.put("com.codename1.gaming.physics.box2d.dynamics.joints.FrictionJoint", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.joints.FrictionJointDef", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.joints.GearJoint", splitMembers("")); - } - - private static void fillFieldIndex10(Map index) { index.put("com.codename1.gaming.physics.box2d.dynamics.joints.GearJointDef", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.joints.Jacobian", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.dynamics.joints.Joint", splitMembers("")); @@ -4739,6 +4998,9 @@ private static void fillFieldIndex10(Map index) { index.put("com.codename1.gaming.physics.box2d.pooling.arrays.IntArray", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.pooling.arrays.Vec2Array", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.pooling.normal.CircleStack", splitMembers("")); + } + + private static void fillFieldIndex11(Map index) { index.put("com.codename1.gaming.physics.box2d.pooling.normal.DefaultWorldPool", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.pooling.normal.MutableStack", splitMembers("")); index.put("com.codename1.gaming.physics.box2d.pooling.normal.OrderedStack", splitMembers("")); @@ -4775,9 +5037,6 @@ private static void fillFieldIndex10(Map index) { index.put("com.codename1.health.AggregateResult", splitMembers("")); index.put("com.codename1.health.BloodPressureSample", splitMembers("")); index.put("com.codename1.health.CategorySample", splitMembers("")); - } - - private static void fillFieldIndex11(Map index) { index.put("com.codename1.health.Health", splitMembers("")); index.put("com.codename1.health.HealthAccess", splitMembers("")); index.put("com.codename1.health.HealthAggregationStyle", splitMembers("")); @@ -4806,6 +5065,9 @@ private static void fillFieldIndex11(Map index) { index.put("com.codename1.health.HealthUnitDimension", splitMembers("")); index.put("com.codename1.health.HealthWriteResult", splitMembers("")); index.put("com.codename1.health.QuantitySample", splitMembers("")); + } + + private static void fillFieldIndex12(Map index) { index.put("com.codename1.health.RecordingMethod", splitMembers("")); index.put("com.codename1.health.SamplePage", splitMembers("")); index.put("com.codename1.health.SampleQuery", splitMembers("")); @@ -4842,9 +5104,6 @@ private static void fillFieldIndex11(Map index) { index.put("com.codename1.health.sensors.TemperatureMeasurement", splitMembers("")); index.put("com.codename1.health.sensors.WeightMeasurement", splitMembers("")); index.put("com.codename1.health.workout.WorkoutConfiguration", splitMembers("")); - } - - private static void fillFieldIndex12(Map index) { index.put("com.codename1.health.workout.WorkoutEvent", splitMembers("")); index.put("com.codename1.health.workout.WorkoutEvent.Kind", splitMembers("")); index.put("com.codename1.health.workout.WorkoutLocationType", splitMembers("")); @@ -4852,6 +5111,73 @@ private static void fillFieldIndex12(Map index) { index.put("com.codename1.health.workout.WorkoutSession", splitMembers("")); index.put("com.codename1.health.workout.WorkoutSessionListener", splitMembers("")); index.put("com.codename1.health.workout.WorkoutSessionState", splitMembers("")); + index.put("com.codename1.home.Accessory", splitMembers("")); + index.put("com.codename1.home.AccessoryCategory", splitMembers("")); + index.put("com.codename1.home.AccessoryService", splitMembers("")); + index.put("com.codename1.home.AirQualityLevel", splitMembers("")); + index.put("com.codename1.home.AlarmState", splitMembers("")); + index.put("com.codename1.home.ChargingState", splitMembers("")); + index.put("com.codename1.home.DoorState", splitMembers("")); + index.put("com.codename1.home.FanMode", splitMembers("")); + index.put("com.codename1.home.HeatingCoolingMode", splitMembers("")); + index.put("com.codename1.home.HomeAuthorizationStatus", splitMembers("")); + index.put("com.codename1.home.HomeAvailability", splitMembers("")); + index.put("com.codename1.home.HomeBackend", splitMembers("")); + index.put("com.codename1.home.HomeChangeListener", splitMembers("")); + index.put("com.codename1.home.HomeConfigurationException", splitMembers("")); + index.put("com.codename1.home.HomeError", splitMembers("")); + index.put("com.codename1.home.HomeException", splitMembers("")); + index.put("com.codename1.home.HomeRoom", splitMembers("")); + index.put("com.codename1.home.HomeStructure", splitMembers("")); + index.put("com.codename1.home.HomeStructureEvent", splitMembers("")); + index.put("com.codename1.home.HomeStructureListener", splitMembers("")); + index.put("com.codename1.home.HomeZone", splitMembers("")); + } + + private static void fillFieldIndex13(Map index) { + index.put("com.codename1.home.LockState", splitMembers("")); + index.put("com.codename1.home.PositionState", splitMembers("")); + index.put("com.codename1.home.Scene", splitMembers("")); + index.put("com.codename1.home.SceneAction", splitMembers("")); + index.put("com.codename1.home.SceneType", splitMembers("")); + index.put("com.codename1.home.ServiceType", splitMembers("")); + index.put("com.codename1.home.SmartHome", splitMembers("")); + index.put("com.codename1.home.StructureChangeKind", splitMembers("")); + index.put("com.codename1.home.SubscriptionRequest", splitMembers("")); + index.put("com.codename1.home.Trait", splitMembers("")); + index.put("com.codename1.home.TraitChangeBatch", splitMembers("")); + index.put("com.codename1.home.TraitConstraint", splitMembers("")); + index.put("com.codename1.home.TraitReadRequest", splitMembers("")); + index.put("com.codename1.home.TraitReading", splitMembers("")); + index.put("com.codename1.home.TraitSubscription", splitMembers("")); + index.put("com.codename1.home.TraitUnit", splitMembers("")); + index.put("com.codename1.home.TraitUnitDimension", splitMembers("")); + index.put("com.codename1.home.TraitValue", splitMembers("")); + index.put("com.codename1.home.TraitValueKind", splitMembers("")); + index.put("com.codename1.home.TraitWrite", splitMembers("")); + index.put("com.codename1.home.TraitWriteResult", splitMembers("")); + index.put("com.codename1.home.commissioning.Commissioner", splitMembers("")); + index.put("com.codename1.home.commissioning.CommissioningRequest", splitMembers("")); + index.put("com.codename1.home.commissioning.CommissioningResult", splitMembers("")); + index.put("com.codename1.home.commissioning.CommissioningStyle", splitMembers("")); + index.put("com.codename1.home.commissioning.SetupPayload", splitMembers("")); + index.put("com.codename1.home.spi.HomeBridge", splitMembers("")); + index.put("com.codename1.intents.AppEntity", splitMembers("")); + index.put("com.codename1.intents.DynamicIntent", splitMembers("")); + index.put("com.codename1.intents.EntitySelectionHandler", splitMembers("")); + index.put("com.codename1.intents.Exposure", splitMembers("")); + index.put("com.codename1.intents.IntentCompletion", splitMembers("")); + index.put("com.codename1.intents.IntentContext", splitMembers("")); + index.put("com.codename1.intents.IntentDates", splitMembers("")); + index.put("com.codename1.intents.IntentDeclaration", splitMembers("")); + index.put("com.codename1.intents.IntentDispatcher", splitMembers("")); + index.put("com.codename1.intents.IntentParameterInfo", splitMembers("")); + index.put("com.codename1.intents.IntentParameterType", splitMembers("")); + index.put("com.codename1.intents.IntentResult", splitMembers("")); + index.put("com.codename1.intents.IntentSerializer", splitMembers("")); + index.put("com.codename1.intents.IntentSource", splitMembers("")); + index.put("com.codename1.intents.Intents", splitMembers("")); + index.put("com.codename1.intents.spi.IntentBridge", splitMembers("")); index.put("com.codename1.io.AccessToken", splitMembers("")); index.put("com.codename1.io.BufferedInputStream", splitMembers("")); index.put("com.codename1.io.BufferedOutputStream", splitMembers("")); @@ -4873,6 +5199,9 @@ private static void fillFieldIndex12(Map index) { index.put("com.codename1.io.JSONParser", splitMembers("")); index.put("com.codename1.io.JSONParser.RawJson", splitMembers("")); index.put("com.codename1.io.JSONWriter", splitMembers("")); + } + + private static void fillFieldIndex14(Map index) { index.put("com.codename1.io.JSONWriter.ArrayBuilder", splitMembers("")); index.put("com.codename1.io.JSONWriter.ObjectBuilder", splitMembers("")); index.put("com.codename1.io.Log", splitMembers("")); @@ -4909,9 +5238,6 @@ private static void fillFieldIndex12(Map index) { index.put("com.codename1.io.bonjour.BonjourService", splitMembers("")); index.put("com.codename1.io.bonjour.BonjourServiceListener", splitMembers("")); index.put("com.codename1.io.graphql.GraphQL", splitMembers("")); - } - - private static void fillFieldIndex13(Map index) { index.put("com.codename1.io.graphql.GraphQLClients", splitMembers("")); index.put("com.codename1.io.graphql.GraphQLClients.Factory", splitMembers("")); index.put("com.codename1.io.graphql.GraphQLError", splitMembers("")); @@ -4940,6 +5266,9 @@ private static void fillFieldIndex13(Map index) { index.put("com.codename1.io.gzip.GZIPHeader", splitMembers("")); index.put("com.codename1.io.gzip.GZIPInputStream", splitMembers("")); index.put("com.codename1.io.gzip.GZIPOutputStream", splitMembers("")); + } + + private static void fillFieldIndex15(Map index) { index.put("com.codename1.io.gzip.Inflater", splitMembers("")); index.put("com.codename1.io.gzip.InflaterInputStream", splitMembers("")); index.put("com.codename1.io.gzip.JZlib", splitMembers("")); @@ -4976,9 +5305,6 @@ private static void fillFieldIndex13(Map index) { index.put("com.codename1.io.usb.UsbDeviceListener", splitMembers("")); index.put("com.codename1.io.usb.UsbPlatform", splitMembers("")); index.put("com.codename1.io.webauthn.PublicKeyCredential", splitMembers("")); - } - - private static void fillFieldIndex14(Map index) { index.put("com.codename1.io.webauthn.PublicKeyCredentialCreationOptions", splitMembers("")); index.put("com.codename1.io.webauthn.PublicKeyCredentialCreationOptions.Builder", splitMembers("")); index.put("com.codename1.io.webauthn.PublicKeyCredentialRequestOptions", splitMembers("")); @@ -5007,6 +5333,9 @@ private static void fillFieldIndex14(Map index) { index.put("com.codename1.l10n.ParseException", splitMembers("")); index.put("com.codename1.l10n.SimpleDateFormat", splitMembers("")); index.put("com.codename1.location.Geofence", splitMembers("")); + } + + private static void fillFieldIndex16(Map index) { index.put("com.codename1.location.GeofenceListener", splitMembers("")); index.put("com.codename1.location.GeofenceManager", splitMembers("")); index.put("com.codename1.location.GeofenceManager.Listener", splitMembers("")); @@ -5043,9 +5372,6 @@ private static void fillFieldIndex14(Map index) { index.put("com.codename1.maps.layers.AbstractLayer", splitMembers("")); index.put("com.codename1.maps.layers.ArrowLinesLayer", splitMembers("")); index.put("com.codename1.maps.layers.Layer", splitMembers("")); - } - - private static void fillFieldIndex15(Map index) { index.put("com.codename1.maps.layers.LinesLayer", splitMembers("")); index.put("com.codename1.maps.layers.PointLayer", splitMembers("")); index.put("com.codename1.maps.layers.PointsLayer", splitMembers("")); @@ -5074,6 +5400,9 @@ private static void fillFieldIndex15(Map index) { index.put("com.codename1.maps.vector.StyleLayer", splitMembers("")); index.put("com.codename1.maps.vector.TileCallback", splitMembers("")); index.put("com.codename1.maps.vector.TileSource", splitMembers("")); + } + + private static void fillFieldIndex17(Map index) { index.put("com.codename1.maps.vector.VectorFeature", splitMembers("")); index.put("com.codename1.maps.vector.VectorLayer", splitMembers("")); index.put("com.codename1.maps.vector.VectorMapEngine", splitMembers("")); @@ -5110,9 +5439,6 @@ private static void fillFieldIndex15(Map index) { index.put("com.codename1.media.SpeechRecognizer", splitMembers("")); index.put("com.codename1.media.TextToSpeech", splitMembers("")); index.put("com.codename1.media.TimedRecognitionCallback", splitMembers("")); - } - - private static void fillFieldIndex16(Map index) { index.put("com.codename1.media.Transcriber", splitMembers("")); index.put("com.codename1.media.TranscriptionRequest", splitMembers("")); index.put("com.codename1.media.TranscriptionResult", splitMembers("")); @@ -5141,6 +5467,9 @@ private static void fillFieldIndex16(Map index) { index.put("com.codename1.nfc.NfcError", splitMembers("")); index.put("com.codename1.nfc.NfcException", splitMembers("")); index.put("com.codename1.nfc.NfcF", splitMembers("")); + } + + private static void fillFieldIndex18(Map index) { index.put("com.codename1.nfc.NfcListener", splitMembers("")); index.put("com.codename1.nfc.NfcReadOptions", splitMembers("")); index.put("com.codename1.nfc.NfcV", splitMembers("")); @@ -5177,9 +5506,6 @@ private static void fillFieldIndex16(Map index) { index.put("com.codename1.plugin.event.OpenGalleryEvent", splitMembers("")); index.put("com.codename1.plugin.event.PluginEvent", splitMembers("")); index.put("com.codename1.printing.PrintResult", splitMembers("")); - } - - private static void fillFieldIndex17(Map index) { index.put("com.codename1.printing.PrintResultListener", splitMembers("")); index.put("com.codename1.printing.Printer", splitMembers("")); index.put("com.codename1.processing.Result", splitMembers("")); @@ -5208,6 +5534,9 @@ private static void fillFieldIndex17(Map index) { index.put("com.codename1.properties.UiBinding", splitMembers("")); index.put("com.codename1.properties.UiBinding.BooleanConverter", splitMembers("")); index.put("com.codename1.properties.UiBinding.BoundTableModel", splitMembers("")); + } + + private static void fillFieldIndex19(Map index) { index.put("com.codename1.properties.UiBinding.CheckBoxRadioSelectionAdapter", splitMembers("")); index.put("com.codename1.properties.UiBinding.ComponentAdapter", splitMembers("")); index.put("com.codename1.properties.UiBinding.DateConverter", splitMembers("")); @@ -5244,9 +5573,6 @@ private static void fillFieldIndex17(Map index) { index.put("com.codename1.router.PopGuard", splitMembers("")); index.put("com.codename1.router.PopReason", splitMembers("")); index.put("com.codename1.router.RouteDispatcher", splitMembers("")); - } - - private static void fillFieldIndex18(Map index) { index.put("com.codename1.security.AuthenticationOptions", splitMembers("")); index.put("com.codename1.security.Base32", splitMembers("")); index.put("com.codename1.security.BiometricError", splitMembers("")); @@ -5270,9 +5596,14 @@ private static void fillFieldIndex18(Map index) { index.put("com.codename1.security.SecureRandom", splitMembers("")); index.put("com.codename1.security.SecureStorage", splitMembers("")); index.put("com.codename1.security.Signature", splitMembers("")); + index.put("com.codename1.security.TapjackingPolicy", splitMembers("")); + index.put("com.codename1.security.hardening.Hardening", splitMembers("")); index.put("com.codename1.security.shield.AppShield", splitMembers("")); index.put("com.codename1.security.shield.FailureMode", splitMembers("")); index.put("com.codename1.security.shield.HostPolicy", splitMembers("")); + } + + private static void fillFieldIndex20(Map index) { index.put("com.codename1.security.shield.PinSet", splitMembers("")); index.put("com.codename1.security.shield.ShieldConfig", splitMembers("")); index.put("com.codename1.security.shield.ShieldException", splitMembers("")); @@ -5311,9 +5642,6 @@ private static void fillFieldIndex18(Map index) { index.put("com.codename1.social.Login", splitMembers("")); index.put("com.codename1.social.LoginCallback", splitMembers("")); index.put("com.codename1.social.MicrosoftConnect", splitMembers("")); - } - - private static void fillFieldIndex19(Map index) { index.put("com.codename1.surfaces.LiveActivity", splitMembers("")); index.put("com.codename1.surfaces.LiveActivityDescriptor", splitMembers("")); index.put("com.codename1.surfaces.SurfaceActionEvent", splitMembers("")); @@ -5340,6 +5668,9 @@ private static void fillFieldIndex19(Map index) { index.put("com.codename1.surfaces.WidgetKind", splitMembers("")); index.put("com.codename1.surfaces.WidgetSize", splitMembers("")); index.put("com.codename1.surfaces.WidgetTimeline", splitMembers("")); + } + + private static void fillFieldIndex21(Map index) { index.put("com.codename1.surfaces.WidgetTimeline.Entry", splitMembers("")); index.put("com.codename1.surfaces.spi.SurfaceBridge", splitMembers("")); index.put("com.codename1.system.CrashReport", splitMembers("")); @@ -5378,9 +5709,6 @@ private static void fillFieldIndex19(Map index) { index.put("com.codename1.ui.CheckBox", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSTATE_DEFAULTSTATE_PRESSEDSTATE_ROLLOVERSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.ui.ClipboardContent", splitMembers("MIME_ASCIIDOCMIME_FILEMIME_GIFMIME_HTMLMIME_JPEGMIME_MARKDOWNMIME_PNGMIME_RTFMIME_TEXT")); index.put("com.codename1.ui.CodeCompletion", splitMembers("")); - } - - private static void fillFieldIndex20(Map index) { index.put("com.codename1.ui.CodeCompletionProvider", splitMembers("")); index.put("com.codename1.ui.CodeDiagnostic", splitMembers("ERRORINFOWARNING")); index.put("com.codename1.ui.CodeEditor", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); @@ -5407,6 +5735,9 @@ private static void fillFieldIndex20(Map index) { index.put("com.codename1.ui.EditField", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORKEY_BACKSPACEKEY_COPYKEY_CUTKEY_DELETEKEY_DOWNKEY_ENDKEY_ESCAPEKEY_HOMEKEY_LEFTKEY_PAGE_DOWNKEY_PAGE_UPKEY_PASTEKEY_REDOKEY_RIGHTKEY_SELECT_ALLKEY_TABKEY_UNDOKEY_UPLEFTMOD_ALTMOD_CTRLMOD_SHIFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.ui.Editable", splitMembers("")); index.put("com.codename1.ui.EncodedImage", splitMembers("")); + } + + private static void fillFieldIndex22(Map index) { index.put("com.codename1.ui.Font", splitMembers("BASELINEBOTTOMCENTERCENTER_BEHAVIOR_CENTERCENTER_BEHAVIOR_CENTER_ABSOLUTECENTER_BEHAVIOR_SCALECENTER_BEHAVIOR_TOTAL_BELOWDENSITY_2HDDENSITY_4KDENSITY_560DENSITY_HDDENSITY_HIGHDENSITY_LOWDENSITY_MEDIUMDENSITY_VERY_HIGHDENSITY_VERY_LOWEASTFACE_MONOSPACEFACE_PROPORTIONALFACE_SYSTEMGALLERY_ALLGALLERY_ALL_MULTIGALLERY_IMAGEGALLERY_IMAGE_MULTIGALLERY_VIDEOGALLERY_VIDEO_MULTILEFTNATIVE_ITALIC_BLACKNATIVE_ITALIC_BOLDNATIVE_ITALIC_LIGHTNATIVE_ITALIC_REGULARNATIVE_ITALIC_THINNATIVE_MAIN_BLACKNATIVE_MAIN_BOLDNATIVE_MAIN_LIGHTNATIVE_MAIN_REGULARNATIVE_MAIN_THINNORTHPICKER_TYPE_CALENDARPICKER_TYPE_DATEPICKER_TYPE_DATE_AND_TIMEPICKER_TYPE_DURATIONPICKER_TYPE_DURATION_HOURSPICKER_TYPE_DURATION_MINUTESPICKER_TYPE_STRINGSPICKER_TYPE_TIMERIGHTSIZE_LARGESIZE_MEDIUMSIZE_SMALLSMS_BOTHSMS_INTERACTIVESMS_NOT_SUPPORTEDSMS_SEAMLESSSOUTHSTYLE_BOLDSTYLE_ITALICSTYLE_PLAINSTYLE_UNDERLINEDTOPWEST")); index.put("com.codename1.ui.FontImage", splitMembers("MATERIAL_10KMATERIAL_10MPMATERIAL_11MPMATERIAL_123MATERIAL_12MPMATERIAL_13MPMATERIAL_14MPMATERIAL_15MPMATERIAL_16MPMATERIAL_17MPMATERIAL_18MPMATERIAL_18_UP_RATINGMATERIAL_19MPMATERIAL_1KMATERIAL_1K_PLUSMATERIAL_1X_MOBILEDATAMATERIAL_20MPMATERIAL_21MPMATERIAL_22MPMATERIAL_23MPMATERIAL_24MPMATERIAL_2KMATERIAL_2K_PLUSMATERIAL_2MPMATERIAL_30FPSMATERIAL_30FPS_SELECTMATERIAL_360MATERIAL_3D_ROTATIONMATERIAL_3G_MOBILEDATAMATERIAL_3KMATERIAL_3K_PLUSMATERIAL_3MPMATERIAL_3PMATERIAL_4G_MOBILEDATAMATERIAL_4G_PLUS_MOBILEDATAMATERIAL_4KMATERIAL_4K_PLUSMATERIAL_4MPMATERIAL_5GMATERIAL_5KMATERIAL_5K_PLUSMATERIAL_5MPMATERIAL_60FPSMATERIAL_60FPS_SELECTMATERIAL_6KMATERIAL_6K_PLUSMATERIAL_6MPMATERIAL_6_FT_APARTMATERIAL_7KMATERIAL_7K_PLUSMATERIAL_7MPMATERIAL_8KMATERIAL_8K_PLUSMATERIAL_8MPMATERIAL_9KMATERIAL_9K_PLUSMATERIAL_9MPMATERIAL_ABCMATERIAL_ACCESSIBILITYMATERIAL_ACCESSIBILITY_NEWMATERIAL_ACCESSIBLEMATERIAL_ACCESSIBLE_FORWARDMATERIAL_ACCESS_ALARMMATERIAL_ACCESS_ALARMSMATERIAL_ACCESS_TIMEMATERIAL_ACCESS_TIME_FILLEDMATERIAL_ACCOUNT_BALANCEMATERIAL_ACCOUNT_BALANCE_WALLETMATERIAL_ACCOUNT_BOXMATERIAL_ACCOUNT_CIRCLEMATERIAL_ACCOUNT_TREEMATERIAL_AC_UNITMATERIAL_ADBMATERIAL_ADDMATERIAL_ADDCHARTMATERIAL_ADD_ALARMMATERIAL_ADD_ALERTMATERIAL_ADD_A_PHOTOMATERIAL_ADD_BOXMATERIAL_ADD_BUSINESSMATERIAL_ADD_CALLMATERIAL_ADD_CARDMATERIAL_ADD_CHARTMATERIAL_ADD_CIRCLEMATERIAL_ADD_CIRCLE_OUTLINEMATERIAL_ADD_COMMENTMATERIAL_ADD_HOMEMATERIAL_ADD_HOME_WORKMATERIAL_ADD_IC_CALLMATERIAL_ADD_LINKMATERIAL_ADD_LOCATIONMATERIAL_ADD_LOCATION_ALTMATERIAL_ADD_MODERATORMATERIAL_ADD_PHOTO_ALTERNATEMATERIAL_ADD_REACTIONMATERIAL_ADD_ROADMATERIAL_ADD_SHOPPING_CARTMATERIAL_ADD_TASKMATERIAL_ADD_TO_DRIVEMATERIAL_ADD_TO_HOME_SCREENMATERIAL_ADD_TO_PHOTOSMATERIAL_ADD_TO_QUEUEMATERIAL_ADF_SCANNERMATERIAL_ADJUSTMATERIAL_ADMIN_PANEL_SETTINGSMATERIAL_ADOBEMATERIAL_ADS_CLICKMATERIAL_AD_UNITSMATERIAL_AGRICULTUREMATERIAL_AIRMATERIAL_AIRLINESMATERIAL_AIRLINE_SEAT_FLATMATERIAL_AIRLINE_SEAT_FLAT_ANGLEDMATERIAL_AIRLINE_SEAT_INDIVIDUAL_SUITEMATERIAL_AIRLINE_SEAT_LEGROOM_EXTRAMATERIAL_AIRLINE_SEAT_LEGROOM_NORMALMATERIAL_AIRLINE_SEAT_LEGROOM_REDUCEDMATERIAL_AIRLINE_SEAT_RECLINE_EXTRAMATERIAL_AIRLINE_SEAT_RECLINE_NORMALMATERIAL_AIRLINE_STOPSMATERIAL_AIRPLANEMODE_ACTIVEMATERIAL_AIRPLANEMODE_INACTIVEMATERIAL_AIRPLANEMODE_OFFMATERIAL_AIRPLANEMODE_ONMATERIAL_AIRPLANE_TICKETMATERIAL_AIRPLAYMATERIAL_AIRPORT_SHUTTLEMATERIAL_ALARMMATERIAL_ALARM_ADDMATERIAL_ALARM_OFFMATERIAL_ALARM_ONMATERIAL_ALBUMMATERIAL_ALIGN_HORIZONTAL_CENTERMATERIAL_ALIGN_HORIZONTAL_LEFTMATERIAL_ALIGN_HORIZONTAL_RIGHTMATERIAL_ALIGN_VERTICAL_BOTTOMMATERIAL_ALIGN_VERTICAL_CENTERMATERIAL_ALIGN_VERTICAL_TOPMATERIAL_ALL_INBOXMATERIAL_ALL_INCLUSIVEMATERIAL_ALL_OUTMATERIAL_ALTERNATE_EMAILMATERIAL_ALT_ROUTEMATERIAL_AMP_STORIESMATERIAL_ANALYTICSMATERIAL_ANCHORMATERIAL_ANDROIDMATERIAL_ANIMATIONMATERIAL_ANNOUNCEMENTMATERIAL_AODMATERIAL_APARTMENTMATERIAL_APIMATERIAL_APPLEMATERIAL_APPROVALMATERIAL_APPSMATERIAL_APPS_OUTAGEMATERIAL_APP_BLOCKINGMATERIAL_APP_REGISTRATIONMATERIAL_APP_SETTINGS_ALTMATERIAL_APP_SHORTCUTMATERIAL_ARCHITECTUREMATERIAL_ARCHIVEMATERIAL_AREA_CHARTMATERIAL_ARROW_BACKMATERIAL_ARROW_BACK_IOSMATERIAL_ARROW_BACK_IOS_NEWMATERIAL_ARROW_CIRCLE_DOWNMATERIAL_ARROW_CIRCLE_LEFTMATERIAL_ARROW_CIRCLE_RIGHTMATERIAL_ARROW_CIRCLE_UPMATERIAL_ARROW_DOWNWARDMATERIAL_ARROW_DROP_DOWNMATERIAL_ARROW_DROP_DOWN_CIRCLEMATERIAL_ARROW_DROP_UPMATERIAL_ARROW_FORWARDMATERIAL_ARROW_FORWARD_IOSMATERIAL_ARROW_LEFTMATERIAL_ARROW_OUTWARDMATERIAL_ARROW_RIGHTMATERIAL_ARROW_RIGHT_ALTMATERIAL_ARROW_UPWARDMATERIAL_ARTICLEMATERIAL_ART_TRACKMATERIAL_ASPECT_RATIOMATERIAL_ASSESSMENTMATERIAL_ASSIGNMENTMATERIAL_ASSIGNMENT_ADDMATERIAL_ASSIGNMENT_INDMATERIAL_ASSIGNMENT_LATEMATERIAL_ASSIGNMENT_RETURNMATERIAL_ASSIGNMENT_RETURNEDMATERIAL_ASSIGNMENT_TURNED_INMATERIAL_ASSISTANTMATERIAL_ASSISTANT_DIRECTIONMATERIAL_ASSISTANT_NAVIGATIONMATERIAL_ASSISTANT_PHOTOMATERIAL_ASSIST_WALKERMATERIAL_ASSURED_WORKLOADMATERIAL_ATMMATERIAL_ATTACHMENTMATERIAL_ATTACH_EMAILMATERIAL_ATTACH_FILEMATERIAL_ATTACH_MONEYMATERIAL_ATTRACTIONSMATERIAL_ATTRIBUTIONMATERIAL_AUDIOTRACKMATERIAL_AUDIO_FILEMATERIAL_AUTOFPS_SELECTMATERIAL_AUTORENEWMATERIAL_AUTO_AWESOMEMATERIAL_AUTO_AWESOME_MOSAICMATERIAL_AUTO_AWESOME_MOTIONMATERIAL_AUTO_DELETEMATERIAL_AUTO_FIX_HIGHMATERIAL_AUTO_FIX_NORMALMATERIAL_AUTO_FIX_OFFMATERIAL_AUTO_GRAPHMATERIAL_AUTO_MODEMATERIAL_AUTO_STORIESMATERIAL_AV_TIMERMATERIAL_BABY_CHANGING_STATIONMATERIAL_BACKPACKMATERIAL_BACKSPACEMATERIAL_BACKUPMATERIAL_BACKUP_TABLEMATERIAL_BACK_HANDMATERIAL_BADGEMATERIAL_BAKERY_DININGMATERIAL_BALANCEMATERIAL_BALCONYMATERIAL_BALLOTMATERIAL_BARCODE_READERMATERIAL_BAR_CHARTMATERIAL_BATCH_PREDICTIONMATERIAL_BATHROOMMATERIAL_BATHTUBMATERIAL_BATTERY_0_BARMATERIAL_BATTERY_1_BARMATERIAL_BATTERY_2_BARMATERIAL_BATTERY_3_BARMATERIAL_BATTERY_4_BARMATERIAL_BATTERY_5_BARMATERIAL_BATTERY_6_BARMATERIAL_BATTERY_ALERTMATERIAL_BATTERY_CHARGING_FULLMATERIAL_BATTERY_FULLMATERIAL_BATTERY_SAVERMATERIAL_BATTERY_STDMATERIAL_BATTERY_UNKNOWNMATERIAL_BEACH_ACCESSMATERIAL_BEDMATERIAL_BEDROOM_BABYMATERIAL_BEDROOM_CHILDMATERIAL_BEDROOM_PARENTMATERIAL_BEDTIMEMATERIAL_BEDTIME_OFFMATERIAL_BEENHEREMATERIAL_BENTOMATERIAL_BIKE_SCOOTERMATERIAL_BIOTECHMATERIAL_BLENDERMATERIAL_BLINDMATERIAL_BLINDSMATERIAL_BLINDS_CLOSEDMATERIAL_BLOCKMATERIAL_BLOCK_FLIPPEDMATERIAL_BLOODTYPEMATERIAL_BLUETOOTHMATERIAL_BLUETOOTH_AUDIOMATERIAL_BLUETOOTH_CONNECTEDMATERIAL_BLUETOOTH_DISABLEDMATERIAL_BLUETOOTH_DRIVEMATERIAL_BLUETOOTH_SEARCHINGMATERIAL_BLUR_CIRCULARMATERIAL_BLUR_LINEARMATERIAL_BLUR_OFFMATERIAL_BLUR_ONMATERIAL_BOLTMATERIAL_BOOKMATERIAL_BOOKMARKMATERIAL_BOOKMARKSMATERIAL_BOOKMARK_ADDMATERIAL_BOOKMARK_ADDEDMATERIAL_BOOKMARK_BORDERMATERIAL_BOOKMARK_OUTLINEMATERIAL_BOOKMARK_REMOVEMATERIAL_BOOK_ONLINEMATERIAL_BORDER_ALLMATERIAL_BORDER_BOTTOMMATERIAL_BORDER_CLEARMATERIAL_BORDER_COLORMATERIAL_BORDER_HORIZONTALMATERIAL_BORDER_INNERMATERIAL_BORDER_LEFTMATERIAL_BORDER_OUTERMATERIAL_BORDER_RIGHTMATERIAL_BORDER_STYLEMATERIAL_BORDER_TOPMATERIAL_BORDER_VERTICALMATERIAL_BOYMATERIAL_BRANDING_WATERMARKMATERIAL_BREAKFAST_DININGMATERIAL_BRIGHTNESS_1MATERIAL_BRIGHTNESS_2MATERIAL_BRIGHTNESS_3MATERIAL_BRIGHTNESS_4MATERIAL_BRIGHTNESS_5MATERIAL_BRIGHTNESS_6MATERIAL_BRIGHTNESS_7MATERIAL_BRIGHTNESS_AUTOMATERIAL_BRIGHTNESS_HIGHMATERIAL_BRIGHTNESS_LOWMATERIAL_BRIGHTNESS_MEDIUMMATERIAL_BROADCAST_ON_HOMEMATERIAL_BROADCAST_ON_PERSONALMATERIAL_BROKEN_IMAGEMATERIAL_BROWSER_NOT_SUPPORTEDMATERIAL_BROWSER_UPDATEDMATERIAL_BROWSE_GALLERYMATERIAL_BRUNCH_DININGMATERIAL_BRUSHMATERIAL_BUBBLE_CHARTMATERIAL_BUG_REPORTMATERIAL_BUILDMATERIAL_BUILD_CIRCLEMATERIAL_BUNGALOWMATERIAL_BURST_MODEMATERIAL_BUSINESSMATERIAL_BUSINESS_CENTERMATERIAL_BUS_ALERTMATERIAL_CABINMATERIAL_CABLEMATERIAL_CACHEDMATERIAL_CAKEMATERIAL_CALCULATEMATERIAL_CALENDAR_MONTHMATERIAL_CALENDAR_TODAYMATERIAL_CALENDAR_VIEW_DAYMATERIAL_CALENDAR_VIEW_MONTHMATERIAL_CALENDAR_VIEW_WEEKMATERIAL_CALLMATERIAL_CALL_ENDMATERIAL_CALL_MADEMATERIAL_CALL_MERGEMATERIAL_CALL_MISSEDMATERIAL_CALL_MISSED_OUTGOINGMATERIAL_CALL_RECEIVEDMATERIAL_CALL_SPLITMATERIAL_CALL_TO_ACTIONMATERIAL_CAMERAMATERIAL_CAMERASWITCHMATERIAL_CAMERA_ALTMATERIAL_CAMERA_ENHANCEMATERIAL_CAMERA_FRONTMATERIAL_CAMERA_INDOORMATERIAL_CAMERA_OUTDOORMATERIAL_CAMERA_REARMATERIAL_CAMERA_ROLLMATERIAL_CAMPAIGNMATERIAL_CANCELMATERIAL_CANCEL_PRESENTATIONMATERIAL_CANCEL_SCHEDULE_SENDMATERIAL_CANDLESTICK_CHARTMATERIAL_CARD_GIFTCARDMATERIAL_CARD_MEMBERSHIPMATERIAL_CARD_TRAVELMATERIAL_CARPENTERMATERIAL_CAR_CRASHMATERIAL_CAR_RENTALMATERIAL_CAR_REPAIRMATERIAL_CASESMATERIAL_CASINOMATERIAL_CASTMATERIAL_CASTLEMATERIAL_CAST_CONNECTEDMATERIAL_CAST_FOR_EDUCATIONMATERIAL_CATCHING_POKEMONMATERIAL_CATEGORYMATERIAL_CELEBRATIONMATERIAL_CELL_TOWERMATERIAL_CELL_WIFIMATERIAL_CENTER_FOCUS_STRONGMATERIAL_CENTER_FOCUS_WEAKMATERIAL_CHAIRMATERIAL_CHAIR_ALTMATERIAL_CHALETMATERIAL_CHANGE_CIRCLEMATERIAL_CHANGE_HISTORYMATERIAL_CHARGING_STATIONMATERIAL_CHATMATERIAL_CHAT_BUBBLEMATERIAL_CHAT_BUBBLE_OUTLINEMATERIAL_CHECKMATERIAL_CHECKLISTMATERIAL_CHECKLIST_RTLMATERIAL_CHECKROOMMATERIAL_CHECK_BOXMATERIAL_CHECK_BOX_OUTLINE_BLANKMATERIAL_CHECK_CIRCLEMATERIAL_CHECK_CIRCLE_OUTLINEMATERIAL_CHEVRON_LEFTMATERIAL_CHEVRON_RIGHTMATERIAL_CHILD_CAREMATERIAL_CHILD_FRIENDLYMATERIAL_CHROME_READER_MODEMATERIAL_CHURCHMATERIAL_CIRCLEMATERIAL_CIRCLE_NOTIFICATIONSMATERIAL_CLASSMATERIAL_CLEANING_SERVICESMATERIAL_CLEAN_HANDSMATERIAL_CLEARMATERIAL_CLEAR_ALLMATERIAL_CLOSEMATERIAL_CLOSED_CAPTIONMATERIAL_CLOSED_CAPTION_DISABLEDMATERIAL_CLOSED_CAPTION_OFFMATERIAL_CLOSE_FULLSCREENMATERIAL_CLOUDMATERIAL_CLOUDY_SNOWINGMATERIAL_CLOUD_CIRCLEMATERIAL_CLOUD_DONEMATERIAL_CLOUD_DOWNLOADMATERIAL_CLOUD_OFFMATERIAL_CLOUD_QUEUEMATERIAL_CLOUD_SYNCMATERIAL_CLOUD_UPLOADMATERIAL_CO2MATERIAL_CODEMATERIAL_CODE_OFFMATERIAL_COFFEEMATERIAL_COFFEE_MAKERMATERIAL_COLLECTIONSMATERIAL_COLLECTIONS_BOOKMARKMATERIAL_COLORIZEMATERIAL_COLOR_LENSMATERIAL_COMMENTMATERIAL_COMMENTS_DISABLEDMATERIAL_COMMENT_BANKMATERIAL_COMMITMATERIAL_COMMUTEMATERIAL_COMPAREMATERIAL_COMPARE_ARROWSMATERIAL_COMPASS_CALIBRATIONMATERIAL_COMPOSTMATERIAL_COMPRESSMATERIAL_COMPUTERMATERIAL_CONFIRMATION_NUMMATERIAL_CONFIRMATION_NUMBERMATERIAL_CONNECTED_TVMATERIAL_CONNECTING_AIRPORTSMATERIAL_CONNECT_WITHOUT_CONTACTMATERIAL_CONSTRUCTIONMATERIAL_CONTACTLESSMATERIAL_CONTACTSMATERIAL_CONTACT_EMERGENCYMATERIAL_CONTACT_MAILMATERIAL_CONTACT_PAGEMATERIAL_CONTACT_PHONEMATERIAL_CONTACT_SUPPORTMATERIAL_CONTENT_COPYMATERIAL_CONTENT_CUTMATERIAL_CONTENT_PASTEMATERIAL_CONTENT_PASTE_GOMATERIAL_CONTENT_PASTE_OFFMATERIAL_CONTENT_PASTE_SEARCHMATERIAL_CONTRASTMATERIAL_CONTROL_CAMERAMATERIAL_CONTROL_POINTMATERIAL_CONTROL_POINT_DUPLICATEMATERIAL_CONVEYOR_BELTMATERIAL_COOKIEMATERIAL_COPYRIGHTMATERIAL_COPY_ALLMATERIAL_CORONAVIRUSMATERIAL_CORPORATE_FAREMATERIAL_COTTAGEMATERIAL_COUNTERTOPSMATERIAL_CO_PRESENTMATERIAL_CREATEMATERIAL_CREATE_NEW_FOLDERMATERIAL_CREDIT_CARDMATERIAL_CREDIT_CARD_OFFMATERIAL_CREDIT_SCOREMATERIAL_CRIBMATERIAL_CRISIS_ALERTMATERIAL_CROPMATERIAL_CROP_16_9MATERIAL_CROP_3_2MATERIAL_CROP_5_4MATERIAL_CROP_7_5MATERIAL_CROP_DINMATERIAL_CROP_FREEMATERIAL_CROP_LANDSCAPEMATERIAL_CROP_ORIGINALMATERIAL_CROP_PORTRAITMATERIAL_CROP_ROTATEMATERIAL_CROP_SQUAREMATERIAL_CRUELTY_FREEMATERIAL_CSSMATERIAL_CURRENCY_BITCOINMATERIAL_CURRENCY_EXCHANGEMATERIAL_CURRENCY_FRANCMATERIAL_CURRENCY_LIRAMATERIAL_CURRENCY_POUNDMATERIAL_CURRENCY_RUBLEMATERIAL_CURRENCY_RUPEEMATERIAL_CURRENCY_YENMATERIAL_CURRENCY_YUANMATERIAL_CURTAINSMATERIAL_CURTAINS_CLOSEDMATERIAL_CYCLONEMATERIAL_DANGEROUSMATERIAL_DARK_MODEMATERIAL_DASHBOARDMATERIAL_DASHBOARD_CUSTOMIZEMATERIAL_DATASETMATERIAL_DATASET_LINKEDMATERIAL_DATA_ARRAYMATERIAL_DATA_EXPLORATIONMATERIAL_DATA_OBJECTMATERIAL_DATA_SAVER_OFFMATERIAL_DATA_SAVER_ONMATERIAL_DATA_THRESHOLDINGMATERIAL_DATA_USAGEMATERIAL_DATE_RANGEMATERIAL_DEBLURMATERIAL_DECKMATERIAL_DEHAZEMATERIAL_DELETEMATERIAL_DELETE_FOREVERMATERIAL_DELETE_OUTLINEMATERIAL_DELETE_SWEEPMATERIAL_DELIVERY_DININGMATERIAL_DENSITY_LARGEMATERIAL_DENSITY_MEDIUMMATERIAL_DENSITY_SMALLMATERIAL_DEPARTURE_BOARDMATERIAL_DESCRIPTIONMATERIAL_DESELECTMATERIAL_DESIGN_SERVICESMATERIAL_DESKMATERIAL_DESKTOP_ACCESS_DISABLEDMATERIAL_DESKTOP_MACMATERIAL_DESKTOP_WINDOWSMATERIAL_DETAILSMATERIAL_DEVELOPER_BOARDMATERIAL_DEVELOPER_BOARD_OFFMATERIAL_DEVELOPER_MODEMATERIAL_DEVICESMATERIAL_DEVICES_FOLDMATERIAL_DEVICES_OTHERMATERIAL_DEVICE_HUBMATERIAL_DEVICE_THERMOSTATMATERIAL_DEVICE_UNKNOWNMATERIAL_DEW_POINTMATERIAL_DIALER_SIPMATERIAL_DIALPADMATERIAL_DIAMONDMATERIAL_DIFFERENCEMATERIAL_DININGMATERIAL_DINNER_DININGMATERIAL_DIRECTIONSMATERIAL_DIRECTIONS_BIKEMATERIAL_DIRECTIONS_BOATMATERIAL_DIRECTIONS_BOAT_FILLEDMATERIAL_DIRECTIONS_BUSMATERIAL_DIRECTIONS_BUS_FILLEDMATERIAL_DIRECTIONS_CARMATERIAL_DIRECTIONS_CAR_FILLEDMATERIAL_DIRECTIONS_FERRYMATERIAL_DIRECTIONS_OFFMATERIAL_DIRECTIONS_RAILWAYMATERIAL_DIRECTIONS_RAILWAY_FILLEDMATERIAL_DIRECTIONS_RUNMATERIAL_DIRECTIONS_SUBWAYMATERIAL_DIRECTIONS_SUBWAY_FILLEDMATERIAL_DIRECTIONS_TRAINMATERIAL_DIRECTIONS_TRANSITMATERIAL_DIRECTIONS_TRANSIT_FILLEDMATERIAL_DIRECTIONS_WALKMATERIAL_DIRTY_LENSMATERIAL_DISABLED_BY_DEFAULTMATERIAL_DISABLED_VISIBLEMATERIAL_DISCORDMATERIAL_DISCOUNTMATERIAL_DISC_FULLMATERIAL_DISPLAY_SETTINGSMATERIAL_DIVERSITY_1MATERIAL_DIVERSITY_2MATERIAL_DIVERSITY_3MATERIAL_DND_FORWARDSLASHMATERIAL_DNSMATERIAL_DOCKMATERIAL_DOCUMENT_SCANNERMATERIAL_DOMAINMATERIAL_DOMAIN_ADDMATERIAL_DOMAIN_DISABLEDMATERIAL_DOMAIN_VERIFICATIONMATERIAL_DONEMATERIAL_DONE_ALLMATERIAL_DONE_OUTLINEMATERIAL_DONUT_LARGEMATERIAL_DONUT_SMALLMATERIAL_DOORBELLMATERIAL_DOOR_BACKMATERIAL_DOOR_FRONTMATERIAL_DOOR_SLIDINGMATERIAL_DOUBLE_ARROWMATERIAL_DOWNHILL_SKIINGMATERIAL_DOWNLOADMATERIAL_DOWNLOADINGMATERIAL_DOWNLOAD_DONEMATERIAL_DOWNLOAD_FOR_OFFLINEMATERIAL_DO_DISTURBMATERIAL_DO_DISTURB_ALTMATERIAL_DO_DISTURB_OFFMATERIAL_DO_DISTURB_ONMATERIAL_DO_NOT_DISTURBMATERIAL_DO_NOT_DISTURB_ALTMATERIAL_DO_NOT_DISTURB_OFFMATERIAL_DO_NOT_DISTURB_ONMATERIAL_DO_NOT_DISTURB_ON_TOTAL_SILENCEMATERIAL_DO_NOT_STEPMATERIAL_DO_NOT_TOUCHMATERIAL_DRAFTSMATERIAL_DRAG_HANDLEMATERIAL_DRAG_INDICATORMATERIAL_DRAWMATERIAL_DRIVE_ETAMATERIAL_DRIVE_FILE_MOVEMATERIAL_DRIVE_FILE_MOVE_OUTLINEMATERIAL_DRIVE_FILE_MOVE_RTLMATERIAL_DRIVE_FILE_RENAME_OUTLINEMATERIAL_DRIVE_FOLDER_UPLOADMATERIAL_DRYMATERIAL_DRY_CLEANINGMATERIAL_DUOMATERIAL_DVRMATERIAL_DYNAMIC_FEEDMATERIAL_DYNAMIC_FORMMATERIAL_EARBUDSMATERIAL_EARBUDS_BATTERYMATERIAL_EASTMATERIAL_ECOMATERIAL_EDGESENSOR_HIGHMATERIAL_EDGESENSOR_LOWMATERIAL_EDITMATERIAL_EDIT_ATTRIBUTESMATERIAL_EDIT_CALENDARMATERIAL_EDIT_DOCUMENTMATERIAL_EDIT_LOCATIONMATERIAL_EDIT_LOCATION_ALTMATERIAL_EDIT_NOTEMATERIAL_EDIT_NOTIFICATIONSMATERIAL_EDIT_OFFMATERIAL_EDIT_ROADMATERIAL_EDIT_SQUAREMATERIAL_EGGMATERIAL_EGG_ALTMATERIAL_EJECTMATERIAL_ELDERLYMATERIAL_ELDERLY_WOMANMATERIAL_ELECTRICAL_SERVICESMATERIAL_ELECTRIC_BIKEMATERIAL_ELECTRIC_BOLTMATERIAL_ELECTRIC_CARMATERIAL_ELECTRIC_METERMATERIAL_ELECTRIC_MOPEDMATERIAL_ELECTRIC_RICKSHAWMATERIAL_ELECTRIC_SCOOTERMATERIAL_ELEVATORMATERIAL_EMAILMATERIAL_EMERGENCYMATERIAL_EMERGENCY_RECORDINGMATERIAL_EMERGENCY_SHAREMATERIAL_EMOJI_EMOTIONSMATERIAL_EMOJI_EVENTSMATERIAL_EMOJI_FLAGSMATERIAL_EMOJI_FOOD_BEVERAGEMATERIAL_EMOJI_NATUREMATERIAL_EMOJI_OBJECTSMATERIAL_EMOJI_PEOPLEMATERIAL_EMOJI_SYMBOLSMATERIAL_EMOJI_TRANSPORTATIONMATERIAL_ENERGY_SAVINGS_LEAFMATERIAL_ENGINEERINGMATERIAL_ENHANCED_ENCRYPTIONMATERIAL_ENHANCE_PHOTO_TRANSLATEMATERIAL_EQUALIZERMATERIAL_ERRORMATERIAL_ERROR_OUTLINEMATERIAL_ESCALATORMATERIAL_ESCALATOR_WARNINGMATERIAL_EUROMATERIAL_EURO_SYMBOLMATERIAL_EVENTMATERIAL_EVENT_AVAILABLEMATERIAL_EVENT_BUSYMATERIAL_EVENT_NOTEMATERIAL_EVENT_REPEATMATERIAL_EVENT_SEATMATERIAL_EV_STATIONMATERIAL_EXIT_TO_APPMATERIAL_EXPANDMATERIAL_EXPAND_CIRCLE_DOWNMATERIAL_EXPAND_LESSMATERIAL_EXPAND_MOREMATERIAL_EXPLICITMATERIAL_EXPLOREMATERIAL_EXPLORE_OFFMATERIAL_EXPOSUREMATERIAL_EXPOSURE_MINUS_1MATERIAL_EXPOSURE_MINUS_2MATERIAL_EXPOSURE_NEG_1MATERIAL_EXPOSURE_NEG_2MATERIAL_EXPOSURE_PLUS_1MATERIAL_EXPOSURE_PLUS_2MATERIAL_EXPOSURE_ZEROMATERIAL_EXTENSIONMATERIAL_EXTENSION_OFFMATERIAL_E_MOBILEDATAMATERIAL_FACEMATERIAL_FACEBOOKMATERIAL_FACE_2MATERIAL_FACE_3MATERIAL_FACE_4MATERIAL_FACE_5MATERIAL_FACE_6MATERIAL_FACE_RETOUCHING_NATURALMATERIAL_FACE_RETOUCHING_OFFMATERIAL_FACTORYMATERIAL_FACT_CHECKMATERIAL_FAMILY_RESTROOMMATERIAL_FASTFOODMATERIAL_FAST_FORWARDMATERIAL_FAST_REWINDMATERIAL_FAVORITEMATERIAL_FAVORITE_BORDERMATERIAL_FAVORITE_OUTLINEMATERIAL_FAXMATERIAL_FEATURED_PLAY_LISTMATERIAL_FEATURED_VIDEOMATERIAL_FEEDMATERIAL_FEEDBACKMATERIAL_FEMALEMATERIAL_FENCEMATERIAL_FESTIVALMATERIAL_FIBER_DVRMATERIAL_FIBER_MANUAL_RECORDMATERIAL_FIBER_NEWMATERIAL_FIBER_PINMATERIAL_FIBER_SMART_RECORDMATERIAL_FILE_COPYMATERIAL_FILE_DOWNLOADMATERIAL_FILE_DOWNLOAD_DONEMATERIAL_FILE_DOWNLOAD_OFFMATERIAL_FILE_OPENMATERIAL_FILE_PRESENTMATERIAL_FILE_UPLOADMATERIAL_FILE_UPLOAD_OFFMATERIAL_FILTERMATERIAL_FILTER_1MATERIAL_FILTER_2MATERIAL_FILTER_3MATERIAL_FILTER_4MATERIAL_FILTER_5MATERIAL_FILTER_6MATERIAL_FILTER_7MATERIAL_FILTER_8MATERIAL_FILTER_9MATERIAL_FILTER_9_PLUSMATERIAL_FILTER_ALTMATERIAL_FILTER_ALT_OFFMATERIAL_FILTER_B_AND_WMATERIAL_FILTER_CENTER_FOCUSMATERIAL_FILTER_DRAMAMATERIAL_FILTER_FRAMESMATERIAL_FILTER_HDRMATERIAL_FILTER_LISTMATERIAL_FILTER_LIST_ALTMATERIAL_FILTER_LIST_OFFMATERIAL_FILTER_NONEMATERIAL_FILTER_TILT_SHIFTMATERIAL_FILTER_VINTAGEMATERIAL_FIND_IN_PAGEMATERIAL_FIND_REPLACEMATERIAL_FINGERPRINTMATERIAL_FIREPLACEMATERIAL_FIRE_EXTINGUISHERMATERIAL_FIRE_HYDRANTMATERIAL_FIRE_HYDRANT_ALTMATERIAL_FIRE_TRUCKMATERIAL_FIRST_PAGEMATERIAL_FITBITMATERIAL_FITNESS_CENTERMATERIAL_FIT_SCREENMATERIAL_FLAGMATERIAL_FLAG_CIRCLEMATERIAL_FLAKYMATERIAL_FLAREMATERIAL_FLASHLIGHT_OFFMATERIAL_FLASHLIGHT_ONMATERIAL_FLASH_AUTOMATERIAL_FLASH_OFFMATERIAL_FLASH_ONMATERIAL_FLATWAREMATERIAL_FLIGHTMATERIAL_FLIGHT_CLASSMATERIAL_FLIGHT_LANDMATERIAL_FLIGHT_TAKEOFFMATERIAL_FLIPMATERIAL_FLIP_CAMERA_ANDROIDMATERIAL_FLIP_CAMERA_IOSMATERIAL_FLIP_TO_BACKMATERIAL_FLIP_TO_FRONTMATERIAL_FLOODMATERIAL_FLOURESCENTMATERIAL_FLUORESCENTMATERIAL_FLUTTER_DASHMATERIAL_FMD_BADMATERIAL_FMD_GOODMATERIAL_FOGGYMATERIAL_FOLDERMATERIAL_FOLDER_COPYMATERIAL_FOLDER_DELETEMATERIAL_FOLDER_OFFMATERIAL_FOLDER_OPENMATERIAL_FOLDER_SHAREDMATERIAL_FOLDER_SPECIALMATERIAL_FOLDER_ZIPMATERIAL_FOLLOW_THE_SIGNSMATERIAL_FONT_DOWNLOADMATERIAL_FONT_DOWNLOAD_OFFMATERIAL_FOOD_BANKMATERIAL_FORESTMATERIAL_FORKLIFTMATERIAL_FORK_LEFTMATERIAL_FORK_RIGHTMATERIAL_FORMAT_ALIGN_CENTERMATERIAL_FORMAT_ALIGN_JUSTIFYMATERIAL_FORMAT_ALIGN_LEFTMATERIAL_FORMAT_ALIGN_RIGHTMATERIAL_FORMAT_BOLDMATERIAL_FORMAT_CLEARMATERIAL_FORMAT_COLOR_FILLMATERIAL_FORMAT_COLOR_RESETMATERIAL_FORMAT_COLOR_TEXTMATERIAL_FORMAT_INDENT_DECREASEMATERIAL_FORMAT_INDENT_INCREASEMATERIAL_FORMAT_ITALICMATERIAL_FORMAT_LINE_SPACINGMATERIAL_FORMAT_LIST_BULLETEDMATERIAL_FORMAT_LIST_BULLETED_ADDMATERIAL_FORMAT_LIST_NUMBEREDMATERIAL_FORMAT_LIST_NUMBERED_RTLMATERIAL_FORMAT_OVERLINEMATERIAL_FORMAT_PAINTMATERIAL_FORMAT_QUOTEMATERIAL_FORMAT_SHAPESMATERIAL_FORMAT_SIZEMATERIAL_FORMAT_STRIKETHROUGHMATERIAL_FORMAT_TEXTDIRECTION_L_TO_RMATERIAL_FORMAT_TEXTDIRECTION_R_TO_LMATERIAL_FORMAT_UNDERLINEMATERIAL_FORMAT_UNDERLINEDMATERIAL_FORTMATERIAL_FORUMMATERIAL_FORWARDMATERIAL_FORWARD_10MATERIAL_FORWARD_30MATERIAL_FORWARD_5MATERIAL_FORWARD_TO_INBOXMATERIAL_FOUNDATIONMATERIAL_FREE_BREAKFASTMATERIAL_FREE_CANCELLATIONMATERIAL_FRONT_HANDMATERIAL_FRONT_LOADERMATERIAL_FULLSCREENMATERIAL_FULLSCREEN_EXITMATERIAL_FUNCTIONSMATERIAL_GAMEPADMATERIAL_GAMESMATERIAL_GARAGEMATERIAL_GAS_METERMATERIAL_GAVELMATERIAL_GENERATING_TOKENSMATERIAL_GESTUREMATERIAL_GET_APPMATERIAL_GIFMATERIAL_GIF_BOXMATERIAL_GIRLMATERIAL_GITEMATERIAL_GOLF_COURSEMATERIAL_GPP_BADMATERIAL_GPP_GOODMATERIAL_GPP_MAYBEMATERIAL_GPS_FIXEDMATERIAL_GPS_NOT_FIXEDMATERIAL_GPS_OFFMATERIAL_GRADEMATERIAL_GRADIENTMATERIAL_GRADINGMATERIAL_GRAINMATERIAL_GRAPHIC_EQMATERIAL_GRASSMATERIAL_GRID_3X3MATERIAL_GRID_4X4MATERIAL_GRID_GOLDENRATIOMATERIAL_GRID_OFFMATERIAL_GRID_ONMATERIAL_GRID_VIEWMATERIAL_GROUPMATERIAL_GROUPSMATERIAL_GROUPS_2MATERIAL_GROUPS_3MATERIAL_GROUP_ADDMATERIAL_GROUP_OFFMATERIAL_GROUP_REMOVEMATERIAL_GROUP_WORKMATERIAL_G_MOBILEDATAMATERIAL_G_TRANSLATEMATERIAL_HAILMATERIAL_HANDSHAKEMATERIAL_HANDYMANMATERIAL_HARDWAREMATERIAL_HDMATERIAL_HDR_AUTOMATERIAL_HDR_AUTO_SELECTMATERIAL_HDR_ENHANCED_SELECTMATERIAL_HDR_OFFMATERIAL_HDR_OFF_SELECTMATERIAL_HDR_ONMATERIAL_HDR_ON_SELECTMATERIAL_HDR_PLUSMATERIAL_HDR_STRONGMATERIAL_HDR_WEAKMATERIAL_HEADPHONESMATERIAL_HEADPHONES_BATTERYMATERIAL_HEADSETMATERIAL_HEADSET_MICMATERIAL_HEADSET_OFFMATERIAL_HEALINGMATERIAL_HEALTH_AND_SAFETYMATERIAL_HEARINGMATERIAL_HEARING_DISABLEDMATERIAL_HEART_BROKENMATERIAL_HEAT_PUMPMATERIAL_HEIGHTMATERIAL_HELPMATERIAL_HELP_CENTERMATERIAL_HELP_OUTLINEMATERIAL_HEVCMATERIAL_HEXAGONMATERIAL_HIDE_IMAGEMATERIAL_HIDE_SOURCEMATERIAL_HIGHLIGHTMATERIAL_HIGHLIGHT_ALTMATERIAL_HIGHLIGHT_OFFMATERIAL_HIGHLIGHT_REMOVEMATERIAL_HIGH_QUALITYMATERIAL_HIKINGMATERIAL_HISTORYMATERIAL_HISTORY_EDUMATERIAL_HISTORY_TOGGLE_OFFMATERIAL_HIVEMATERIAL_HLSMATERIAL_HLS_OFFMATERIAL_HOLIDAY_VILLAGEMATERIAL_HOMEMATERIAL_HOME_FILLEDMATERIAL_HOME_MAXMATERIAL_HOME_MINIMATERIAL_HOME_REPAIR_SERVICEMATERIAL_HOME_WORKMATERIAL_HORIZONTAL_DISTRIBUTEMATERIAL_HORIZONTAL_RULEMATERIAL_HORIZONTAL_SPLITMATERIAL_HOTELMATERIAL_HOTEL_CLASSMATERIAL_HOT_TUBMATERIAL_HOURGLASS_BOTTOMMATERIAL_HOURGLASS_DISABLEDMATERIAL_HOURGLASS_EMPTYMATERIAL_HOURGLASS_FULLMATERIAL_HOURGLASS_TOPMATERIAL_HOUSEMATERIAL_HOUSEBOATMATERIAL_HOUSE_SIDINGMATERIAL_HOW_TO_REGMATERIAL_HOW_TO_VOTEMATERIAL_HTMLMATERIAL_HTTPMATERIAL_HTTPSMATERIAL_HUBMATERIAL_HVACMATERIAL_H_MOBILEDATAMATERIAL_H_PLUS_MOBILEDATAMATERIAL_ICECREAMMATERIAL_ICE_SKATINGMATERIAL_IMAGEMATERIAL_IMAGESEARCH_ROLLERMATERIAL_IMAGE_ASPECT_RATIOMATERIAL_IMAGE_NOT_SUPPORTEDMATERIAL_IMAGE_SEARCHMATERIAL_IMPORTANT_DEVICESMATERIAL_IMPORT_CONTACTSMATERIAL_IMPORT_EXPORTMATERIAL_INBOXMATERIAL_INCOMPLETE_CIRCLEMATERIAL_INDETERMINATE_CHECK_BOXMATERIAL_INFOMATERIAL_INFO_OUTLINEMATERIAL_INPUTMATERIAL_INSERT_CHARTMATERIAL_INSERT_CHART_OUTLINEDMATERIAL_INSERT_COMMENTMATERIAL_INSERT_DRIVE_FILEMATERIAL_INSERT_EMOTICONMATERIAL_INSERT_INVITATIONMATERIAL_INSERT_LINKMATERIAL_INSERT_PAGE_BREAKMATERIAL_INSERT_PHOTOMATERIAL_INSIGHTSMATERIAL_INSTALL_DESKTOPMATERIAL_INSTALL_MOBILEMATERIAL_INTEGRATION_INSTRUCTIONSMATERIAL_INTERESTSMATERIAL_INTERPRETER_MODEMATERIAL_INVENTORYMATERIAL_INVENTORY_2MATERIAL_INVERT_COLORSMATERIAL_INVERT_COLORS_OFFMATERIAL_INVERT_COLORS_ONMATERIAL_IOS_SHAREMATERIAL_IRONMATERIAL_ISOMATERIAL_JAVASCRIPTMATERIAL_JOIN_FULLMATERIAL_JOIN_INNERMATERIAL_JOIN_LEFTMATERIAL_JOIN_RIGHTMATERIAL_KAYAKINGMATERIAL_KEBAB_DININGMATERIAL_KEYMATERIAL_KEYBOARDMATERIAL_KEYBOARD_ALTMATERIAL_KEYBOARD_ARROW_DOWNMATERIAL_KEYBOARD_ARROW_LEFTMATERIAL_KEYBOARD_ARROW_RIGHTMATERIAL_KEYBOARD_ARROW_UPMATERIAL_KEYBOARD_BACKSPACEMATERIAL_KEYBOARD_CAPSLOCKMATERIAL_KEYBOARD_COMMANDMATERIAL_KEYBOARD_COMMAND_KEYMATERIAL_KEYBOARD_CONTROLMATERIAL_KEYBOARD_CONTROL_KEYMATERIAL_KEYBOARD_DOUBLE_ARROW_DOWNMATERIAL_KEYBOARD_DOUBLE_ARROW_LEFTMATERIAL_KEYBOARD_DOUBLE_ARROW_RIGHTMATERIAL_KEYBOARD_DOUBLE_ARROW_UPMATERIAL_KEYBOARD_HIDEMATERIAL_KEYBOARD_OPTIONMATERIAL_KEYBOARD_OPTION_KEYMATERIAL_KEYBOARD_RETURNMATERIAL_KEYBOARD_TABMATERIAL_KEYBOARD_VOICEMATERIAL_KEY_OFFMATERIAL_KING_BEDMATERIAL_KITCHENMATERIAL_KITESURFINGMATERIAL_LABELMATERIAL_LABEL_IMPORTANTMATERIAL_LABEL_IMPORTANT_OUTLINEMATERIAL_LABEL_OFFMATERIAL_LABEL_OUTLINEMATERIAL_LANMATERIAL_LANDSCAPEMATERIAL_LANDSLIDEMATERIAL_LANGUAGEMATERIAL_LAPTOPMATERIAL_LAPTOP_CHROMEBOOKMATERIAL_LAPTOP_MACMATERIAL_LAPTOP_WINDOWSMATERIAL_LAST_PAGEMATERIAL_LAUNCHMATERIAL_LAYERSMATERIAL_LAYERS_CLEARMATERIAL_LEADERBOARDMATERIAL_LEAK_ADDMATERIAL_LEAK_REMOVEMATERIAL_LEAVE_BAGS_AT_HOMEMATERIAL_LEGEND_TOGGLEMATERIAL_LENSMATERIAL_LENS_BLURMATERIAL_LIBRARY_ADDMATERIAL_LIBRARY_ADD_CHECKMATERIAL_LIBRARY_BOOKSMATERIAL_LIBRARY_MUSICMATERIAL_LIGHTMATERIAL_LIGHTBULBMATERIAL_LIGHTBULB_CIRCLEMATERIAL_LIGHTBULB_OUTLINEMATERIAL_LIGHT_MODEMATERIAL_LINEAR_SCALEMATERIAL_LINE_AXISMATERIAL_LINE_STYLEMATERIAL_LINE_WEIGHTMATERIAL_LINKMATERIAL_LINKED_CAMERAMATERIAL_LINK_OFFMATERIAL_LIQUORMATERIAL_LISTMATERIAL_LIST_ALTMATERIAL_LIVE_HELPMATERIAL_LIVE_TVMATERIAL_LIVINGMATERIAL_LOCAL_ACTIVITYMATERIAL_LOCAL_AIRPORTMATERIAL_LOCAL_ATMMATERIAL_LOCAL_ATTRACTIONMATERIAL_LOCAL_BARMATERIAL_LOCAL_CAFEMATERIAL_LOCAL_CAR_WASHMATERIAL_LOCAL_CONVENIENCE_STOREMATERIAL_LOCAL_DININGMATERIAL_LOCAL_DRINKMATERIAL_LOCAL_FIRE_DEPARTMENTMATERIAL_LOCAL_FLORISTMATERIAL_LOCAL_GAS_STATIONMATERIAL_LOCAL_GROCERY_STOREMATERIAL_LOCAL_HOSPITALMATERIAL_LOCAL_HOTELMATERIAL_LOCAL_LAUNDRY_SERVICEMATERIAL_LOCAL_LIBRARYMATERIAL_LOCAL_MALLMATERIAL_LOCAL_MOVIESMATERIAL_LOCAL_OFFERMATERIAL_LOCAL_PARKINGMATERIAL_LOCAL_PHARMACYMATERIAL_LOCAL_PHONEMATERIAL_LOCAL_PIZZAMATERIAL_LOCAL_PLAYMATERIAL_LOCAL_POLICEMATERIAL_LOCAL_POST_OFFICEMATERIAL_LOCAL_PRINTSHOPMATERIAL_LOCAL_PRINT_SHOPMATERIAL_LOCAL_RESTAURANTMATERIAL_LOCAL_SEEMATERIAL_LOCAL_SHIPPINGMATERIAL_LOCAL_TAXIMATERIAL_LOCATION_CITYMATERIAL_LOCATION_DISABLEDMATERIAL_LOCATION_HISTORYMATERIAL_LOCATION_OFFMATERIAL_LOCATION_ONMATERIAL_LOCATION_PINMATERIAL_LOCATION_SEARCHINGMATERIAL_LOCKMATERIAL_LOCK_CLOCKMATERIAL_LOCK_OPENMATERIAL_LOCK_OUTLINEMATERIAL_LOCK_PERSONMATERIAL_LOCK_RESETMATERIAL_LOGINMATERIAL_LOGOUTMATERIAL_LOGO_DEVMATERIAL_LOOKSMATERIAL_LOOKS_3MATERIAL_LOOKS_4MATERIAL_LOOKS_5MATERIAL_LOOKS_6MATERIAL_LOOKS_ONEMATERIAL_LOOKS_TWOMATERIAL_LOOPMATERIAL_LOUPEMATERIAL_LOW_PRIORITYMATERIAL_LOYALTYMATERIAL_LTE_MOBILEDATAMATERIAL_LTE_PLUS_MOBILEDATAMATERIAL_LUGGAGEMATERIAL_LUNCH_DININGMATERIAL_LYRICSMATERIAL_MACRO_OFFMATERIAL_MAILMATERIAL_MAIL_LOCKMATERIAL_MAIL_OUTLINEMATERIAL_MALEMATERIAL_MANMATERIAL_MANAGE_ACCOUNTSMATERIAL_MANAGE_HISTORYMATERIAL_MANAGE_SEARCHMATERIAL_MAN_2MATERIAL_MAN_3MATERIAL_MAN_4MATERIAL_MAPMATERIAL_MAPS_HOME_WORKMATERIAL_MAPS_UGCMATERIAL_MARGINMATERIAL_MARKUNREADMATERIAL_MARKUNREAD_MAILBOXMATERIAL_MARK_AS_UNREADMATERIAL_MARK_CHAT_READMATERIAL_MARK_CHAT_UNREADMATERIAL_MARK_EMAIL_READMATERIAL_MARK_EMAIL_UNREADMATERIAL_MARK_UNREAD_CHAT_ALTMATERIAL_MASKSMATERIAL_MAXIMIZEMATERIAL_MEDIATIONMATERIAL_MEDIA_BLUETOOTH_OFFMATERIAL_MEDIA_BLUETOOTH_ONMATERIAL_MEDICAL_INFORMATIONMATERIAL_MEDICAL_SERVICESMATERIAL_MEDICATIONMATERIAL_MEDICATION_LIQUIDMATERIAL_MEETING_ROOMMATERIAL_MEMORYMATERIAL_MENUMATERIAL_MENU_BOOKMATERIAL_MENU_OPENMATERIAL_MERGEMATERIAL_MERGE_TYPEMATERIAL_MESSAGEMATERIAL_MESSENGERMATERIAL_MESSENGER_OUTLINEMATERIAL_MICMATERIAL_MICROWAVEMATERIAL_MIC_EXTERNAL_OFFMATERIAL_MIC_EXTERNAL_ONMATERIAL_MIC_NONEMATERIAL_MIC_OFFMATERIAL_MILITARY_TECHMATERIAL_MINIMIZEMATERIAL_MINOR_CRASHMATERIAL_MISCELLANEOUS_SERVICESMATERIAL_MISSED_VIDEO_CALLMATERIAL_MMSMATERIAL_MOBILEDATA_OFFMATERIAL_MOBILE_FRIENDLYMATERIAL_MOBILE_OFFMATERIAL_MOBILE_SCREEN_SHAREMATERIAL_MODEMATERIAL_MODEL_TRAININGMATERIAL_MODE_COMMENTMATERIAL_MODE_EDITMATERIAL_MODE_EDIT_OUTLINEMATERIAL_MODE_FAN_OFFMATERIAL_MODE_NIGHTMATERIAL_MODE_OF_TRAVELMATERIAL_MODE_STANDBYMATERIAL_MONETIZATION_ONMATERIAL_MONEYMATERIAL_MONEY_OFFMATERIAL_MONEY_OFF_CSREDMATERIAL_MONITORMATERIAL_MONITOR_HEARTMATERIAL_MONITOR_WEIGHTMATERIAL_MONOCHROME_PHOTOSMATERIAL_MOODMATERIAL_MOOD_BADMATERIAL_MOPEDMATERIAL_MOREMATERIAL_MORE_HORIZMATERIAL_MORE_TIMEMATERIAL_MORE_VERTMATERIAL_MOSQUEMATERIAL_MOTION_PHOTOS_AUTOMATERIAL_MOTION_PHOTOS_OFFMATERIAL_MOTION_PHOTOS_ONMATERIAL_MOTION_PHOTOS_PAUSEMATERIAL_MOTION_PHOTOS_PAUSEDMATERIAL_MOTORCYCLEMATERIAL_MOUSEMATERIAL_MOVE_DOWNMATERIAL_MOVE_TO_INBOXMATERIAL_MOVE_UPMATERIAL_MOVIEMATERIAL_MOVIE_CREATIONMATERIAL_MOVIE_EDITMATERIAL_MOVIE_FILTERMATERIAL_MOVINGMATERIAL_MPMATERIAL_MULTILINE_CHARTMATERIAL_MULTIPLE_STOPMATERIAL_MULTITRACK_AUDIOMATERIAL_MUSEUMMATERIAL_MUSIC_NOTEMATERIAL_MUSIC_OFFMATERIAL_MUSIC_VIDEOMATERIAL_MY_LIBRARY_ADDMATERIAL_MY_LIBRARY_BOOKSMATERIAL_MY_LIBRARY_MUSICMATERIAL_MY_LOCATIONMATERIAL_NATMATERIAL_NATUREMATERIAL_NATURE_PEOPLEMATERIAL_NAVIGATE_BEFOREMATERIAL_NAVIGATE_NEXTMATERIAL_NAVIGATIONMATERIAL_NEARBY_ERRORMATERIAL_NEARBY_OFFMATERIAL_NEAR_MEMATERIAL_NEAR_ME_DISABLEDMATERIAL_NEST_CAM_WIRED_STANDMATERIAL_NETWORK_CELLMATERIAL_NETWORK_CHECKMATERIAL_NETWORK_LOCKEDMATERIAL_NETWORK_PINGMATERIAL_NETWORK_WIFIMATERIAL_NETWORK_WIFI_1_BARMATERIAL_NETWORK_WIFI_2_BARMATERIAL_NETWORK_WIFI_3_BARMATERIAL_NEWSPAPERMATERIAL_NEW_LABELMATERIAL_NEW_RELEASESMATERIAL_NEXT_PLANMATERIAL_NEXT_WEEKMATERIAL_NFCMATERIAL_NIGHTLIFEMATERIAL_NIGHTLIGHTMATERIAL_NIGHTLIGHT_ROUNDMATERIAL_NIGHTS_STAYMATERIAL_NIGHT_SHELTERMATERIAL_NOISE_AWAREMATERIAL_NOISE_CONTROL_OFFMATERIAL_NORDIC_WALKINGMATERIAL_NORTHMATERIAL_NORTH_EASTMATERIAL_NORTH_WESTMATERIAL_NOTEMATERIAL_NOTESMATERIAL_NOTE_ADDMATERIAL_NOTE_ALTMATERIAL_NOTIFICATIONSMATERIAL_NOTIFICATIONS_ACTIVEMATERIAL_NOTIFICATIONS_NONEMATERIAL_NOTIFICATIONS_OFFMATERIAL_NOTIFICATIONS_ONMATERIAL_NOTIFICATIONS_PAUSEDMATERIAL_NOTIFICATION_ADDMATERIAL_NOTIFICATION_IMPORTANTMATERIAL_NOT_ACCESSIBLEMATERIAL_NOT_INTERESTEDMATERIAL_NOT_LISTED_LOCATIONMATERIAL_NOT_STARTEDMATERIAL_NOW_WALLPAPERMATERIAL_NOW_WIDGETSMATERIAL_NO_ACCOUNTSMATERIAL_NO_ADULT_CONTENTMATERIAL_NO_BACKPACKMATERIAL_NO_CELLMATERIAL_NO_CRASHMATERIAL_NO_DRINKSMATERIAL_NO_ENCRYPTIONMATERIAL_NO_ENCRYPTION_GMAILERRORREDMATERIAL_NO_FLASHMATERIAL_NO_FOODMATERIAL_NO_LUGGAGEMATERIAL_NO_MEALSMATERIAL_NO_MEALS_OULINEMATERIAL_NO_MEETING_ROOMMATERIAL_NO_PHOTOGRAPHYMATERIAL_NO_SIMMATERIAL_NO_STROLLERMATERIAL_NO_TRANSFERMATERIAL_NUMBERSMATERIAL_OFFLINE_BOLTMATERIAL_OFFLINE_PINMATERIAL_OFFLINE_SHAREMATERIAL_OIL_BARRELMATERIAL_ONDEMAND_VIDEOMATERIAL_ONLINE_PREDICTIONMATERIAL_ON_DEVICE_TRAININGMATERIAL_OPACITYMATERIAL_OPEN_IN_BROWSERMATERIAL_OPEN_IN_FULLMATERIAL_OPEN_IN_NEWMATERIAL_OPEN_IN_NEW_OFFMATERIAL_OPEN_WITHMATERIAL_OTHER_HOUSESMATERIAL_OUTBONDMATERIAL_OUTBOUNDMATERIAL_OUTBOXMATERIAL_OUTDOOR_GRILLMATERIAL_OUTGOING_MAILMATERIAL_OUTLETMATERIAL_OUTLINED_FLAGMATERIAL_OUTPUTMATERIAL_PADDINGMATERIAL_PAGESMATERIAL_PAGEVIEWMATERIAL_PAIDMATERIAL_PALETTEMATERIAL_PALLETMATERIAL_PANORAMAMATERIAL_PANORAMA_FISHEYEMATERIAL_PANORAMA_FISH_EYEMATERIAL_PANORAMA_HORIZONTALMATERIAL_PANORAMA_HORIZONTAL_SELECTMATERIAL_PANORAMA_PHOTOSPHEREMATERIAL_PANORAMA_PHOTOSPHERE_SELECTMATERIAL_PANORAMA_VERTICALMATERIAL_PANORAMA_VERTICAL_SELECTMATERIAL_PANORAMA_WIDE_ANGLEMATERIAL_PANORAMA_WIDE_ANGLE_SELECTMATERIAL_PAN_TOOLMATERIAL_PAN_TOOL_ALTMATERIAL_PARAGLIDINGMATERIAL_PARKMATERIAL_PARTY_MODEMATERIAL_PASSWORDMATERIAL_PATTERNMATERIAL_PAUSEMATERIAL_PAUSE_CIRCLEMATERIAL_PAUSE_CIRCLE_FILLEDMATERIAL_PAUSE_CIRCLE_OUTLINEMATERIAL_PAUSE_PRESENTATIONMATERIAL_PAYMENTMATERIAL_PAYMENTSMATERIAL_PAYPALMATERIAL_PEDAL_BIKEMATERIAL_PENDINGMATERIAL_PENDING_ACTIONSMATERIAL_PENTAGONMATERIAL_PEOPLEMATERIAL_PEOPLE_ALTMATERIAL_PEOPLE_OUTLINEMATERIAL_PERCENTMATERIAL_PERM_CAMERA_MICMATERIAL_PERM_CONTACT_CALMATERIAL_PERM_CONTACT_CALENDARMATERIAL_PERM_DATA_SETTINGMATERIAL_PERM_DEVICE_INFOMATERIAL_PERM_DEVICE_INFORMATIONMATERIAL_PERM_IDENTITYMATERIAL_PERM_MEDIAMATERIAL_PERM_PHONE_MSGMATERIAL_PERM_SCAN_WIFIMATERIAL_PERSONMATERIAL_PERSONAL_INJURYMATERIAL_PERSONAL_VIDEOMATERIAL_PERSON_2MATERIAL_PERSON_3MATERIAL_PERSON_4MATERIAL_PERSON_ADDMATERIAL_PERSON_ADD_ALTMATERIAL_PERSON_ADD_ALT_1MATERIAL_PERSON_ADD_DISABLEDMATERIAL_PERSON_OFFMATERIAL_PERSON_OUTLINEMATERIAL_PERSON_PINMATERIAL_PERSON_PIN_CIRCLEMATERIAL_PERSON_REMOVEMATERIAL_PERSON_REMOVE_ALT_1MATERIAL_PERSON_SEARCHMATERIAL_PEST_CONTROLMATERIAL_PEST_CONTROL_RODENTMATERIAL_PETSMATERIAL_PHISHINGMATERIAL_PHONEMATERIAL_PHONELINKMATERIAL_PHONELINK_ERASEMATERIAL_PHONELINK_LOCKMATERIAL_PHONELINK_OFFMATERIAL_PHONELINK_RINGMATERIAL_PHONELINK_SETUPMATERIAL_PHONE_ANDROIDMATERIAL_PHONE_BLUETOOTH_SPEAKERMATERIAL_PHONE_CALLBACKMATERIAL_PHONE_DISABLEDMATERIAL_PHONE_ENABLEDMATERIAL_PHONE_FORWARDEDMATERIAL_PHONE_IN_TALKMATERIAL_PHONE_IPHONEMATERIAL_PHONE_LOCKEDMATERIAL_PHONE_MISSEDMATERIAL_PHONE_PAUSEDMATERIAL_PHOTOMATERIAL_PHOTO_ALBUMMATERIAL_PHOTO_CAMERAMATERIAL_PHOTO_CAMERA_BACKMATERIAL_PHOTO_CAMERA_FRONTMATERIAL_PHOTO_FILTERMATERIAL_PHOTO_LIBRARYMATERIAL_PHOTO_SIZE_SELECT_ACTUALMATERIAL_PHOTO_SIZE_SELECT_LARGEMATERIAL_PHOTO_SIZE_SELECT_SMALLMATERIAL_PHPMATERIAL_PIANOMATERIAL_PIANO_OFFMATERIAL_PICTURE_AS_PDFMATERIAL_PICTURE_IN_PICTUREMATERIAL_PICTURE_IN_PICTURE_ALTMATERIAL_PIE_CHARTMATERIAL_PIE_CHART_OUTLINEMATERIAL_PIE_CHART_OUTLINEDMATERIAL_PINMATERIAL_PINCHMATERIAL_PIN_DROPMATERIAL_PIN_ENDMATERIAL_PIN_INVOKEMATERIAL_PIVOT_TABLE_CHARTMATERIAL_PIXMATERIAL_PLACEMATERIAL_PLAGIARISMMATERIAL_PLAYLIST_ADDMATERIAL_PLAYLIST_ADD_CHECKMATERIAL_PLAYLIST_ADD_CHECK_CIRCLEMATERIAL_PLAYLIST_ADD_CIRCLEMATERIAL_PLAYLIST_PLAYMATERIAL_PLAYLIST_REMOVEMATERIAL_PLAY_ARROWMATERIAL_PLAY_CIRCLEMATERIAL_PLAY_CIRCLE_FILLMATERIAL_PLAY_CIRCLE_FILLEDMATERIAL_PLAY_CIRCLE_OUTLINEMATERIAL_PLAY_DISABLEDMATERIAL_PLAY_FOR_WORKMATERIAL_PLAY_LESSONMATERIAL_PLUMBINGMATERIAL_PLUS_ONEMATERIAL_PODCASTSMATERIAL_POINT_OF_SALEMATERIAL_POLICYMATERIAL_POLLMATERIAL_POLYLINEMATERIAL_POLYMERMATERIAL_POOLMATERIAL_PORTABLE_WIFI_OFFMATERIAL_PORTRAITMATERIAL_POST_ADDMATERIAL_POWERMATERIAL_POWER_INPUTMATERIAL_POWER_OFFMATERIAL_POWER_SETTINGS_NEWMATERIAL_PRECISION_MANUFACTURINGMATERIAL_PREGNANT_WOMANMATERIAL_PRESENT_TO_ALLMATERIAL_PREVIEWMATERIAL_PRICE_CHANGEMATERIAL_PRICE_CHECKMATERIAL_PRINTMATERIAL_PRINT_DISABLEDMATERIAL_PRIORITY_HIGHMATERIAL_PRIVACY_TIPMATERIAL_PRIVATE_CONNECTIVITYMATERIAL_PRODUCTION_QUANTITY_LIMITSMATERIAL_PROPANEMATERIAL_PROPANE_TANKMATERIAL_PSYCHOLOGYMATERIAL_PSYCHOLOGY_ALTMATERIAL_PUBLICMATERIAL_PUBLIC_OFFMATERIAL_PUBLISHMATERIAL_PUBLISHED_WITH_CHANGESMATERIAL_PUNCH_CLOCKMATERIAL_PUSH_PINMATERIAL_QR_CODEMATERIAL_QR_CODE_2MATERIAL_QR_CODE_SCANNERMATERIAL_QUERY_BUILDERMATERIAL_QUERY_STATSMATERIAL_QUESTION_ANSWERMATERIAL_QUESTION_MARKMATERIAL_QUEUEMATERIAL_QUEUE_MUSICMATERIAL_QUEUE_PLAY_NEXTMATERIAL_QUICKREPLYMATERIAL_QUICK_CONTACTS_DIALERMATERIAL_QUICK_CONTACTS_MAILMATERIAL_QUIZMATERIAL_QUORAMATERIAL_RADARMATERIAL_RADIOMATERIAL_RADIO_BUTTON_CHECKEDMATERIAL_RADIO_BUTTON_OFFMATERIAL_RADIO_BUTTON_ONMATERIAL_RADIO_BUTTON_UNCHECKEDMATERIAL_RAILWAY_ALERTMATERIAL_RAMEN_DININGMATERIAL_RAMP_LEFTMATERIAL_RAMP_RIGHTMATERIAL_RATE_REVIEWMATERIAL_RAW_OFFMATERIAL_RAW_ONMATERIAL_READ_MOREMATERIAL_REAL_ESTATE_AGENTMATERIAL_REBASE_EDITMATERIAL_RECEIPTMATERIAL_RECEIPT_LONGMATERIAL_RECENT_ACTORSMATERIAL_RECOMMENDMATERIAL_RECORD_VOICE_OVERMATERIAL_RECTANGLEMATERIAL_RECYCLINGMATERIAL_REDDITMATERIAL_REDEEMMATERIAL_REDOMATERIAL_REDUCE_CAPACITYMATERIAL_REFRESHMATERIAL_REMEMBER_MEMATERIAL_REMOVEMATERIAL_REMOVE_CIRCLEMATERIAL_REMOVE_CIRCLE_OUTLINEMATERIAL_REMOVE_DONEMATERIAL_REMOVE_FROM_QUEUEMATERIAL_REMOVE_MODERATORMATERIAL_REMOVE_RED_EYEMATERIAL_REMOVE_ROADMATERIAL_REMOVE_SHOPPING_CARTMATERIAL_REORDERMATERIAL_REPARTITIONMATERIAL_REPEATMATERIAL_REPEAT_ONMATERIAL_REPEAT_ONEMATERIAL_REPEAT_ONE_ONMATERIAL_REPLAYMATERIAL_REPLAY_10MATERIAL_REPLAY_30MATERIAL_REPLAY_5MATERIAL_REPLAY_CIRCLE_FILLEDMATERIAL_REPLYMATERIAL_REPLY_ALLMATERIAL_REPORTMATERIAL_REPORT_GMAILERRORREDMATERIAL_REPORT_OFFMATERIAL_REPORT_PROBLEMMATERIAL_REQUEST_PAGEMATERIAL_REQUEST_QUOTEMATERIAL_RESET_TVMATERIAL_RESTART_ALTMATERIAL_RESTAURANTMATERIAL_RESTAURANT_MENUMATERIAL_RESTOREMATERIAL_RESTORE_FROM_TRASHMATERIAL_RESTORE_PAGEMATERIAL_REVIEWSMATERIAL_RICE_BOWLMATERIAL_RING_VOLUMEMATERIAL_ROCKETMATERIAL_ROCKET_LAUNCHMATERIAL_ROLLER_SHADESMATERIAL_ROLLER_SHADES_CLOSEDMATERIAL_ROLLER_SKATINGMATERIAL_ROOFINGMATERIAL_ROOMMATERIAL_ROOM_PREFERENCESMATERIAL_ROOM_SERVICEMATERIAL_ROTATE_90_DEGREES_CCWMATERIAL_ROTATE_90_DEGREES_CWMATERIAL_ROTATE_LEFTMATERIAL_ROTATE_RIGHTMATERIAL_ROUNDABOUT_LEFTMATERIAL_ROUNDABOUT_RIGHTMATERIAL_ROUNDED_CORNERMATERIAL_ROUTEMATERIAL_ROUTERMATERIAL_ROWINGMATERIAL_RSS_FEEDMATERIAL_RSVPMATERIAL_RTTMATERIAL_RULEMATERIAL_RULE_FOLDERMATERIAL_RUNNING_WITH_ERRORSMATERIAL_RUN_CIRCLEMATERIAL_RV_HOOKUPMATERIAL_R_MOBILEDATAMATERIAL_SAFETY_CHECKMATERIAL_SAFETY_DIVIDERMATERIAL_SAILINGMATERIAL_SANITIZERMATERIAL_SATELLITEMATERIAL_SATELLITE_ALTMATERIAL_SAVEMATERIAL_SAVED_SEARCHMATERIAL_SAVE_ALTMATERIAL_SAVE_ASMATERIAL_SAVINGSMATERIAL_SCALEMATERIAL_SCANNERMATERIAL_SCATTER_PLOTMATERIAL_SCHEDULEMATERIAL_SCHEDULE_SENDMATERIAL_SCHEMAMATERIAL_SCHOOLMATERIAL_SCIENCEMATERIAL_SCOREMATERIAL_SCOREBOARDMATERIAL_SCREENSHOTMATERIAL_SCREENSHOT_MONITORMATERIAL_SCREEN_LOCK_LANDSCAPEMATERIAL_SCREEN_LOCK_PORTRAITMATERIAL_SCREEN_LOCK_ROTATIONMATERIAL_SCREEN_ROTATIONMATERIAL_SCREEN_ROTATION_ALTMATERIAL_SCREEN_SEARCH_DESKTOPMATERIAL_SCREEN_SHAREMATERIAL_SCUBA_DIVINGMATERIAL_SDMATERIAL_SD_CARDMATERIAL_SD_CARD_ALERTMATERIAL_SD_STORAGEMATERIAL_SEARCHMATERIAL_SEARCH_OFFMATERIAL_SECURITYMATERIAL_SECURITY_UPDATEMATERIAL_SECURITY_UPDATE_GOODMATERIAL_SECURITY_UPDATE_WARNINGMATERIAL_SEGMENTMATERIAL_SELECT_ALLMATERIAL_SELF_IMPROVEMENTMATERIAL_SELLMATERIAL_SENDMATERIAL_SEND_AND_ARCHIVEMATERIAL_SEND_TIME_EXTENSIONMATERIAL_SEND_TO_MOBILEMATERIAL_SENSORSMATERIAL_SENSORS_OFFMATERIAL_SENSOR_DOORMATERIAL_SENSOR_OCCUPIEDMATERIAL_SENSOR_WINDOWMATERIAL_SENTIMENT_DISSATISFIEDMATERIAL_SENTIMENT_NEUTRALMATERIAL_SENTIMENT_SATISFIEDMATERIAL_SENTIMENT_SATISFIED_ALTMATERIAL_SENTIMENT_VERY_DISSATISFIEDMATERIAL_SENTIMENT_VERY_SATISFIEDMATERIAL_SETTINGSMATERIAL_SETTINGS_ACCESSIBILITYMATERIAL_SETTINGS_APPLICATIONSMATERIAL_SETTINGS_BACKUP_RESTOREMATERIAL_SETTINGS_BLUETOOTHMATERIAL_SETTINGS_BRIGHTNESSMATERIAL_SETTINGS_CELLMATERIAL_SETTINGS_DISPLAYMATERIAL_SETTINGS_ETHERNETMATERIAL_SETTINGS_INPUT_ANTENNAMATERIAL_SETTINGS_INPUT_COMPONENTMATERIAL_SETTINGS_INPUT_COMPOSITEMATERIAL_SETTINGS_INPUT_HDMIMATERIAL_SETTINGS_INPUT_SVIDEOMATERIAL_SETTINGS_OVERSCANMATERIAL_SETTINGS_PHONEMATERIAL_SETTINGS_POWERMATERIAL_SETTINGS_REMOTEMATERIAL_SETTINGS_SUGGESTMATERIAL_SETTINGS_SYSTEM_DAYDREAMMATERIAL_SETTINGS_VOICEMATERIAL_SET_MEALMATERIAL_SEVERE_COLDMATERIAL_SHAPE_LINEMATERIAL_SHAREMATERIAL_SHARE_ARRIVAL_TIMEMATERIAL_SHARE_LOCATIONMATERIAL_SHELVESMATERIAL_SHIELDMATERIAL_SHIELD_MOONMATERIAL_SHOPMATERIAL_SHOPIFYMATERIAL_SHOPPING_BAGMATERIAL_SHOPPING_BASKETMATERIAL_SHOPPING_CARTMATERIAL_SHOPPING_CART_CHECKOUTMATERIAL_SHOP_2MATERIAL_SHOP_TWOMATERIAL_SHORTCUTMATERIAL_SHORT_TEXTMATERIAL_SHOWERMATERIAL_SHOW_CHARTMATERIAL_SHUFFLEMATERIAL_SHUFFLE_ONMATERIAL_SHUTTER_SPEEDMATERIAL_SICKMATERIAL_SIGNAL_CELLULAR_0_BARMATERIAL_SIGNAL_CELLULAR_4_BARMATERIAL_SIGNAL_CELLULAR_ALTMATERIAL_SIGNAL_CELLULAR_ALT_1_BARMATERIAL_SIGNAL_CELLULAR_ALT_2_BARMATERIAL_SIGNAL_CELLULAR_CONNECTED_NO_INTERNET_0_BARMATERIAL_SIGNAL_CELLULAR_CONNECTED_NO_INTERNET_4_BARMATERIAL_SIGNAL_CELLULAR_NODATAMATERIAL_SIGNAL_CELLULAR_NO_SIMMATERIAL_SIGNAL_CELLULAR_NULLMATERIAL_SIGNAL_CELLULAR_OFFMATERIAL_SIGNAL_WIFI_0_BARMATERIAL_SIGNAL_WIFI_4_BARMATERIAL_SIGNAL_WIFI_4_BAR_LOCKMATERIAL_SIGNAL_WIFI_BADMATERIAL_SIGNAL_WIFI_CONNECTED_NO_INTERNET_4MATERIAL_SIGNAL_WIFI_OFFMATERIAL_SIGNAL_WIFI_STATUSBAR_4_BARMATERIAL_SIGNAL_WIFI_STATUSBAR_CONNECTED_NO_INTERNET_4MATERIAL_SIGNAL_WIFI_STATUSBAR_NULLMATERIAL_SIGNPOSTMATERIAL_SIGN_LANGUAGEMATERIAL_SIM_CARDMATERIAL_SIM_CARD_ALERTMATERIAL_SIM_CARD_DOWNLOADMATERIAL_SINGLE_BEDMATERIAL_SIPMATERIAL_SKATEBOARDINGMATERIAL_SKIP_NEXTMATERIAL_SKIP_PREVIOUSMATERIAL_SLEDDINGMATERIAL_SLIDESHOWMATERIAL_SLOW_MOTION_VIDEOMATERIAL_SMARTPHONEMATERIAL_SMART_BUTTONMATERIAL_SMART_DISPLAYMATERIAL_SMART_SCREENMATERIAL_SMART_TOYMATERIAL_SMOKE_FREEMATERIAL_SMOKING_ROOMSMATERIAL_SMSMATERIAL_SMS_FAILEDMATERIAL_SNAPCHATMATERIAL_SNIPPET_FOLDERMATERIAL_SNOOZEMATERIAL_SNOWBOARDINGMATERIAL_SNOWINGMATERIAL_SNOWMOBILEMATERIAL_SNOWSHOEINGMATERIAL_SOAPMATERIAL_SOCIAL_DISTANCEMATERIAL_SOLAR_POWERMATERIAL_SORTMATERIAL_SORT_BY_ALPHAMATERIAL_SOSMATERIAL_SOUP_KITCHENMATERIAL_SOURCEMATERIAL_SOUTHMATERIAL_SOUTH_AMERICAMATERIAL_SOUTH_EASTMATERIAL_SOUTH_WESTMATERIAL_SPAMATERIAL_SPACE_BARMATERIAL_SPACE_DASHBOARDMATERIAL_SPATIAL_AUDIOMATERIAL_SPATIAL_AUDIO_OFFMATERIAL_SPATIAL_TRACKINGMATERIAL_SPEAKERMATERIAL_SPEAKER_GROUPMATERIAL_SPEAKER_NOTESMATERIAL_SPEAKER_NOTES_OFFMATERIAL_SPEAKER_PHONEMATERIAL_SPEEDMATERIAL_SPELLCHECKMATERIAL_SPLITSCREENMATERIAL_SPOKEMATERIAL_SPORTSMATERIAL_SPORTS_BARMATERIAL_SPORTS_BASEBALLMATERIAL_SPORTS_BASKETBALLMATERIAL_SPORTS_CRICKETMATERIAL_SPORTS_ESPORTSMATERIAL_SPORTS_FOOTBALLMATERIAL_SPORTS_GOLFMATERIAL_SPORTS_GYMNASTICSMATERIAL_SPORTS_HANDBALLMATERIAL_SPORTS_HOCKEYMATERIAL_SPORTS_KABADDIMATERIAL_SPORTS_MARTIAL_ARTSMATERIAL_SPORTS_MMAMATERIAL_SPORTS_MOTORSPORTSMATERIAL_SPORTS_RUGBYMATERIAL_SPORTS_SCOREMATERIAL_SPORTS_SOCCERMATERIAL_SPORTS_TENNISMATERIAL_SPORTS_VOLLEYBALLMATERIAL_SQUAREMATERIAL_SQUARE_FOOTMATERIAL_SSID_CHARTMATERIAL_STACKED_BAR_CHARTMATERIAL_STACKED_LINE_CHARTMATERIAL_STADIUMMATERIAL_STAIRSMATERIAL_STARMATERIAL_STARSMATERIAL_STARTMATERIAL_STAR_BORDERMATERIAL_STAR_BORDER_PURPLE500MATERIAL_STAR_HALFMATERIAL_STAR_OUTLINEMATERIAL_STAR_PURPLE500MATERIAL_STAR_RATEMATERIAL_STAY_CURRENT_LANDSCAPEMATERIAL_STAY_CURRENT_PORTRAITMATERIAL_STAY_PRIMARY_LANDSCAPEMATERIAL_STAY_PRIMARY_PORTRAITMATERIAL_STICKY_NOTE_2MATERIAL_STOPMATERIAL_STOP_CIRCLEMATERIAL_STOP_SCREEN_SHAREMATERIAL_STORAGEMATERIAL_STOREMATERIAL_STOREFRONTMATERIAL_STORE_MALL_DIRECTORYMATERIAL_STORMMATERIAL_STRAIGHTMATERIAL_STRAIGHTENMATERIAL_STREAMMATERIAL_STREETVIEWMATERIAL_STRIKETHROUGH_SMATERIAL_STROLLERMATERIAL_STYLEMATERIAL_SUBDIRECTORY_ARROW_LEFTMATERIAL_SUBDIRECTORY_ARROW_RIGHTMATERIAL_SUBJECTMATERIAL_SUBSCRIPTMATERIAL_SUBSCRIPTIONSMATERIAL_SUBTITLESMATERIAL_SUBTITLES_OFFMATERIAL_SUBWAYMATERIAL_SUMMARIZEMATERIAL_SUNNYMATERIAL_SUNNY_SNOWINGMATERIAL_SUPERSCRIPTMATERIAL_SUPERVISED_USER_CIRCLEMATERIAL_SUPERVISOR_ACCOUNTMATERIAL_SUPPORTMATERIAL_SUPPORT_AGENTMATERIAL_SURFINGMATERIAL_SURROUND_SOUNDMATERIAL_SWAP_CALLSMATERIAL_SWAP_HORIZMATERIAL_SWAP_HORIZONTAL_CIRCLEMATERIAL_SWAP_VERTMATERIAL_SWAP_VERTICAL_CIRCLEMATERIAL_SWAP_VERT_CIRCLEMATERIAL_SWIPEMATERIAL_SWIPE_DOWNMATERIAL_SWIPE_DOWN_ALTMATERIAL_SWIPE_LEFTMATERIAL_SWIPE_LEFT_ALTMATERIAL_SWIPE_RIGHTMATERIAL_SWIPE_RIGHT_ALTMATERIAL_SWIPE_UPMATERIAL_SWIPE_UP_ALTMATERIAL_SWIPE_VERTICALMATERIAL_SWITCH_ACCESS_SHORTCUTMATERIAL_SWITCH_ACCESS_SHORTCUT_ADDMATERIAL_SWITCH_ACCOUNTMATERIAL_SWITCH_CAMERAMATERIAL_SWITCH_LEFTMATERIAL_SWITCH_RIGHTMATERIAL_SWITCH_VIDEOMATERIAL_SYNAGOGUEMATERIAL_SYNCMATERIAL_SYNC_ALTMATERIAL_SYNC_DISABLEDMATERIAL_SYNC_LOCKMATERIAL_SYNC_PROBLEMMATERIAL_SYSTEM_SECURITY_UPDATEMATERIAL_SYSTEM_SECURITY_UPDATE_GOODMATERIAL_SYSTEM_SECURITY_UPDATE_WARNINGMATERIAL_SYSTEM_UPDATEMATERIAL_SYSTEM_UPDATE_ALTMATERIAL_SYSTEM_UPDATE_TVMATERIAL_TABMATERIAL_TABLETMATERIAL_TABLET_ANDROIDMATERIAL_TABLET_MACMATERIAL_TABLE_BARMATERIAL_TABLE_CHARTMATERIAL_TABLE_RESTAURANTMATERIAL_TABLE_ROWSMATERIAL_TABLE_VIEWMATERIAL_TAB_UNSELECTEDMATERIAL_TAGMATERIAL_TAG_FACESMATERIAL_TAKEOUT_DININGMATERIAL_TAPASMATERIAL_TAP_AND_PLAYMATERIAL_TASKMATERIAL_TASK_ALTMATERIAL_TAXI_ALERTMATERIAL_TELEGRAMMATERIAL_TEMPLE_BUDDHISTMATERIAL_TEMPLE_HINDUMATERIAL_TERMINALMATERIAL_TERRAINMATERIAL_TEXTSMSMATERIAL_TEXTUREMATERIAL_TEXT_DECREASEMATERIAL_TEXT_FIELDSMATERIAL_TEXT_FORMATMATERIAL_TEXT_INCREASEMATERIAL_TEXT_ROTATE_UPMATERIAL_TEXT_ROTATE_VERTICALMATERIAL_TEXT_ROTATION_ANGLEDOWNMATERIAL_TEXT_ROTATION_ANGLEUPMATERIAL_TEXT_ROTATION_DOWNMATERIAL_TEXT_ROTATION_NONEMATERIAL_TEXT_SNIPPETMATERIAL_THEATERSMATERIAL_THEATER_COMEDYMATERIAL_THERMOSTATMATERIAL_THERMOSTAT_AUTOMATERIAL_THUMBS_UP_DOWNMATERIAL_THUMB_DOWNMATERIAL_THUMB_DOWN_ALTMATERIAL_THUMB_DOWN_OFF_ALTMATERIAL_THUMB_UPMATERIAL_THUMB_UP_ALTMATERIAL_THUMB_UP_OFF_ALTMATERIAL_THUNDERSTORMMATERIAL_TIKTOKMATERIAL_TIMELAPSEMATERIAL_TIMELINEMATERIAL_TIMERMATERIAL_TIMER_10MATERIAL_TIMER_10_SELECTMATERIAL_TIMER_3MATERIAL_TIMER_3_SELECTMATERIAL_TIMER_OFFMATERIAL_TIME_TO_LEAVEMATERIAL_TIPS_AND_UPDATESMATERIAL_TIRE_REPAIRMATERIAL_TITLEMATERIAL_TOCMATERIAL_TODAYMATERIAL_TOGGLE_OFFMATERIAL_TOGGLE_ONMATERIAL_TOKENMATERIAL_TOLLMATERIAL_TONALITYMATERIAL_TOPICMATERIAL_TORNADOMATERIAL_TOUCH_APPMATERIAL_TOURMATERIAL_TOYSMATERIAL_TRACK_CHANGESMATERIAL_TRAFFICMATERIAL_TRAINMATERIAL_TRAMMATERIAL_TRANSCRIBEMATERIAL_TRANSFER_WITHIN_A_STATIONMATERIAL_TRANSFORMMATERIAL_TRANSGENDERMATERIAL_TRANSIT_ENTEREXITMATERIAL_TRANSLATEMATERIAL_TRAVEL_EXPLOREMATERIAL_TRENDING_DOWNMATERIAL_TRENDING_FLATMATERIAL_TRENDING_NEUTRALMATERIAL_TRENDING_UPMATERIAL_TRIP_ORIGINMATERIAL_TROLLEYMATERIAL_TROUBLESHOOTMATERIAL_TRYMATERIAL_TSUNAMIMATERIAL_TTYMATERIAL_TUNEMATERIAL_TUNGSTENMATERIAL_TURNED_INMATERIAL_TURNED_IN_NOTMATERIAL_TURN_LEFTMATERIAL_TURN_RIGHTMATERIAL_TURN_SHARP_LEFTMATERIAL_TURN_SHARP_RIGHTMATERIAL_TURN_SLIGHT_LEFTMATERIAL_TURN_SLIGHT_RIGHTMATERIAL_TVMATERIAL_TV_OFFMATERIAL_TWO_WHEELERMATERIAL_TYPE_SPECIMENMATERIAL_UMBRELLAMATERIAL_UNARCHIVEMATERIAL_UNDOMATERIAL_UNFOLD_LESSMATERIAL_UNFOLD_LESS_DOUBLEMATERIAL_UNFOLD_MOREMATERIAL_UNFOLD_MORE_DOUBLEMATERIAL_UNPUBLISHEDMATERIAL_UNSUBSCRIBEMATERIAL_UPCOMINGMATERIAL_UPDATEMATERIAL_UPDATE_DISABLEDMATERIAL_UPGRADEMATERIAL_UPLOADMATERIAL_UPLOAD_FILEMATERIAL_USBMATERIAL_USB_OFFMATERIAL_U_TURN_LEFTMATERIAL_U_TURN_RIGHTMATERIAL_VACCINESMATERIAL_VAPE_FREEMATERIAL_VAPING_ROOMSMATERIAL_VERIFIEDMATERIAL_VERIFIED_USERMATERIAL_VERTICAL_ALIGN_BOTTOMMATERIAL_VERTICAL_ALIGN_CENTERMATERIAL_VERTICAL_ALIGN_TOPMATERIAL_VERTICAL_DISTRIBUTEMATERIAL_VERTICAL_SHADESMATERIAL_VERTICAL_SHADES_CLOSEDMATERIAL_VERTICAL_SPLITMATERIAL_VIBRATIONMATERIAL_VIDEOCAMMATERIAL_VIDEOCAM_OFFMATERIAL_VIDEOGAME_ASSETMATERIAL_VIDEOGAME_ASSET_OFFMATERIAL_VIDEO_CALLMATERIAL_VIDEO_CAMERA_BACKMATERIAL_VIDEO_CAMERA_FRONTMATERIAL_VIDEO_CHATMATERIAL_VIDEO_COLLECTIONMATERIAL_VIDEO_FILEMATERIAL_VIDEO_LABELMATERIAL_VIDEO_LIBRARYMATERIAL_VIDEO_SETTINGSMATERIAL_VIDEO_STABLEMATERIAL_VIEW_AGENDAMATERIAL_VIEW_ARRAYMATERIAL_VIEW_CAROUSELMATERIAL_VIEW_COLUMNMATERIAL_VIEW_COMFORTABLEMATERIAL_VIEW_COMFYMATERIAL_VIEW_COMFY_ALTMATERIAL_VIEW_COMPACTMATERIAL_VIEW_COMPACT_ALTMATERIAL_VIEW_COZYMATERIAL_VIEW_DAYMATERIAL_VIEW_HEADLINEMATERIAL_VIEW_IN_ARMATERIAL_VIEW_KANBANMATERIAL_VIEW_LISTMATERIAL_VIEW_MODULEMATERIAL_VIEW_QUILTMATERIAL_VIEW_SIDEBARMATERIAL_VIEW_STREAMMATERIAL_VIEW_TIMELINEMATERIAL_VIEW_WEEKMATERIAL_VIGNETTEMATERIAL_VILLAMATERIAL_VISIBILITYMATERIAL_VISIBILITY_OFFMATERIAL_VOICEMAILMATERIAL_VOICE_CHATMATERIAL_VOICE_OVER_OFFMATERIAL_VOLCANOMATERIAL_VOLUME_DOWNMATERIAL_VOLUME_DOWN_ALTMATERIAL_VOLUME_MUTEMATERIAL_VOLUME_OFFMATERIAL_VOLUME_UPMATERIAL_VOLUNTEER_ACTIVISMMATERIAL_VPN_KEYMATERIAL_VPN_KEY_OFFMATERIAL_VPN_LOCKMATERIAL_VRPANOMATERIAL_WALLETMATERIAL_WALLET_GIFTCARDMATERIAL_WALLET_MEMBERSHIPMATERIAL_WALLET_TRAVELMATERIAL_WALLPAPERMATERIAL_WAREHOUSEMATERIAL_WARNINGMATERIAL_WARNING_AMBERMATERIAL_WASHMATERIAL_WATCHMATERIAL_WATCH_LATERMATERIAL_WATCH_OFFMATERIAL_WATERMATERIAL_WATERFALL_CHARTMATERIAL_WATER_DAMAGEMATERIAL_WATER_DROPMATERIAL_WAVESMATERIAL_WAVING_HANDMATERIAL_WB_AUTOMATERIAL_WB_CLOUDYMATERIAL_WB_INCANDESCENTMATERIAL_WB_IRIDESCENTMATERIAL_WB_SHADEMATERIAL_WB_SUNNYMATERIAL_WB_TWIGHLIGHTMATERIAL_WB_TWILIGHTMATERIAL_WCMATERIAL_WEBMATERIAL_WEBHOOKMATERIAL_WEB_ASSETMATERIAL_WEB_ASSET_OFFMATERIAL_WEB_STORIESMATERIAL_WECHATMATERIAL_WEEKENDMATERIAL_WESTMATERIAL_WHATSHOTMATERIAL_WHEELCHAIR_PICKUPMATERIAL_WHERE_TO_VOTEMATERIAL_WIDGETSMATERIAL_WIDTH_FULLMATERIAL_WIDTH_NORMALMATERIAL_WIDTH_WIDEMATERIAL_WIFIMATERIAL_WIFI_1_BARMATERIAL_WIFI_2_BARMATERIAL_WIFI_CALLINGMATERIAL_WIFI_CALLING_3MATERIAL_WIFI_CHANNELMATERIAL_WIFI_FINDMATERIAL_WIFI_LOCKMATERIAL_WIFI_OFFMATERIAL_WIFI_PASSWORDMATERIAL_WIFI_PROTECTED_SETUPMATERIAL_WIFI_TETHERINGMATERIAL_WIFI_TETHERING_ERRORMATERIAL_WIFI_TETHERING_ERROR_ROUNDEDMATERIAL_WIFI_TETHERING_OFFMATERIAL_WINDOWMATERIAL_WIND_POWERMATERIAL_WINE_BARMATERIAL_WOMANMATERIAL_WOMAN_2MATERIAL_WOO_COMMERCEMATERIAL_WORDPRESSMATERIAL_WORKMATERIAL_WORKSPACESMATERIAL_WORKSPACES_FILLEDMATERIAL_WORKSPACES_OUTLINEMATERIAL_WORKSPACE_PREMIUMMATERIAL_WORK_HISTORYMATERIAL_WORK_OFFMATERIAL_WORK_OUTLINEMATERIAL_WRAP_TEXTMATERIAL_WRONG_LOCATIONMATERIAL_WYSIWYGMATERIAL_YARDMATERIAL_YOUTUBE_SEARCHED_FORMATERIAL_ZOOM_INMATERIAL_ZOOM_IN_MAPMATERIAL_ZOOM_OUTMATERIAL_ZOOM_OUT_MAP")); index.put("com.codename1.ui.Form", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); @@ -5445,9 +5776,6 @@ private static void fillFieldIndex20(Map index) { index.put("com.codename1.ui.RichTextFormat", splitMembers("ASCIIDOCHTMLMARKDOWNPLAIN_TEXTRTF")); index.put("com.codename1.ui.SelectableIconHolder", splitMembers("")); index.put("com.codename1.ui.Sheet", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); - } - - private static void fillFieldIndex21(Map index) { index.put("com.codename1.ui.SideMenuBar", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCOMMAND_ACTIONABLECOMMAND_PLACEMENT_KEYCOMMAND_PLACEMENT_VALUE_RIGHTCOMMAND_PLACEMENT_VALUE_TOPCOMMAND_SIDE_COMPONENTCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.ui.Slider", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.ui.Stroke", splitMembers("CAP_BUTTCAP_ROUNDCAP_SQUAREJOIN_BEVELJOIN_MITERJOIN_ROUND")); @@ -5474,6 +5802,9 @@ private static void fillFieldIndex21(Map index) { index.put("com.codename1.ui.UIFragment.DefaultComponentFactory", splitMembers("")); index.put("com.codename1.ui.URLImage", splitMembers("FLAG_RESIZE_FAILFLAG_RESIZE_SCALEFLAG_RESIZE_SCALE_TO_FILLRESIZE_FAILRESIZE_SCALERESIZE_SCALE_TO_FILL")); index.put("com.codename1.ui.URLImage.ErrorCallback", splitMembers("")); + } + + private static void fillFieldIndex23(Map index) { index.put("com.codename1.ui.URLImage.ImageAdapter", splitMembers("")); index.put("com.codename1.ui.URLImage.RequestDecorator", splitMembers("")); index.put("com.codename1.ui.VirtualInputDevice", splitMembers("")); @@ -5512,9 +5843,6 @@ private static void fillFieldIndex21(Map index) { index.put("com.codename1.ui.css.CSSThemeCompiler.CSSSyntaxException", splitMembers("")); index.put("com.codename1.ui.editor.CodePureEditor", splitMembers("")); index.put("com.codename1.ui.editor.CodeView", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORKEY_BACKSPACEKEY_COPYKEY_CUTKEY_DELETEKEY_DOWNKEY_ENDKEY_ESCAPEKEY_HOMEKEY_LEFTKEY_PAGE_DOWNKEY_PAGE_UPKEY_PASTEKEY_REDOKEY_RIGHTKEY_SELECT_ALLKEY_TABKEY_UNDOKEY_UPLEFTMOD_ALTMOD_CTRLMOD_SHIFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); - } - - private static void fillFieldIndex22(Map index) { index.put("com.codename1.ui.editor.EditorDocument", splitMembers("")); index.put("com.codename1.ui.editor.EditorHost", splitMembers("")); index.put("com.codename1.ui.editor.EditorView", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORKEY_BACKSPACEKEY_COPYKEY_CUTKEY_DELETEKEY_DOWNKEY_ENDKEY_ESCAPEKEY_HOMEKEY_LEFTKEY_PAGE_DOWNKEY_PAGE_UPKEY_PASTEKEY_REDOKEY_RIGHTKEY_SELECT_ALLKEY_TABKEY_UNDOKEY_UPLEFTMOD_ALTMOD_CTRLMOD_SHIFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); @@ -5541,6 +5869,9 @@ private static void fillFieldIndex22(Map index) { index.put("com.codename1.ui.editor.Tokenizer", splitMembers("COMMENTKEYWORDNUMBERPROPERTYSTATE_BLOCK_COMMENTSTATE_CSS_COMMENT_DECLARATIONSTATE_CSS_DECLARATIONSTATE_NORMALSTATE_TEMPLATESTATE_TRIPLE_DOUBLESTATE_TRIPLE_SINGLESTATE_XML_COMMENTSTRING")); index.put("com.codename1.ui.editor.UndoManager", splitMembers("")); index.put("com.codename1.ui.events.ActionEvent", splitMembers("")); + } + + private static void fillFieldIndex24(Map index) { index.put("com.codename1.ui.events.ActionEvent.Type", splitMembers("CalendarChangeCommandDataDoneDragFinishedEditExceptionIsGalleryTypeSupportedJavaScriptKeyPressKeyReleaseLogLongPointerPressOpenGalleryOrientationChangeOtherPointerPointerDragPointerPressedPointerReleasedPointerWheelPostureChangeProgressResponseShowSizeChangeSwipeTheme")); index.put("com.codename1.ui.events.ActionListener", splitMembers("")); index.put("com.codename1.ui.events.ActionSource", splitMembers("")); @@ -5579,9 +5910,6 @@ private static void fillFieldIndex22(Map index) { index.put("com.codename1.ui.html.HTMLParser", splitMembers("")); index.put("com.codename1.ui.html.HTMLUtils", splitMembers("")); index.put("com.codename1.ui.html.IOCallback", splitMembers("")); - } - - private static void fillFieldIndex23(Map index) { index.put("com.codename1.ui.layouts.BorderLayout", splitMembers("CENTERCENTER_BEHAVIOR_CENTERCENTER_BEHAVIOR_CENTER_ABSOLUTECENTER_BEHAVIOR_SCALECENTER_BEHAVIOR_TOTAL_BELLOWCENTER_BEHAVIOR_TOTAL_BELOWEASTNORTHOVERLAYSOUTHWEST")); index.put("com.codename1.ui.layouts.BoxLayout", splitMembers("X_AXISX_AXIS_NO_GROWY_AXISY_AXIS_BOTTOM_LAST")); index.put("com.codename1.ui.layouts.CoordinateLayout", splitMembers("")); @@ -5608,6 +5936,9 @@ private static void fillFieldIndex23(Map index) { index.put("com.codename1.ui.layouts.mig.LayoutCallback", splitMembers("")); index.put("com.codename1.ui.layouts.mig.LayoutUtil", splitMembers("HAS_BEANSHORIZONTALINFMAXMINPREFVERTICAL")); index.put("com.codename1.ui.layouts.mig.LinkHandler", splitMembers("HEIGHTWIDTHXX2YY2")); + } + + private static void fillFieldIndex25(Map index) { index.put("com.codename1.ui.layouts.mig.MigLayout", splitMembers("")); index.put("com.codename1.ui.layouts.mig.PlatformDefaults", splitMembers("BASE_FONT_SIZEBASE_REAL_PIXELBASE_SCALE_FACTORGNOMEMAC_OSXVISUAL_PADDING_PROPERTYWINDOWS_XP")); index.put("com.codename1.ui.layouts.mig.UnitConverter", splitMembers("UNABLE")); @@ -5646,9 +5977,6 @@ private static void fillFieldIndex23(Map index) { index.put("com.codename1.ui.plaf.StyleParser.StyleInfo", splitMembers("")); index.put("com.codename1.ui.plaf.UIManager", splitMembers("")); index.put("com.codename1.ui.scene.Bounds", splitMembers("")); - } - - private static void fillFieldIndex24(Map index) { index.put("com.codename1.ui.scene.Camera", splitMembers("farClipnearClip")); index.put("com.codename1.ui.scene.Node", splitMembers("boundsInLocallayoutXlayoutYlayoutZlocalCanvasZopacitypaintingRectrotaterotationAxisscaleXscaleYscaleZtranslateXtranslateYtranslateZvisible")); index.put("com.codename1.ui.scene.NodePainter", splitMembers("")); @@ -5675,6 +6003,9 @@ private static void fillFieldIndex24(Map index) { index.put("com.codename1.ui.tree.Tree", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.ui.tree.Tree.TreeState", splitMembers("")); index.put("com.codename1.ui.tree.TreeModel", splitMembers("")); + } + + private static void fillFieldIndex26(Map index) { index.put("com.codename1.ui.util.Effects", splitMembers("")); index.put("com.codename1.ui.util.EmbeddedContainer", splitMembers("BASELINEBOTTOMBRB_CENTER_OFFSETBRB_CONSTANT_ASCENTBRB_CONSTANT_DESCENTBRB_OTHERCENTERCROSSHAIR_CURSORDEFAULT_CURSORDRAG_REGION_IMMEDIATELY_DRAG_XDRAG_REGION_IMMEDIATELY_DRAG_XYDRAG_REGION_IMMEDIATELY_DRAG_YDRAG_REGION_LIKELY_DRAG_XDRAG_REGION_LIKELY_DRAG_XYDRAG_REGION_LIKELY_DRAG_YDRAG_REGION_NOT_DRAGGABLEDRAG_REGION_POSSIBLE_DRAG_XDRAG_REGION_POSSIBLE_DRAG_XYDRAG_REGION_POSSIBLE_DRAG_YE_RESIZE_CURSORHAND_CURSORLEFTMOVE_CURSORNE_RESIZE_CURSORNW_RESIZE_CURSORN_RESIZE_CURSORRIGHTSE_RESIZE_CURSORSW_RESIZE_CURSORS_RESIZE_CURSORTEXT_CURSORTOPWAIT_CURSORW_RESIZE_CURSOR")); index.put("com.codename1.ui.util.EventDispatcher", splitMembers("")); @@ -5713,9 +6044,6 @@ private static void fillFieldIndex24(Map index) { index.put("com.codename1.util.EasyThread.ErrorListener", splitMembers("")); index.put("com.codename1.util.FailureCallback", splitMembers("")); index.put("com.codename1.util.LazyValue", splitMembers("")); - } - - private static void fillFieldIndex25(Map index) { index.put("com.codename1.util.MathUtil", splitMembers("")); index.put("com.codename1.util.OnComplete", splitMembers("")); index.put("com.codename1.util.RunnableWithResult", splitMembers("")); @@ -5742,6 +6070,9 @@ private static void fillFieldIndex25(Map index) { index.put("com.codename1.util.regex.StringReader", splitMembers("")); index.put("com.codename1.vr.HeadTracker", splitMembers("")); index.put("com.codename1.vr.Media360View", splitMembers("")); + } + + private static void fillFieldIndex27(Map index) { index.put("com.codename1.vr.OrientationFilter", splitMembers("")); index.put("com.codename1.vr.TextureSource", splitMembers("")); index.put("com.codename1.vr.VRCameraRig", splitMembers("")); @@ -5750,6 +6081,7 @@ private static void fillFieldIndex25(Map index) { index.put("com.codename1.vr.VRSettings", splitMembers("")); index.put("com.codename1.vr.VRView", splitMembers("")); index.put("com.codename1.wearable.WearableConnection", splitMembers("")); + index.put("com.codename1.wearable.WearableConnection.DroppedDeliveryHandler", splitMembers("")); index.put("com.codename1.wearable.WearableDataListener", splitMembers("")); index.put("com.codename1.wearable.WearableMessage", splitMembers("")); index.put("com.codename1.wearable.WearableMessageListener", splitMembers("")); @@ -5780,9 +6112,6 @@ private static void fillFieldIndex25(Map index) { index.put("java.io.FileNotFoundException", splitMembers("")); index.put("java.io.Flushable", splitMembers("")); index.put("java.io.IOException", splitMembers("")); - } - - private static void fillFieldIndex26(Map index) { index.put("java.io.InputStream", splitMembers("")); index.put("java.io.InputStreamReader", splitMembers("")); index.put("java.io.InterruptedIOException", splitMembers("")); @@ -5808,6 +6137,9 @@ private static void fillFieldIndex26(Map index) { index.put("java.lang.Character", splitMembers("")); index.put("java.lang.Class", splitMembers("")); index.put("java.lang.ClassCastException", splitMembers("")); + } + + private static void fillFieldIndex28(Map index) { index.put("java.lang.ClassLoader", splitMembers("")); index.put("java.lang.ClassNotFoundException", splitMembers("")); index.put("java.lang.CloneNotSupportedException", splitMembers("")); @@ -5847,9 +6179,6 @@ private static void fillFieldIndex26(Map index) { index.put("java.lang.SafeVarargs", splitMembers("")); index.put("java.lang.SecurityException", splitMembers("")); index.put("java.lang.Short", splitMembers("")); - } - - private static void fillFieldIndex27(Map index) { index.put("java.lang.StackTraceElement", splitMembers("")); index.put("java.lang.String", splitMembers("")); index.put("java.lang.StringBuffer", splitMembers("")); @@ -5875,6 +6204,9 @@ private static void fillFieldIndex27(Map index) { index.put("java.text.DateFormat", splitMembers("")); index.put("java.text.DateFormatSymbols", splitMembers("")); index.put("java.text.Format", splitMembers("")); + } + + private static void fillFieldIndex29(Map index) { index.put("java.text.ParseException", splitMembers("")); index.put("java.text.SimpleDateFormat", splitMembers("")); index.put("java.time.Clock", splitMembers("")); @@ -5914,9 +6246,6 @@ private static void fillFieldIndex27(Map index) { index.put("java.util.Dictionary", splitMembers("")); index.put("java.util.EmptyStackException", splitMembers("")); index.put("java.util.Enumeration", splitMembers("")); - } - - private static void fillFieldIndex28(Map index) { index.put("java.util.EventListener", splitMembers("")); index.put("java.util.HashMap", splitMembers("")); index.put("java.util.HashSet", splitMembers("")); @@ -5942,6 +6271,9 @@ private static void fillFieldIndex28(Map index) { index.put("java.util.Random", splitMembers("")); index.put("java.util.RandomAccess", splitMembers("")); index.put("java.util.Set", splitMembers("")); + } + + private static void fillFieldIndex30(Map index) { index.put("java.util.SortedMap", splitMembers("")); index.put("java.util.SortedSet", splitMembers("")); index.put("java.util.Stack", splitMembers("")); @@ -6020,6 +6352,9 @@ private static Class findClassInPackage(String packageName, String fullName) if ("com.codename1.annotations".equals(packageName)) { return GeneratedAccess_com_codename1_annotations.findClass(fullName); } + if ("com.codename1.annotations.buildhints".equals(packageName)) { + return GeneratedAccess_com_codename1_annotations_buildhints.findClass(fullName); + } if ("com.codename1.annotations.graphql".equals(packageName)) { return GeneratedAccess_com_codename1_annotations_graphql.findClass(fullName); } @@ -6179,6 +6514,21 @@ private static Class findClassInPackage(String packageName, String fullName) if ("com.codename1.health.workout".equals(packageName)) { return GeneratedAccess_com_codename1_health_workout.findClass(fullName); } + if ("com.codename1.home".equals(packageName)) { + return GeneratedAccess_com_codename1_home.findClass(fullName); + } + if ("com.codename1.home.commissioning".equals(packageName)) { + return GeneratedAccess_com_codename1_home_commissioning.findClass(fullName); + } + if ("com.codename1.home.spi".equals(packageName)) { + return GeneratedAccess_com_codename1_home_spi.findClass(fullName); + } + if ("com.codename1.intents".equals(packageName)) { + return GeneratedAccess_com_codename1_intents.findClass(fullName); + } + if ("com.codename1.intents.spi".equals(packageName)) { + return GeneratedAccess_com_codename1_intents_spi.findClass(fullName); + } if ("com.codename1.io".equals(packageName)) { return GeneratedAccess_com_codename1_io.findClass(fullName); } @@ -6290,6 +6640,9 @@ private static Class findClassInPackage(String packageName, String fullName) if ("com.codename1.security".equals(packageName)) { return GeneratedAccess_com_codename1_security.findClass(fullName); } + if ("com.codename1.security.hardening".equals(packageName)) { + return GeneratedAccess_com_codename1_security_hardening.findClass(fullName); + } if ("com.codename1.security.shield".equals(packageName)) { return GeneratedAccess_com_codename1_security_shield.findClass(fullName); } @@ -6486,6 +6839,9 @@ public Object construct(Class type, Object[] args) throws Exception { if ("com.codename1.annotations".equals(candidate)) { return GeneratedAccess_com_codename1_annotations.construct(type, args); } + if ("com.codename1.annotations.buildhints".equals(candidate)) { + return GeneratedAccess_com_codename1_annotations_buildhints.construct(type, args); + } if ("com.codename1.annotations.graphql".equals(candidate)) { return GeneratedAccess_com_codename1_annotations_graphql.construct(type, args); } @@ -6645,6 +7001,21 @@ public Object construct(Class type, Object[] args) throws Exception { if ("com.codename1.health.workout".equals(candidate)) { return GeneratedAccess_com_codename1_health_workout.construct(type, args); } + if ("com.codename1.home".equals(candidate)) { + return GeneratedAccess_com_codename1_home.construct(type, args); + } + if ("com.codename1.home.commissioning".equals(candidate)) { + return GeneratedAccess_com_codename1_home_commissioning.construct(type, args); + } + if ("com.codename1.home.spi".equals(candidate)) { + return GeneratedAccess_com_codename1_home_spi.construct(type, args); + } + if ("com.codename1.intents".equals(candidate)) { + return GeneratedAccess_com_codename1_intents.construct(type, args); + } + if ("com.codename1.intents.spi".equals(candidate)) { + return GeneratedAccess_com_codename1_intents_spi.construct(type, args); + } if ("com.codename1.io".equals(candidate)) { return GeneratedAccess_com_codename1_io.construct(type, args); } @@ -6756,6 +7127,9 @@ public Object construct(Class type, Object[] args) throws Exception { if ("com.codename1.security".equals(candidate)) { return GeneratedAccess_com_codename1_security.construct(type, args); } + if ("com.codename1.security.hardening".equals(candidate)) { + return GeneratedAccess_com_codename1_security_hardening.construct(type, args); + } if ("com.codename1.security.shield".equals(candidate)) { return GeneratedAccess_com_codename1_security_shield.construct(type, args); } @@ -6943,6 +7317,9 @@ public Object invokeStatic(Class type, String name, Object[] args) throws Exc if ("com.codename1.annotations".equals(candidate)) { return GeneratedAccess_com_codename1_annotations.invokeStatic(type, name, args); } + if ("com.codename1.annotations.buildhints".equals(candidate)) { + return GeneratedAccess_com_codename1_annotations_buildhints.invokeStatic(type, name, args); + } if ("com.codename1.annotations.graphql".equals(candidate)) { return GeneratedAccess_com_codename1_annotations_graphql.invokeStatic(type, name, args); } @@ -7102,6 +7479,21 @@ public Object invokeStatic(Class type, String name, Object[] args) throws Exc if ("com.codename1.health.workout".equals(candidate)) { return GeneratedAccess_com_codename1_health_workout.invokeStatic(type, name, args); } + if ("com.codename1.home".equals(candidate)) { + return GeneratedAccess_com_codename1_home.invokeStatic(type, name, args); + } + if ("com.codename1.home.commissioning".equals(candidate)) { + return GeneratedAccess_com_codename1_home_commissioning.invokeStatic(type, name, args); + } + if ("com.codename1.home.spi".equals(candidate)) { + return GeneratedAccess_com_codename1_home_spi.invokeStatic(type, name, args); + } + if ("com.codename1.intents".equals(candidate)) { + return GeneratedAccess_com_codename1_intents.invokeStatic(type, name, args); + } + if ("com.codename1.intents.spi".equals(candidate)) { + return GeneratedAccess_com_codename1_intents_spi.invokeStatic(type, name, args); + } if ("com.codename1.io".equals(candidate)) { return GeneratedAccess_com_codename1_io.invokeStatic(type, name, args); } @@ -7213,6 +7605,9 @@ public Object invokeStatic(Class type, String name, Object[] args) throws Exc if ("com.codename1.security".equals(candidate)) { return GeneratedAccess_com_codename1_security.invokeStatic(type, name, args); } + if ("com.codename1.security.hardening".equals(candidate)) { + return GeneratedAccess_com_codename1_security_hardening.invokeStatic(type, name, args); + } if ("com.codename1.security.shield".equals(candidate)) { return GeneratedAccess_com_codename1_security_shield.invokeStatic(type, name, args); } @@ -7386,151 +7781,158 @@ private static java.util.Map buildPackageHandlerIndex() { m.put("com.codename1.ai.vision", Integer.valueOf(5)); m.put("com.codename1.analytics", Integer.valueOf(6)); m.put("com.codename1.annotations", Integer.valueOf(7)); - m.put("com.codename1.annotations.graphql", Integer.valueOf(8)); - m.put("com.codename1.annotations.grpc", Integer.valueOf(9)); - m.put("com.codename1.annotations.rest", Integer.valueOf(10)); - m.put("com.codename1.appreview", Integer.valueOf(11)); - m.put("com.codename1.ar", Integer.valueOf(12)); - m.put("com.codename1.background", Integer.valueOf(13)); - m.put("com.codename1.binding", Integer.valueOf(14)); - m.put("com.codename1.bluetooth", Integer.valueOf(15)); - m.put("com.codename1.bluetooth.classic", Integer.valueOf(16)); - m.put("com.codename1.bluetooth.gatt", Integer.valueOf(17)); - m.put("com.codename1.bluetooth.le", Integer.valueOf(18)); - m.put("com.codename1.bluetooth.le.server", Integer.valueOf(19)); - m.put("com.codename1.calendar", Integer.valueOf(20)); - m.put("com.codename1.camera", Integer.valueOf(21)); - m.put("com.codename1.capture", Integer.valueOf(22)); - m.put("com.codename1.car", Integer.valueOf(23)); - m.put("com.codename1.car.spi", Integer.valueOf(24)); - m.put("com.codename1.charts", Integer.valueOf(25)); - m.put("com.codename1.charts.compat", Integer.valueOf(26)); - m.put("com.codename1.charts.models", Integer.valueOf(27)); - m.put("com.codename1.charts.renderers", Integer.valueOf(28)); - m.put("com.codename1.charts.transitions", Integer.valueOf(29)); - m.put("com.codename1.charts.util", Integer.valueOf(30)); - m.put("com.codename1.charts.views", Integer.valueOf(31)); - m.put("com.codename1.cloud", Integer.valueOf(32)); - m.put("com.codename1.codescan", Integer.valueOf(33)); - m.put("com.codename1.compat.java.util", Integer.valueOf(34)); - m.put("com.codename1.components", Integer.valueOf(35)); - m.put("com.codename1.contacts", Integer.valueOf(36)); - m.put("com.codename1.crash", Integer.valueOf(37)); - m.put("com.codename1.db", Integer.valueOf(38)); - m.put("com.codename1.facebook", Integer.valueOf(39)); - m.put("com.codename1.facebook.ui", Integer.valueOf(40)); - m.put("com.codename1.gaming", Integer.valueOf(41)); - m.put("com.codename1.gaming.level", Integer.valueOf(42)); - m.put("com.codename1.gaming.physics", Integer.valueOf(43)); - m.put("com.codename1.gaming.physics.box2d.callbacks", Integer.valueOf(44)); - m.put("com.codename1.gaming.physics.box2d.collision", Integer.valueOf(45)); - m.put("com.codename1.gaming.physics.box2d.collision.broadphase", Integer.valueOf(46)); - m.put("com.codename1.gaming.physics.box2d.collision.shapes", Integer.valueOf(47)); - m.put("com.codename1.gaming.physics.box2d.common", Integer.valueOf(48)); - m.put("com.codename1.gaming.physics.box2d.dynamics", Integer.valueOf(49)); - m.put("com.codename1.gaming.physics.box2d.dynamics.contacts", Integer.valueOf(50)); - m.put("com.codename1.gaming.physics.box2d.dynamics.joints", Integer.valueOf(51)); - m.put("com.codename1.gaming.physics.box2d.pooling", Integer.valueOf(52)); - m.put("com.codename1.gaming.physics.box2d.pooling.arrays", Integer.valueOf(53)); - m.put("com.codename1.gaming.physics.box2d.pooling.normal", Integer.valueOf(54)); - m.put("com.codename1.gaming.physics.box2d.pooling.stacks", Integer.valueOf(55)); - m.put("com.codename1.gpu", Integer.valueOf(56)); - m.put("com.codename1.health", Integer.valueOf(57)); - m.put("com.codename1.health.nutrition", Integer.valueOf(58)); - m.put("com.codename1.health.sensors", Integer.valueOf(59)); - m.put("com.codename1.health.workout", Integer.valueOf(60)); - m.put("com.codename1.io", Integer.valueOf(61)); - m.put("com.codename1.io.bonjour", Integer.valueOf(62)); - m.put("com.codename1.io.graphql", Integer.valueOf(63)); - m.put("com.codename1.io.grpc", Integer.valueOf(64)); - m.put("com.codename1.io.gzip", Integer.valueOf(65)); - m.put("com.codename1.io.oidc", Integer.valueOf(66)); - m.put("com.codename1.io.rest", Integer.valueOf(67)); - m.put("com.codename1.io.services", Integer.valueOf(68)); - m.put("com.codename1.io.tar", Integer.valueOf(69)); - m.put("com.codename1.io.usb", Integer.valueOf(70)); - m.put("com.codename1.io.webauthn", Integer.valueOf(71)); - m.put("com.codename1.io.wifi", Integer.valueOf(72)); - m.put("com.codename1.javascript", Integer.valueOf(73)); - m.put("com.codename1.l10n", Integer.valueOf(74)); - m.put("com.codename1.location", Integer.valueOf(75)); - m.put("com.codename1.mapping", Integer.valueOf(76)); - m.put("com.codename1.maps", Integer.valueOf(77)); - m.put("com.codename1.maps.layers", Integer.valueOf(78)); - m.put("com.codename1.maps.providers", Integer.valueOf(79)); - m.put("com.codename1.maps.routing", Integer.valueOf(80)); - m.put("com.codename1.maps.spi", Integer.valueOf(81)); - m.put("com.codename1.maps.vector", Integer.valueOf(82)); - m.put("com.codename1.mcp", Integer.valueOf(83)); - m.put("com.codename1.media", Integer.valueOf(84)); - m.put("com.codename1.messaging", Integer.valueOf(85)); - m.put("com.codename1.nfc", Integer.valueOf(86)); - m.put("com.codename1.notifications", Integer.valueOf(87)); - m.put("com.codename1.orm", Integer.valueOf(88)); - m.put("com.codename1.payment", Integer.valueOf(89)); - m.put("com.codename1.plugin", Integer.valueOf(90)); - m.put("com.codename1.plugin.event", Integer.valueOf(91)); - m.put("com.codename1.printing", Integer.valueOf(92)); - m.put("com.codename1.processing", Integer.valueOf(93)); - m.put("com.codename1.properties", Integer.valueOf(94)); - m.put("com.codename1.push", Integer.valueOf(95)); - m.put("com.codename1.router", Integer.valueOf(96)); - m.put("com.codename1.security", Integer.valueOf(97)); - m.put("com.codename1.security.shield", Integer.valueOf(98)); - m.put("com.codename1.security.shield.spi", Integer.valueOf(99)); - m.put("com.codename1.sensors", Integer.valueOf(100)); - m.put("com.codename1.share", Integer.valueOf(101)); - m.put("com.codename1.social", Integer.valueOf(102)); - m.put("com.codename1.surfaces", Integer.valueOf(103)); - m.put("com.codename1.surfaces.spi", Integer.valueOf(104)); - m.put("com.codename1.system", Integer.valueOf(105)); - m.put("com.codename1.testing", Integer.valueOf(106)); - m.put("com.codename1.ui", Integer.valueOf(107)); - m.put("com.codename1.ui.accessibility", Integer.valueOf(108)); - m.put("com.codename1.ui.animations", Integer.valueOf(109)); - m.put("com.codename1.ui.css", Integer.valueOf(110)); - m.put("com.codename1.ui.editor", Integer.valueOf(111)); - m.put("com.codename1.ui.events", Integer.valueOf(112)); - m.put("com.codename1.ui.geom", Integer.valueOf(113)); - m.put("com.codename1.ui.html", Integer.valueOf(114)); - m.put("com.codename1.ui.layouts", Integer.valueOf(115)); - m.put("com.codename1.ui.layouts.mig", Integer.valueOf(116)); - m.put("com.codename1.ui.list", Integer.valueOf(117)); - m.put("com.codename1.ui.painter", Integer.valueOf(118)); - m.put("com.codename1.ui.plaf", Integer.valueOf(119)); - m.put("com.codename1.ui.scene", Integer.valueOf(120)); - m.put("com.codename1.ui.spinner", Integer.valueOf(121)); - m.put("com.codename1.ui.table", Integer.valueOf(122)); - m.put("com.codename1.ui.tree", Integer.valueOf(123)); - m.put("com.codename1.ui.util", Integer.valueOf(124)); - m.put("com.codename1.ui.validation", Integer.valueOf(125)); - m.put("com.codename1.util", Integer.valueOf(126)); - m.put("com.codename1.util.promise", Integer.valueOf(127)); - m.put("com.codename1.util.regex", Integer.valueOf(128)); - m.put("com.codename1.vr", Integer.valueOf(129)); - m.put("com.codename1.wearable", Integer.valueOf(130)); - m.put("com.codename1.wearable.spi", Integer.valueOf(131)); - m.put("com.codename1.xml", Integer.valueOf(132)); - m.put("com.codenameone.playground", Integer.valueOf(133)); - m.put("java.io", Integer.valueOf(134)); - m.put("java.lang", Integer.valueOf(135)); - m.put("java.lang.ref", Integer.valueOf(136)); - m.put("java.lang.reflect", Integer.valueOf(137)); - m.put("java.net", Integer.valueOf(138)); - m.put("java.nio.charset", Integer.valueOf(139)); - m.put("java.text", Integer.valueOf(140)); - m.put("java.time", Integer.valueOf(141)); - m.put("java.time.format", Integer.valueOf(142)); - m.put("java.time.temporal", Integer.valueOf(143)); - m.put("java.util", Integer.valueOf(144)); - m.put("java.util.concurrent", Integer.valueOf(145)); - m.put("java.util.concurrent.atomic", Integer.valueOf(146)); - m.put("java.util.function", Integer.valueOf(147)); - m.put("java.util.stream", Integer.valueOf(148)); + m.put("com.codename1.annotations.buildhints", Integer.valueOf(8)); + m.put("com.codename1.annotations.graphql", Integer.valueOf(9)); + m.put("com.codename1.annotations.grpc", Integer.valueOf(10)); + m.put("com.codename1.annotations.rest", Integer.valueOf(11)); + m.put("com.codename1.appreview", Integer.valueOf(12)); + m.put("com.codename1.ar", Integer.valueOf(13)); + m.put("com.codename1.background", Integer.valueOf(14)); + m.put("com.codename1.binding", Integer.valueOf(15)); + m.put("com.codename1.bluetooth", Integer.valueOf(16)); + m.put("com.codename1.bluetooth.classic", Integer.valueOf(17)); + m.put("com.codename1.bluetooth.gatt", Integer.valueOf(18)); + m.put("com.codename1.bluetooth.le", Integer.valueOf(19)); + m.put("com.codename1.bluetooth.le.server", Integer.valueOf(20)); + m.put("com.codename1.calendar", Integer.valueOf(21)); + m.put("com.codename1.camera", Integer.valueOf(22)); + m.put("com.codename1.capture", Integer.valueOf(23)); + m.put("com.codename1.car", Integer.valueOf(24)); + m.put("com.codename1.car.spi", Integer.valueOf(25)); + m.put("com.codename1.charts", Integer.valueOf(26)); + m.put("com.codename1.charts.compat", Integer.valueOf(27)); + m.put("com.codename1.charts.models", Integer.valueOf(28)); + m.put("com.codename1.charts.renderers", Integer.valueOf(29)); + m.put("com.codename1.charts.transitions", Integer.valueOf(30)); + m.put("com.codename1.charts.util", Integer.valueOf(31)); + m.put("com.codename1.charts.views", Integer.valueOf(32)); + m.put("com.codename1.cloud", Integer.valueOf(33)); + m.put("com.codename1.codescan", Integer.valueOf(34)); + m.put("com.codename1.compat.java.util", Integer.valueOf(35)); + m.put("com.codename1.components", Integer.valueOf(36)); + m.put("com.codename1.contacts", Integer.valueOf(37)); + m.put("com.codename1.crash", Integer.valueOf(38)); + m.put("com.codename1.db", Integer.valueOf(39)); + m.put("com.codename1.facebook", Integer.valueOf(40)); + m.put("com.codename1.facebook.ui", Integer.valueOf(41)); + m.put("com.codename1.gaming", Integer.valueOf(42)); + m.put("com.codename1.gaming.level", Integer.valueOf(43)); + m.put("com.codename1.gaming.physics", Integer.valueOf(44)); + m.put("com.codename1.gaming.physics.box2d.callbacks", Integer.valueOf(45)); + m.put("com.codename1.gaming.physics.box2d.collision", Integer.valueOf(46)); + m.put("com.codename1.gaming.physics.box2d.collision.broadphase", Integer.valueOf(47)); + m.put("com.codename1.gaming.physics.box2d.collision.shapes", Integer.valueOf(48)); + m.put("com.codename1.gaming.physics.box2d.common", Integer.valueOf(49)); + m.put("com.codename1.gaming.physics.box2d.dynamics", Integer.valueOf(50)); + m.put("com.codename1.gaming.physics.box2d.dynamics.contacts", Integer.valueOf(51)); + m.put("com.codename1.gaming.physics.box2d.dynamics.joints", Integer.valueOf(52)); + m.put("com.codename1.gaming.physics.box2d.pooling", Integer.valueOf(53)); + m.put("com.codename1.gaming.physics.box2d.pooling.arrays", Integer.valueOf(54)); + m.put("com.codename1.gaming.physics.box2d.pooling.normal", Integer.valueOf(55)); + m.put("com.codename1.gaming.physics.box2d.pooling.stacks", Integer.valueOf(56)); + m.put("com.codename1.gpu", Integer.valueOf(57)); + m.put("com.codename1.health", Integer.valueOf(58)); + m.put("com.codename1.health.nutrition", Integer.valueOf(59)); + m.put("com.codename1.health.sensors", Integer.valueOf(60)); + m.put("com.codename1.health.workout", Integer.valueOf(61)); + m.put("com.codename1.home", Integer.valueOf(62)); + m.put("com.codename1.home.commissioning", Integer.valueOf(63)); + m.put("com.codename1.home.spi", Integer.valueOf(64)); + m.put("com.codename1.intents", Integer.valueOf(65)); + m.put("com.codename1.intents.spi", Integer.valueOf(66)); + m.put("com.codename1.io", Integer.valueOf(67)); + m.put("com.codename1.io.bonjour", Integer.valueOf(68)); + m.put("com.codename1.io.graphql", Integer.valueOf(69)); + m.put("com.codename1.io.grpc", Integer.valueOf(70)); + m.put("com.codename1.io.gzip", Integer.valueOf(71)); + m.put("com.codename1.io.oidc", Integer.valueOf(72)); + m.put("com.codename1.io.rest", Integer.valueOf(73)); + m.put("com.codename1.io.services", Integer.valueOf(74)); + m.put("com.codename1.io.tar", Integer.valueOf(75)); + m.put("com.codename1.io.usb", Integer.valueOf(76)); + m.put("com.codename1.io.webauthn", Integer.valueOf(77)); + m.put("com.codename1.io.wifi", Integer.valueOf(78)); + m.put("com.codename1.javascript", Integer.valueOf(79)); + m.put("com.codename1.l10n", Integer.valueOf(80)); + m.put("com.codename1.location", Integer.valueOf(81)); + m.put("com.codename1.mapping", Integer.valueOf(82)); + m.put("com.codename1.maps", Integer.valueOf(83)); + m.put("com.codename1.maps.layers", Integer.valueOf(84)); + m.put("com.codename1.maps.providers", Integer.valueOf(85)); + m.put("com.codename1.maps.routing", Integer.valueOf(86)); + m.put("com.codename1.maps.spi", Integer.valueOf(87)); + m.put("com.codename1.maps.vector", Integer.valueOf(88)); + m.put("com.codename1.mcp", Integer.valueOf(89)); + m.put("com.codename1.media", Integer.valueOf(90)); + m.put("com.codename1.messaging", Integer.valueOf(91)); + m.put("com.codename1.nfc", Integer.valueOf(92)); + m.put("com.codename1.notifications", Integer.valueOf(93)); + m.put("com.codename1.orm", Integer.valueOf(94)); + m.put("com.codename1.payment", Integer.valueOf(95)); + m.put("com.codename1.plugin", Integer.valueOf(96)); + m.put("com.codename1.plugin.event", Integer.valueOf(97)); + m.put("com.codename1.printing", Integer.valueOf(98)); + m.put("com.codename1.processing", Integer.valueOf(99)); + m.put("com.codename1.properties", Integer.valueOf(100)); + m.put("com.codename1.push", Integer.valueOf(101)); + m.put("com.codename1.router", Integer.valueOf(102)); + m.put("com.codename1.security", Integer.valueOf(103)); + m.put("com.codename1.security.hardening", Integer.valueOf(104)); + m.put("com.codename1.security.shield", Integer.valueOf(105)); + m.put("com.codename1.security.shield.spi", Integer.valueOf(106)); + m.put("com.codename1.sensors", Integer.valueOf(107)); + m.put("com.codename1.share", Integer.valueOf(108)); + m.put("com.codename1.social", Integer.valueOf(109)); + m.put("com.codename1.surfaces", Integer.valueOf(110)); + m.put("com.codename1.surfaces.spi", Integer.valueOf(111)); + m.put("com.codename1.system", Integer.valueOf(112)); + m.put("com.codename1.testing", Integer.valueOf(113)); + m.put("com.codename1.ui", Integer.valueOf(114)); + m.put("com.codename1.ui.accessibility", Integer.valueOf(115)); + m.put("com.codename1.ui.animations", Integer.valueOf(116)); + m.put("com.codename1.ui.css", Integer.valueOf(117)); + m.put("com.codename1.ui.editor", Integer.valueOf(118)); + m.put("com.codename1.ui.events", Integer.valueOf(119)); + m.put("com.codename1.ui.geom", Integer.valueOf(120)); + m.put("com.codename1.ui.html", Integer.valueOf(121)); + m.put("com.codename1.ui.layouts", Integer.valueOf(122)); + m.put("com.codename1.ui.layouts.mig", Integer.valueOf(123)); + m.put("com.codename1.ui.list", Integer.valueOf(124)); + m.put("com.codename1.ui.painter", Integer.valueOf(125)); + m.put("com.codename1.ui.plaf", Integer.valueOf(126)); + m.put("com.codename1.ui.scene", Integer.valueOf(127)); + m.put("com.codename1.ui.spinner", Integer.valueOf(128)); + m.put("com.codename1.ui.table", Integer.valueOf(129)); + m.put("com.codename1.ui.tree", Integer.valueOf(130)); + m.put("com.codename1.ui.util", Integer.valueOf(131)); + m.put("com.codename1.ui.validation", Integer.valueOf(132)); + m.put("com.codename1.util", Integer.valueOf(133)); + m.put("com.codename1.util.promise", Integer.valueOf(134)); + m.put("com.codename1.util.regex", Integer.valueOf(135)); + m.put("com.codename1.vr", Integer.valueOf(136)); + m.put("com.codename1.wearable", Integer.valueOf(137)); + m.put("com.codename1.wearable.spi", Integer.valueOf(138)); + m.put("com.codename1.xml", Integer.valueOf(139)); + m.put("com.codenameone.playground", Integer.valueOf(140)); + m.put("java.io", Integer.valueOf(141)); + m.put("java.lang", Integer.valueOf(142)); + m.put("java.lang.ref", Integer.valueOf(143)); + m.put("java.lang.reflect", Integer.valueOf(144)); + m.put("java.net", Integer.valueOf(145)); + m.put("java.nio.charset", Integer.valueOf(146)); + m.put("java.text", Integer.valueOf(147)); + m.put("java.time", Integer.valueOf(148)); + m.put("java.time.format", Integer.valueOf(149)); + m.put("java.time.temporal", Integer.valueOf(150)); + m.put("java.util", Integer.valueOf(151)); + m.put("java.util.concurrent", Integer.valueOf(152)); + m.put("java.util.concurrent.atomic", Integer.valueOf(153)); + m.put("java.util.function", Integer.valueOf(154)); + m.put("java.util.stream", Integer.valueOf(155)); return m; } - private static final int PACKAGE_HANDLER_COUNT = 149; + private static final int PACKAGE_HANDLER_COUNT = 156; @Override public Object invoke(Object target, String name, Object[] args) throws Exception { @@ -7594,147 +7996,154 @@ private static Object dispatchInstance(int __idx, Object target, String name, Ob case 5: return GeneratedAccess_com_codename1_ai_vision.invoke(target, name, args); case 6: return GeneratedAccess_com_codename1_analytics.invoke(target, name, args); case 7: return GeneratedAccess_com_codename1_annotations.invoke(target, name, args); - case 8: return GeneratedAccess_com_codename1_annotations_graphql.invoke(target, name, args); - case 9: return GeneratedAccess_com_codename1_annotations_grpc.invoke(target, name, args); - case 10: return GeneratedAccess_com_codename1_annotations_rest.invoke(target, name, args); - case 11: return GeneratedAccess_com_codename1_appreview.invoke(target, name, args); - case 12: return GeneratedAccess_com_codename1_ar.invoke(target, name, args); - case 13: return GeneratedAccess_com_codename1_background.invoke(target, name, args); - case 14: return GeneratedAccess_com_codename1_binding.invoke(target, name, args); - case 15: return GeneratedAccess_com_codename1_bluetooth.invoke(target, name, args); - case 16: return GeneratedAccess_com_codename1_bluetooth_classic.invoke(target, name, args); - case 17: return GeneratedAccess_com_codename1_bluetooth_gatt.invoke(target, name, args); - case 18: return GeneratedAccess_com_codename1_bluetooth_le.invoke(target, name, args); - case 19: return GeneratedAccess_com_codename1_bluetooth_le_server.invoke(target, name, args); - case 20: return GeneratedAccess_com_codename1_calendar.invoke(target, name, args); - case 21: return GeneratedAccess_com_codename1_camera.invoke(target, name, args); - case 22: return GeneratedAccess_com_codename1_capture.invoke(target, name, args); - case 23: return GeneratedAccess_com_codename1_car.invoke(target, name, args); - case 24: return GeneratedAccess_com_codename1_car_spi.invoke(target, name, args); - case 25: return GeneratedAccess_com_codename1_charts.invoke(target, name, args); - case 26: return GeneratedAccess_com_codename1_charts_compat.invoke(target, name, args); - case 27: return GeneratedAccess_com_codename1_charts_models.invoke(target, name, args); - case 28: return GeneratedAccess_com_codename1_charts_renderers.invoke(target, name, args); - case 29: return GeneratedAccess_com_codename1_charts_transitions.invoke(target, name, args); - case 30: return GeneratedAccess_com_codename1_charts_util.invoke(target, name, args); - case 31: return GeneratedAccess_com_codename1_charts_views.invoke(target, name, args); - case 32: return GeneratedAccess_com_codename1_cloud.invoke(target, name, args); - case 33: return GeneratedAccess_com_codename1_codescan.invoke(target, name, args); - case 34: return GeneratedAccess_com_codename1_compat_java_util.invoke(target, name, args); - case 35: return GeneratedAccess_com_codename1_components.invoke(target, name, args); - case 36: return GeneratedAccess_com_codename1_contacts.invoke(target, name, args); - case 37: return GeneratedAccess_com_codename1_crash.invoke(target, name, args); - case 38: return GeneratedAccess_com_codename1_db.invoke(target, name, args); - case 39: return GeneratedAccess_com_codename1_facebook.invoke(target, name, args); - case 40: return GeneratedAccess_com_codename1_facebook_ui.invoke(target, name, args); - case 41: return GeneratedAccess_com_codename1_gaming.invoke(target, name, args); - case 42: return GeneratedAccess_com_codename1_gaming_level.invoke(target, name, args); - case 43: return GeneratedAccess_com_codename1_gaming_physics.invoke(target, name, args); - case 44: return GeneratedAccess_com_codename1_gaming_physics_box2d_callbacks.invoke(target, name, args); - case 45: return GeneratedAccess_com_codename1_gaming_physics_box2d_collision.invoke(target, name, args); - case 46: return GeneratedAccess_com_codename1_gaming_physics_box2d_collision_broadphase.invoke(target, name, args); - case 47: return GeneratedAccess_com_codename1_gaming_physics_box2d_collision_shapes.invoke(target, name, args); - case 48: return GeneratedAccess_com_codename1_gaming_physics_box2d_common.invoke(target, name, args); - case 49: return GeneratedAccess_com_codename1_gaming_physics_box2d_dynamics.invoke(target, name, args); - case 50: return GeneratedAccess_com_codename1_gaming_physics_box2d_dynamics_contacts.invoke(target, name, args); - case 51: return GeneratedAccess_com_codename1_gaming_physics_box2d_dynamics_joints.invoke(target, name, args); - case 52: return GeneratedAccess_com_codename1_gaming_physics_box2d_pooling.invoke(target, name, args); - case 53: return GeneratedAccess_com_codename1_gaming_physics_box2d_pooling_arrays.invoke(target, name, args); - case 54: return GeneratedAccess_com_codename1_gaming_physics_box2d_pooling_normal.invoke(target, name, args); - case 55: return GeneratedAccess_com_codename1_gaming_physics_box2d_pooling_stacks.invoke(target, name, args); - case 56: return GeneratedAccess_com_codename1_gpu.invoke(target, name, args); - case 57: return GeneratedAccess_com_codename1_health.invoke(target, name, args); - case 58: return GeneratedAccess_com_codename1_health_nutrition.invoke(target, name, args); - case 59: return GeneratedAccess_com_codename1_health_sensors.invoke(target, name, args); - case 60: return GeneratedAccess_com_codename1_health_workout.invoke(target, name, args); - case 61: return GeneratedAccess_com_codename1_io.invoke(target, name, args); - case 62: return GeneratedAccess_com_codename1_io_bonjour.invoke(target, name, args); - case 63: return GeneratedAccess_com_codename1_io_graphql.invoke(target, name, args); - case 64: return GeneratedAccess_com_codename1_io_grpc.invoke(target, name, args); - case 65: return GeneratedAccess_com_codename1_io_gzip.invoke(target, name, args); - case 66: return GeneratedAccess_com_codename1_io_oidc.invoke(target, name, args); - case 67: return GeneratedAccess_com_codename1_io_rest.invoke(target, name, args); - case 68: return GeneratedAccess_com_codename1_io_services.invoke(target, name, args); - case 69: return GeneratedAccess_com_codename1_io_tar.invoke(target, name, args); - case 70: return GeneratedAccess_com_codename1_io_usb.invoke(target, name, args); - case 71: return GeneratedAccess_com_codename1_io_webauthn.invoke(target, name, args); - case 72: return GeneratedAccess_com_codename1_io_wifi.invoke(target, name, args); - case 73: return GeneratedAccess_com_codename1_javascript.invoke(target, name, args); - case 74: return GeneratedAccess_com_codename1_l10n.invoke(target, name, args); - case 75: return GeneratedAccess_com_codename1_location.invoke(target, name, args); - case 76: return GeneratedAccess_com_codename1_mapping.invoke(target, name, args); - case 77: return GeneratedAccess_com_codename1_maps.invoke(target, name, args); - case 78: return GeneratedAccess_com_codename1_maps_layers.invoke(target, name, args); - case 79: return GeneratedAccess_com_codename1_maps_providers.invoke(target, name, args); - case 80: return GeneratedAccess_com_codename1_maps_routing.invoke(target, name, args); - case 81: return GeneratedAccess_com_codename1_maps_spi.invoke(target, name, args); - case 82: return GeneratedAccess_com_codename1_maps_vector.invoke(target, name, args); - case 83: return GeneratedAccess_com_codename1_mcp.invoke(target, name, args); - case 84: return GeneratedAccess_com_codename1_media.invoke(target, name, args); - case 85: return GeneratedAccess_com_codename1_messaging.invoke(target, name, args); - case 86: return GeneratedAccess_com_codename1_nfc.invoke(target, name, args); - case 87: return GeneratedAccess_com_codename1_notifications.invoke(target, name, args); - case 88: return GeneratedAccess_com_codename1_orm.invoke(target, name, args); - case 89: return GeneratedAccess_com_codename1_payment.invoke(target, name, args); - case 90: return GeneratedAccess_com_codename1_plugin.invoke(target, name, args); - case 91: return GeneratedAccess_com_codename1_plugin_event.invoke(target, name, args); - case 92: return GeneratedAccess_com_codename1_printing.invoke(target, name, args); - case 93: return GeneratedAccess_com_codename1_processing.invoke(target, name, args); - case 94: return GeneratedAccess_com_codename1_properties.invoke(target, name, args); - case 95: return GeneratedAccess_com_codename1_push.invoke(target, name, args); - case 96: return GeneratedAccess_com_codename1_router.invoke(target, name, args); - case 97: return GeneratedAccess_com_codename1_security.invoke(target, name, args); - case 98: return GeneratedAccess_com_codename1_security_shield.invoke(target, name, args); - case 99: return GeneratedAccess_com_codename1_security_shield_spi.invoke(target, name, args); - case 100: return GeneratedAccess_com_codename1_sensors.invoke(target, name, args); - case 101: return GeneratedAccess_com_codename1_share.invoke(target, name, args); - case 102: return GeneratedAccess_com_codename1_social.invoke(target, name, args); - case 103: return GeneratedAccess_com_codename1_surfaces.invoke(target, name, args); - case 104: return GeneratedAccess_com_codename1_surfaces_spi.invoke(target, name, args); - case 105: return GeneratedAccess_com_codename1_system.invoke(target, name, args); - case 106: return GeneratedAccess_com_codename1_testing.invoke(target, name, args); - case 107: return GeneratedAccess_com_codename1_ui.invoke(target, name, args); - case 108: return GeneratedAccess_com_codename1_ui_accessibility.invoke(target, name, args); - case 109: return GeneratedAccess_com_codename1_ui_animations.invoke(target, name, args); - case 110: return GeneratedAccess_com_codename1_ui_css.invoke(target, name, args); - case 111: return GeneratedAccess_com_codename1_ui_editor.invoke(target, name, args); - case 112: return GeneratedAccess_com_codename1_ui_events.invoke(target, name, args); - case 113: return GeneratedAccess_com_codename1_ui_geom.invoke(target, name, args); - case 114: return GeneratedAccess_com_codename1_ui_html.invoke(target, name, args); - case 115: return GeneratedAccess_com_codename1_ui_layouts.invoke(target, name, args); - case 116: return GeneratedAccess_com_codename1_ui_layouts_mig.invoke(target, name, args); - case 117: return GeneratedAccess_com_codename1_ui_list.invoke(target, name, args); - case 118: return GeneratedAccess_com_codename1_ui_painter.invoke(target, name, args); - case 119: return GeneratedAccess_com_codename1_ui_plaf.invoke(target, name, args); - case 120: return GeneratedAccess_com_codename1_ui_scene.invoke(target, name, args); - case 121: return GeneratedAccess_com_codename1_ui_spinner.invoke(target, name, args); - case 122: return GeneratedAccess_com_codename1_ui_table.invoke(target, name, args); - case 123: return GeneratedAccess_com_codename1_ui_tree.invoke(target, name, args); - case 124: return GeneratedAccess_com_codename1_ui_util.invoke(target, name, args); - case 125: return GeneratedAccess_com_codename1_ui_validation.invoke(target, name, args); - case 126: return GeneratedAccess_com_codename1_util.invoke(target, name, args); - case 127: return GeneratedAccess_com_codename1_util_promise.invoke(target, name, args); - case 128: return GeneratedAccess_com_codename1_util_regex.invoke(target, name, args); - case 129: return GeneratedAccess_com_codename1_vr.invoke(target, name, args); - case 130: return GeneratedAccess_com_codename1_wearable.invoke(target, name, args); - case 131: return GeneratedAccess_com_codename1_wearable_spi.invoke(target, name, args); - case 132: return GeneratedAccess_com_codename1_xml.invoke(target, name, args); - case 133: return GeneratedAccess_com_codenameone_playground.invoke(target, name, args); - case 134: return GeneratedAccess_java_io.invoke(target, name, args); - case 135: return GeneratedAccess_java_lang.invoke(target, name, args); - case 136: return GeneratedAccess_java_lang_ref.invoke(target, name, args); - case 137: return GeneratedAccess_java_lang_reflect.invoke(target, name, args); - case 138: return GeneratedAccess_java_net.invoke(target, name, args); - case 139: return GeneratedAccess_java_nio_charset.invoke(target, name, args); - case 140: return GeneratedAccess_java_text.invoke(target, name, args); - case 141: return GeneratedAccess_java_time.invoke(target, name, args); - case 142: return GeneratedAccess_java_time_format.invoke(target, name, args); - case 143: return GeneratedAccess_java_time_temporal.invoke(target, name, args); - case 144: return GeneratedAccess_java_util.invoke(target, name, args); - case 145: return GeneratedAccess_java_util_concurrent.invoke(target, name, args); - case 146: return GeneratedAccess_java_util_concurrent_atomic.invoke(target, name, args); - case 147: return GeneratedAccess_java_util_function.invoke(target, name, args); - case 148: return GeneratedAccess_java_util_stream.invoke(target, name, args); + case 8: return GeneratedAccess_com_codename1_annotations_buildhints.invoke(target, name, args); + case 9: return GeneratedAccess_com_codename1_annotations_graphql.invoke(target, name, args); + case 10: return GeneratedAccess_com_codename1_annotations_grpc.invoke(target, name, args); + case 11: return GeneratedAccess_com_codename1_annotations_rest.invoke(target, name, args); + case 12: return GeneratedAccess_com_codename1_appreview.invoke(target, name, args); + case 13: return GeneratedAccess_com_codename1_ar.invoke(target, name, args); + case 14: return GeneratedAccess_com_codename1_background.invoke(target, name, args); + case 15: return GeneratedAccess_com_codename1_binding.invoke(target, name, args); + case 16: return GeneratedAccess_com_codename1_bluetooth.invoke(target, name, args); + case 17: return GeneratedAccess_com_codename1_bluetooth_classic.invoke(target, name, args); + case 18: return GeneratedAccess_com_codename1_bluetooth_gatt.invoke(target, name, args); + case 19: return GeneratedAccess_com_codename1_bluetooth_le.invoke(target, name, args); + case 20: return GeneratedAccess_com_codename1_bluetooth_le_server.invoke(target, name, args); + case 21: return GeneratedAccess_com_codename1_calendar.invoke(target, name, args); + case 22: return GeneratedAccess_com_codename1_camera.invoke(target, name, args); + case 23: return GeneratedAccess_com_codename1_capture.invoke(target, name, args); + case 24: return GeneratedAccess_com_codename1_car.invoke(target, name, args); + case 25: return GeneratedAccess_com_codename1_car_spi.invoke(target, name, args); + case 26: return GeneratedAccess_com_codename1_charts.invoke(target, name, args); + case 27: return GeneratedAccess_com_codename1_charts_compat.invoke(target, name, args); + case 28: return GeneratedAccess_com_codename1_charts_models.invoke(target, name, args); + case 29: return GeneratedAccess_com_codename1_charts_renderers.invoke(target, name, args); + case 30: return GeneratedAccess_com_codename1_charts_transitions.invoke(target, name, args); + case 31: return GeneratedAccess_com_codename1_charts_util.invoke(target, name, args); + case 32: return GeneratedAccess_com_codename1_charts_views.invoke(target, name, args); + case 33: return GeneratedAccess_com_codename1_cloud.invoke(target, name, args); + case 34: return GeneratedAccess_com_codename1_codescan.invoke(target, name, args); + case 35: return GeneratedAccess_com_codename1_compat_java_util.invoke(target, name, args); + case 36: return GeneratedAccess_com_codename1_components.invoke(target, name, args); + case 37: return GeneratedAccess_com_codename1_contacts.invoke(target, name, args); + case 38: return GeneratedAccess_com_codename1_crash.invoke(target, name, args); + case 39: return GeneratedAccess_com_codename1_db.invoke(target, name, args); + case 40: return GeneratedAccess_com_codename1_facebook.invoke(target, name, args); + case 41: return GeneratedAccess_com_codename1_facebook_ui.invoke(target, name, args); + case 42: return GeneratedAccess_com_codename1_gaming.invoke(target, name, args); + case 43: return GeneratedAccess_com_codename1_gaming_level.invoke(target, name, args); + case 44: return GeneratedAccess_com_codename1_gaming_physics.invoke(target, name, args); + case 45: return GeneratedAccess_com_codename1_gaming_physics_box2d_callbacks.invoke(target, name, args); + case 46: return GeneratedAccess_com_codename1_gaming_physics_box2d_collision.invoke(target, name, args); + case 47: return GeneratedAccess_com_codename1_gaming_physics_box2d_collision_broadphase.invoke(target, name, args); + case 48: return GeneratedAccess_com_codename1_gaming_physics_box2d_collision_shapes.invoke(target, name, args); + case 49: return GeneratedAccess_com_codename1_gaming_physics_box2d_common.invoke(target, name, args); + case 50: return GeneratedAccess_com_codename1_gaming_physics_box2d_dynamics.invoke(target, name, args); + case 51: return GeneratedAccess_com_codename1_gaming_physics_box2d_dynamics_contacts.invoke(target, name, args); + case 52: return GeneratedAccess_com_codename1_gaming_physics_box2d_dynamics_joints.invoke(target, name, args); + case 53: return GeneratedAccess_com_codename1_gaming_physics_box2d_pooling.invoke(target, name, args); + case 54: return GeneratedAccess_com_codename1_gaming_physics_box2d_pooling_arrays.invoke(target, name, args); + case 55: return GeneratedAccess_com_codename1_gaming_physics_box2d_pooling_normal.invoke(target, name, args); + case 56: return GeneratedAccess_com_codename1_gaming_physics_box2d_pooling_stacks.invoke(target, name, args); + case 57: return GeneratedAccess_com_codename1_gpu.invoke(target, name, args); + case 58: return GeneratedAccess_com_codename1_health.invoke(target, name, args); + case 59: return GeneratedAccess_com_codename1_health_nutrition.invoke(target, name, args); + case 60: return GeneratedAccess_com_codename1_health_sensors.invoke(target, name, args); + case 61: return GeneratedAccess_com_codename1_health_workout.invoke(target, name, args); + case 62: return GeneratedAccess_com_codename1_home.invoke(target, name, args); + case 63: return GeneratedAccess_com_codename1_home_commissioning.invoke(target, name, args); + case 64: return GeneratedAccess_com_codename1_home_spi.invoke(target, name, args); + case 65: return GeneratedAccess_com_codename1_intents.invoke(target, name, args); + case 66: return GeneratedAccess_com_codename1_intents_spi.invoke(target, name, args); + case 67: return GeneratedAccess_com_codename1_io.invoke(target, name, args); + case 68: return GeneratedAccess_com_codename1_io_bonjour.invoke(target, name, args); + case 69: return GeneratedAccess_com_codename1_io_graphql.invoke(target, name, args); + case 70: return GeneratedAccess_com_codename1_io_grpc.invoke(target, name, args); + case 71: return GeneratedAccess_com_codename1_io_gzip.invoke(target, name, args); + case 72: return GeneratedAccess_com_codename1_io_oidc.invoke(target, name, args); + case 73: return GeneratedAccess_com_codename1_io_rest.invoke(target, name, args); + case 74: return GeneratedAccess_com_codename1_io_services.invoke(target, name, args); + case 75: return GeneratedAccess_com_codename1_io_tar.invoke(target, name, args); + case 76: return GeneratedAccess_com_codename1_io_usb.invoke(target, name, args); + case 77: return GeneratedAccess_com_codename1_io_webauthn.invoke(target, name, args); + case 78: return GeneratedAccess_com_codename1_io_wifi.invoke(target, name, args); + case 79: return GeneratedAccess_com_codename1_javascript.invoke(target, name, args); + case 80: return GeneratedAccess_com_codename1_l10n.invoke(target, name, args); + case 81: return GeneratedAccess_com_codename1_location.invoke(target, name, args); + case 82: return GeneratedAccess_com_codename1_mapping.invoke(target, name, args); + case 83: return GeneratedAccess_com_codename1_maps.invoke(target, name, args); + case 84: return GeneratedAccess_com_codename1_maps_layers.invoke(target, name, args); + case 85: return GeneratedAccess_com_codename1_maps_providers.invoke(target, name, args); + case 86: return GeneratedAccess_com_codename1_maps_routing.invoke(target, name, args); + case 87: return GeneratedAccess_com_codename1_maps_spi.invoke(target, name, args); + case 88: return GeneratedAccess_com_codename1_maps_vector.invoke(target, name, args); + case 89: return GeneratedAccess_com_codename1_mcp.invoke(target, name, args); + case 90: return GeneratedAccess_com_codename1_media.invoke(target, name, args); + case 91: return GeneratedAccess_com_codename1_messaging.invoke(target, name, args); + case 92: return GeneratedAccess_com_codename1_nfc.invoke(target, name, args); + case 93: return GeneratedAccess_com_codename1_notifications.invoke(target, name, args); + case 94: return GeneratedAccess_com_codename1_orm.invoke(target, name, args); + case 95: return GeneratedAccess_com_codename1_payment.invoke(target, name, args); + case 96: return GeneratedAccess_com_codename1_plugin.invoke(target, name, args); + case 97: return GeneratedAccess_com_codename1_plugin_event.invoke(target, name, args); + case 98: return GeneratedAccess_com_codename1_printing.invoke(target, name, args); + case 99: return GeneratedAccess_com_codename1_processing.invoke(target, name, args); + case 100: return GeneratedAccess_com_codename1_properties.invoke(target, name, args); + case 101: return GeneratedAccess_com_codename1_push.invoke(target, name, args); + case 102: return GeneratedAccess_com_codename1_router.invoke(target, name, args); + case 103: return GeneratedAccess_com_codename1_security.invoke(target, name, args); + case 104: return GeneratedAccess_com_codename1_security_hardening.invoke(target, name, args); + case 105: return GeneratedAccess_com_codename1_security_shield.invoke(target, name, args); + case 106: return GeneratedAccess_com_codename1_security_shield_spi.invoke(target, name, args); + case 107: return GeneratedAccess_com_codename1_sensors.invoke(target, name, args); + case 108: return GeneratedAccess_com_codename1_share.invoke(target, name, args); + case 109: return GeneratedAccess_com_codename1_social.invoke(target, name, args); + case 110: return GeneratedAccess_com_codename1_surfaces.invoke(target, name, args); + case 111: return GeneratedAccess_com_codename1_surfaces_spi.invoke(target, name, args); + case 112: return GeneratedAccess_com_codename1_system.invoke(target, name, args); + case 113: return GeneratedAccess_com_codename1_testing.invoke(target, name, args); + case 114: return GeneratedAccess_com_codename1_ui.invoke(target, name, args); + case 115: return GeneratedAccess_com_codename1_ui_accessibility.invoke(target, name, args); + case 116: return GeneratedAccess_com_codename1_ui_animations.invoke(target, name, args); + case 117: return GeneratedAccess_com_codename1_ui_css.invoke(target, name, args); + case 118: return GeneratedAccess_com_codename1_ui_editor.invoke(target, name, args); + case 119: return GeneratedAccess_com_codename1_ui_events.invoke(target, name, args); + case 120: return GeneratedAccess_com_codename1_ui_geom.invoke(target, name, args); + case 121: return GeneratedAccess_com_codename1_ui_html.invoke(target, name, args); + case 122: return GeneratedAccess_com_codename1_ui_layouts.invoke(target, name, args); + case 123: return GeneratedAccess_com_codename1_ui_layouts_mig.invoke(target, name, args); + case 124: return GeneratedAccess_com_codename1_ui_list.invoke(target, name, args); + case 125: return GeneratedAccess_com_codename1_ui_painter.invoke(target, name, args); + case 126: return GeneratedAccess_com_codename1_ui_plaf.invoke(target, name, args); + case 127: return GeneratedAccess_com_codename1_ui_scene.invoke(target, name, args); + case 128: return GeneratedAccess_com_codename1_ui_spinner.invoke(target, name, args); + case 129: return GeneratedAccess_com_codename1_ui_table.invoke(target, name, args); + case 130: return GeneratedAccess_com_codename1_ui_tree.invoke(target, name, args); + case 131: return GeneratedAccess_com_codename1_ui_util.invoke(target, name, args); + case 132: return GeneratedAccess_com_codename1_ui_validation.invoke(target, name, args); + case 133: return GeneratedAccess_com_codename1_util.invoke(target, name, args); + case 134: return GeneratedAccess_com_codename1_util_promise.invoke(target, name, args); + case 135: return GeneratedAccess_com_codename1_util_regex.invoke(target, name, args); + case 136: return GeneratedAccess_com_codename1_vr.invoke(target, name, args); + case 137: return GeneratedAccess_com_codename1_wearable.invoke(target, name, args); + case 138: return GeneratedAccess_com_codename1_wearable_spi.invoke(target, name, args); + case 139: return GeneratedAccess_com_codename1_xml.invoke(target, name, args); + case 140: return GeneratedAccess_com_codenameone_playground.invoke(target, name, args); + case 141: return GeneratedAccess_java_io.invoke(target, name, args); + case 142: return GeneratedAccess_java_lang.invoke(target, name, args); + case 143: return GeneratedAccess_java_lang_ref.invoke(target, name, args); + case 144: return GeneratedAccess_java_lang_reflect.invoke(target, name, args); + case 145: return GeneratedAccess_java_net.invoke(target, name, args); + case 146: return GeneratedAccess_java_nio_charset.invoke(target, name, args); + case 147: return GeneratedAccess_java_text.invoke(target, name, args); + case 148: return GeneratedAccess_java_time.invoke(target, name, args); + case 149: return GeneratedAccess_java_time_format.invoke(target, name, args); + case 150: return GeneratedAccess_java_time_temporal.invoke(target, name, args); + case 151: return GeneratedAccess_java_util.invoke(target, name, args); + case 152: return GeneratedAccess_java_util_concurrent.invoke(target, name, args); + case 153: return GeneratedAccess_java_util_concurrent_atomic.invoke(target, name, args); + case 154: return GeneratedAccess_java_util_function.invoke(target, name, args); + case 155: return GeneratedAccess_java_util_stream.invoke(target, name, args); default: throw new CN1AccessException("no instance handler for index " + __idx); } } @@ -7779,6 +8188,9 @@ public Object getStaticField(Class type, String name) throws Exception { if ("com.codename1.annotations".equals(candidate)) { return GeneratedAccess_com_codename1_annotations.getStaticField(type, name); } + if ("com.codename1.annotations.buildhints".equals(candidate)) { + return GeneratedAccess_com_codename1_annotations_buildhints.getStaticField(type, name); + } if ("com.codename1.annotations.graphql".equals(candidate)) { return GeneratedAccess_com_codename1_annotations_graphql.getStaticField(type, name); } @@ -7938,6 +8350,21 @@ public Object getStaticField(Class type, String name) throws Exception { if ("com.codename1.health.workout".equals(candidate)) { return GeneratedAccess_com_codename1_health_workout.getStaticField(type, name); } + if ("com.codename1.home".equals(candidate)) { + return GeneratedAccess_com_codename1_home.getStaticField(type, name); + } + if ("com.codename1.home.commissioning".equals(candidate)) { + return GeneratedAccess_com_codename1_home_commissioning.getStaticField(type, name); + } + if ("com.codename1.home.spi".equals(candidate)) { + return GeneratedAccess_com_codename1_home_spi.getStaticField(type, name); + } + if ("com.codename1.intents".equals(candidate)) { + return GeneratedAccess_com_codename1_intents.getStaticField(type, name); + } + if ("com.codename1.intents.spi".equals(candidate)) { + return GeneratedAccess_com_codename1_intents_spi.getStaticField(type, name); + } if ("com.codename1.io".equals(candidate)) { return GeneratedAccess_com_codename1_io.getStaticField(type, name); } @@ -8049,6 +8476,9 @@ public Object getStaticField(Class type, String name) throws Exception { if ("com.codename1.security".equals(candidate)) { return GeneratedAccess_com_codename1_security.getStaticField(type, name); } + if ("com.codename1.security.hardening".equals(candidate)) { + return GeneratedAccess_com_codename1_security_hardening.getStaticField(type, name); + } if ("com.codename1.security.shield".equals(candidate)) { return GeneratedAccess_com_codename1_security_shield.getStaticField(type, name); } @@ -8251,6 +8681,11 @@ public Object getField(Object target, String name) throws Exception { } catch (CN1AccessException ex) { unsupported = ex; } + try { + return GeneratedAccess_com_codename1_annotations_buildhints.getField(target, name); + } catch (CN1AccessException ex) { + unsupported = ex; + } try { return GeneratedAccess_com_codename1_annotations_graphql.getField(target, name); } catch (CN1AccessException ex) { @@ -8516,6 +8951,31 @@ public Object getField(Object target, String name) throws Exception { } catch (CN1AccessException ex) { unsupported = ex; } + try { + return GeneratedAccess_com_codename1_home.getField(target, name); + } catch (CN1AccessException ex) { + unsupported = ex; + } + try { + return GeneratedAccess_com_codename1_home_commissioning.getField(target, name); + } catch (CN1AccessException ex) { + unsupported = ex; + } + try { + return GeneratedAccess_com_codename1_home_spi.getField(target, name); + } catch (CN1AccessException ex) { + unsupported = ex; + } + try { + return GeneratedAccess_com_codename1_intents.getField(target, name); + } catch (CN1AccessException ex) { + unsupported = ex; + } + try { + return GeneratedAccess_com_codename1_intents_spi.getField(target, name); + } catch (CN1AccessException ex) { + unsupported = ex; + } try { return GeneratedAccess_com_codename1_io.getField(target, name); } catch (CN1AccessException ex) { @@ -8701,6 +9161,11 @@ public Object getField(Object target, String name) throws Exception { } catch (CN1AccessException ex) { unsupported = ex; } + try { + return GeneratedAccess_com_codename1_security_hardening.getField(target, name); + } catch (CN1AccessException ex) { + unsupported = ex; + } try { return GeneratedAccess_com_codename1_security_shield.getField(target, name); } catch (CN1AccessException ex) { @@ -8998,6 +9463,10 @@ public void setStaticField(Class type, String name, Object value) throws Exce GeneratedAccess_com_codename1_annotations.setStaticField(type, name, value); return; } + if ("com.codename1.annotations.buildhints".equals(candidate)) { + GeneratedAccess_com_codename1_annotations_buildhints.setStaticField(type, name, value); + return; + } if ("com.codename1.annotations.graphql".equals(candidate)) { GeneratedAccess_com_codename1_annotations_graphql.setStaticField(type, name, value); return; @@ -9210,6 +9679,26 @@ public void setStaticField(Class type, String name, Object value) throws Exce GeneratedAccess_com_codename1_health_workout.setStaticField(type, name, value); return; } + if ("com.codename1.home".equals(candidate)) { + GeneratedAccess_com_codename1_home.setStaticField(type, name, value); + return; + } + if ("com.codename1.home.commissioning".equals(candidate)) { + GeneratedAccess_com_codename1_home_commissioning.setStaticField(type, name, value); + return; + } + if ("com.codename1.home.spi".equals(candidate)) { + GeneratedAccess_com_codename1_home_spi.setStaticField(type, name, value); + return; + } + if ("com.codename1.intents".equals(candidate)) { + GeneratedAccess_com_codename1_intents.setStaticField(type, name, value); + return; + } + if ("com.codename1.intents.spi".equals(candidate)) { + GeneratedAccess_com_codename1_intents_spi.setStaticField(type, name, value); + return; + } if ("com.codename1.io".equals(candidate)) { GeneratedAccess_com_codename1_io.setStaticField(type, name, value); return; @@ -9358,6 +9847,10 @@ public void setStaticField(Class type, String name, Object value) throws Exce GeneratedAccess_com_codename1_security.setStaticField(type, name, value); return; } + if ("com.codename1.security.hardening".equals(candidate)) { + GeneratedAccess_com_codename1_security_hardening.setStaticField(type, name, value); + return; + } if ("com.codename1.security.shield".equals(candidate)) { GeneratedAccess_com_codename1_security_shield.setStaticField(type, name, value); return; @@ -9619,6 +10112,12 @@ public void setField(Object target, String name, Object value) throws Exception } catch (CN1AccessException ex) { unsupported = ex; } + try { + GeneratedAccess_com_codename1_annotations_buildhints.setField(target, name, value); + return; + } catch (CN1AccessException ex) { + unsupported = ex; + } try { GeneratedAccess_com_codename1_annotations_graphql.setField(target, name, value); return; @@ -9937,6 +10436,36 @@ public void setField(Object target, String name, Object value) throws Exception } catch (CN1AccessException ex) { unsupported = ex; } + try { + GeneratedAccess_com_codename1_home.setField(target, name, value); + return; + } catch (CN1AccessException ex) { + unsupported = ex; + } + try { + GeneratedAccess_com_codename1_home_commissioning.setField(target, name, value); + return; + } catch (CN1AccessException ex) { + unsupported = ex; + } + try { + GeneratedAccess_com_codename1_home_spi.setField(target, name, value); + return; + } catch (CN1AccessException ex) { + unsupported = ex; + } + try { + GeneratedAccess_com_codename1_intents.setField(target, name, value); + return; + } catch (CN1AccessException ex) { + unsupported = ex; + } + try { + GeneratedAccess_com_codename1_intents_spi.setField(target, name, value); + return; + } catch (CN1AccessException ex) { + unsupported = ex; + } try { GeneratedAccess_com_codename1_io.setField(target, name, value); return; @@ -10159,6 +10688,12 @@ public void setField(Object target, String name, Object value) throws Exception } catch (CN1AccessException ex) { unsupported = ex; } + try { + GeneratedAccess_com_codename1_security_hardening.setField(target, name, value); + return; + } catch (CN1AccessException ex) { + unsupported = ex; + } try { GeneratedAccess_com_codename1_security_shield.setField(target, name, value); return; diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ai_vision.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ai_vision.java index 52998b9d85e..955ec85683d 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ai_vision.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ai_vision.java @@ -55,9 +55,18 @@ private static Class findClassChunk0(String simpleName) { if ("Barcode".equals(simpleName)) { return com.codename1.ai.vision.Barcode.class; } + if ("BarcodeFormat".equals(simpleName)) { + return com.codename1.ai.vision.BarcodeFormat.class; + } if ("BarcodeScanner".equals(simpleName)) { return com.codename1.ai.vision.BarcodeScanner.class; } + if ("CodeScanner".equals(simpleName)) { + return com.codename1.ai.vision.CodeScanner.class; + } + if ("CodeScannerOptions".equals(simpleName)) { + return com.codename1.ai.vision.CodeScannerOptions.class; + } if ("DocumentScanResult".equals(simpleName)) { return com.codename1.ai.vision.DocumentScanResult.class; } @@ -70,6 +79,9 @@ private static Class findClassChunk0(String simpleName) { if ("FaceDetector".equals(simpleName)) { return com.codename1.ai.vision.FaceDetector.class; } + if ("FaceLandmarks".equals(simpleName)) { + return com.codename1.ai.vision.FaceLandmarks.class; + } if ("ImageLabel".equals(simpleName)) { return com.codename1.ai.vision.ImageLabel.class; } @@ -85,6 +97,9 @@ private static Class findClassChunk0(String simpleName) { if ("PoseDetector".equals(simpleName)) { return com.codename1.ai.vision.PoseDetector.class; } + if ("PoseLandmarks".equals(simpleName)) { + return com.codename1.ai.vision.PoseLandmarks.class; + } if ("SegmentationMask".equals(simpleName)) { return com.codename1.ai.vision.SegmentationMask.class; } @@ -100,6 +115,9 @@ private static Class findClassChunk0(String simpleName) { if ("TextRecognizer".equals(simpleName)) { return com.codename1.ai.vision.TextRecognizer.class; } + if ("TextScript".equals(simpleName)) { + return com.codename1.ai.vision.TextScript.class; + } if ("VisionAnalyzer".equals(simpleName)) { return com.codename1.ai.vision.VisionAnalyzer.class; } @@ -109,6 +127,9 @@ private static Class findClassChunk0(String simpleName) { if ("VisionBackends".equals(simpleName)) { return com.codename1.ai.vision.VisionBackends.class; } + if ("VisionCameraView".equals(simpleName)) { + return com.codename1.ai.vision.VisionCameraView.class; + } if ("VisionException".equals(simpleName)) { return com.codename1.ai.vision.VisionException.class; } @@ -160,6 +181,12 @@ public static Object construct(Class type, Object[] args) throws Exception { return new com.codename1.ai.vision.BarcodeScanner((com.codename1.ai.vision.VisionOptions) adaptedArgs[0]); } } + if (type == com.codename1.ai.vision.CodeScannerOptions.class) { + if (matches(safeArgs, new Class[0], false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[0], false); + return new com.codename1.ai.vision.CodeScannerOptions(); + } + } if (type == com.codename1.ai.vision.DocumentScanResult.class) { if (matches(safeArgs, new Class[]{byte[][].class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{byte[][].class}, false); @@ -292,6 +319,12 @@ public static Object construct(Class type, Object[] args) throws Exception { return new com.codename1.ai.vision.TextRecognizer((com.codename1.ai.vision.VisionOptions) adaptedArgs[0]); } } + if (type == com.codename1.ai.vision.VisionCameraView.class) { + if (matches(safeArgs, new Class[]{com.codename1.ai.vision.VisionAnalyzer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.VisionAnalyzer.class}, false); + return new com.codename1.ai.vision.VisionCameraView((com.codename1.ai.vision.VisionAnalyzer) adaptedArgs[0]); + } + } if (type == com.codename1.ai.vision.VisionException.class) { if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class}, false); @@ -341,12 +374,76 @@ public static Object construct(Class type, Object[] args) throws Exception { public static Object invokeStatic(Class type, String name, Object[] args) throws Exception { Object[] safeArgs = safeArgs(args); - if (type == com.codename1.ai.vision.VisionBackends.class) return invokeStatic0(name, safeArgs); - if (type == com.codename1.ai.vision.VisionImage.class) return invokeStatic1(name, safeArgs); + if (type == com.codename1.ai.vision.BarcodeFormat.class) return invokeStatic0(name, safeArgs); + if (type == com.codename1.ai.vision.CodeScanner.class) return invokeStatic1(name, safeArgs); + if (type == com.codename1.ai.vision.TextScript.class) return invokeStatic2(name, safeArgs); + if (type == com.codename1.ai.vision.VisionBackends.class) return invokeStatic3(name, safeArgs); + if (type == com.codename1.ai.vision.VisionImage.class) return invokeStatic4(name, safeArgs); throw unsupportedStatic(type, name, safeArgs); } private static Object invokeStatic0(String name, Object[] safeArgs) throws Exception { + if ("matches".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ai.vision.Barcode.class, java.lang.String[].class}, true)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.Barcode.class, java.lang.String[].class}, true); + java.lang.String[] varArgs = new java.lang.String[adaptedArgs.length - 1]; + for (int i = 1; i < adaptedArgs.length; i++) { + varArgs[i - 1] = (java.lang.String) adaptedArgs[i]; + } + return com.codename1.ai.vision.BarcodeFormat.matches((com.codename1.ai.vision.Barcode) adaptedArgs[0], varArgs); + } + } + throw unsupportedStatic(com.codename1.ai.vision.BarcodeFormat.class, name, safeArgs); + } + + private static Object invokeStatic1(String name, Object[] safeArgs) throws Exception { + if ("isSupported".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.ai.vision.CodeScanner.isSupported(); + } + } + if ("scan".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.ai.vision.CodeScanner.scan(); + } + if (matches(safeArgs, new Class[]{com.codename1.ai.vision.CodeScannerOptions.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.CodeScannerOptions.class}, false); + return com.codename1.ai.vision.CodeScanner.scan((com.codename1.ai.vision.CodeScannerOptions) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.ai.vision.CodeScanner.class, name, safeArgs); + } + + private static Object invokeStatic2(String name, Object[] safeArgs) throws Exception { + if ("chinese".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.ai.vision.TextScript.chinese(); + } + } + if ("devanagari".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.ai.vision.TextScript.devanagari(); + } + } + if ("japanese".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.ai.vision.TextScript.japanese(); + } + } + if ("korean".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.ai.vision.TextScript.korean(); + } + } + if ("latin".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.ai.vision.TextScript.latin(); + } + } + throw unsupportedStatic(com.codename1.ai.vision.TextScript.class, name, safeArgs); + } + + private static Object invokeStatic3(String name, Object[] safeArgs) throws Exception { if ("appleVision".equals(name)) { if (safeArgs.length == 0) { return com.codename1.ai.vision.VisionBackends.appleVision(); @@ -390,7 +487,7 @@ private static Object invokeStatic0(String name, Object[] safeArgs) throws Excep throw unsupportedStatic(com.codename1.ai.vision.VisionBackends.class, name, safeArgs); } - private static Object invokeStatic1(String name, Object[] safeArgs) throws Exception { + private static Object invokeStatic4(String name, Object[] safeArgs) throws Exception { if ("encoded".equals(name)) { if (matches(safeArgs, new Class[]{byte[].class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{byte[].class}, false); @@ -407,6 +504,18 @@ private static Object invokeStatic1(String name, Object[] safeArgs) throws Excep return com.codename1.ai.vision.VisionImage.fromCameraFrame((com.codename1.camera.CameraFrame) adaptedArgs[0]); } } + if ("fromFile".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.ai.vision.VisionImage.fromFile((java.lang.String) adaptedArgs[0]); + } + } + if ("fromImage".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Image.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Image.class}, false); + return com.codename1.ai.vision.VisionImage.fromImage((com.codename1.ui.Image) adaptedArgs[0]); + } + } if ("pixels".equals(name)) { if (matches(safeArgs, new Class[]{byte[].class, java.lang.Integer.class, java.lang.Integer.class, com.codename1.camera.FrameFormat.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{byte[].class, java.lang.Integer.class, java.lang.Integer.class, com.codename1.camera.FrameFormat.class, java.lang.Integer.class}, false); @@ -426,128 +535,149 @@ public static Object invoke(Object target, String name, Object[] args) throws Ex unsupported = ex; } } + if (target instanceof com.codename1.ai.vision.CodeScannerOptions) { + try { + return invoke1((com.codename1.ai.vision.CodeScannerOptions) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } if (target instanceof com.codename1.ai.vision.DocumentScanResult) { try { - return invoke1((com.codename1.ai.vision.DocumentScanResult) target, name, safeArgs); + return invoke2((com.codename1.ai.vision.DocumentScanResult) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.Face) { try { - return invoke2((com.codename1.ai.vision.Face) target, name, safeArgs); + return invoke3((com.codename1.ai.vision.Face) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.ImageLabel) { try { - return invoke3((com.codename1.ai.vision.ImageLabel) target, name, safeArgs); + return invoke4((com.codename1.ai.vision.ImageLabel) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.Pose) { try { - return invoke4((com.codename1.ai.vision.Pose) target, name, safeArgs); + return invoke5((com.codename1.ai.vision.Pose) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.Pose.Landmark) { try { - return invoke5((com.codename1.ai.vision.Pose.Landmark) target, name, safeArgs); + return invoke6((com.codename1.ai.vision.Pose.Landmark) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.SegmentationMask) { try { - return invoke6((com.codename1.ai.vision.SegmentationMask) target, name, safeArgs); + return invoke7((com.codename1.ai.vision.SegmentationMask) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.TextRecognitionResult) { try { - return invoke7((com.codename1.ai.vision.TextRecognitionResult) target, name, safeArgs); + return invoke8((com.codename1.ai.vision.TextRecognitionResult) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.TextRecognitionResult.TextBlock) { try { - return invoke8((com.codename1.ai.vision.TextRecognitionResult.TextBlock) target, name, safeArgs); + return invoke9((com.codename1.ai.vision.TextRecognitionResult.TextBlock) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.ai.vision.TextScript) { + try { + return invoke10((com.codename1.ai.vision.TextScript) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.ai.vision.VisionCameraView) { + try { + return invoke11((com.codename1.ai.vision.VisionCameraView) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionException) { try { - return invoke9((com.codename1.ai.vision.VisionException) target, name, safeArgs); + return invoke12((com.codename1.ai.vision.VisionException) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionImage) { try { - return invoke10((com.codename1.ai.vision.VisionImage) target, name, safeArgs); + return invoke13((com.codename1.ai.vision.VisionImage) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionMetadata) { try { - return invoke11((com.codename1.ai.vision.VisionMetadata) target, name, safeArgs); + return invoke14((com.codename1.ai.vision.VisionMetadata) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionOptions) { try { - return invoke12((com.codename1.ai.vision.VisionOptions) target, name, safeArgs); + return invoke15((com.codename1.ai.vision.VisionOptions) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionPipeline) { try { - return invoke13((com.codename1.ai.vision.VisionPipeline) target, name, safeArgs); + return invoke16((com.codename1.ai.vision.VisionPipeline) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionPoint) { try { - return invoke14((com.codename1.ai.vision.VisionPoint) target, name, safeArgs); + return invoke17((com.codename1.ai.vision.VisionPoint) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionRect) { try { - return invoke15((com.codename1.ai.vision.VisionRect) target, name, safeArgs); + return invoke18((com.codename1.ai.vision.VisionRect) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionAnalyzer) { try { - return invoke16((com.codename1.ai.vision.VisionAnalyzer) target, name, safeArgs); + return invoke19((com.codename1.ai.vision.VisionAnalyzer) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionBackend) { try { - return invoke17((com.codename1.ai.vision.VisionBackend) target, name, safeArgs); + return invoke20((com.codename1.ai.vision.VisionBackend) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.ai.vision.VisionPipelineListener) { try { - return invoke18((com.codename1.ai.vision.VisionPipelineListener) target, name, safeArgs); + return invoke21((com.codename1.ai.vision.VisionPipelineListener) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } @@ -592,7 +722,81 @@ private static Object invoke0(com.codename1.ai.vision.Barcode typedTarget, Strin throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke1(com.codename1.ai.vision.DocumentScanResult typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke1(com.codename1.ai.vision.CodeScannerOptions typedTarget, String name, Object[] safeArgs) throws Exception { + if ("facing".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.camera.CameraFacing.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.camera.CameraFacing.class}, false); + return typedTarget.facing((com.codename1.camera.CameraFacing) adaptedArgs[0]); + } + } + if ("formats".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String[].class}, true)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String[].class}, true); + java.lang.String[] varArgs = new java.lang.String[adaptedArgs.length - 0]; + for (int i = 0; i < adaptedArgs.length; i++) { + varArgs[i - 0] = (java.lang.String) adaptedArgs[i]; + } + return typedTarget.formats(varArgs); + } + } + if ("getFacing".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getFacing(); + } + } + if ("getFormats".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getFormats(); + } + } + if ("getHint".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getHint(); + } + } + if ("getTitle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTitle(); + } + } + if ("getVisionOptions".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getVisionOptions(); + } + } + if ("hint".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.hint((java.lang.String) adaptedArgs[0]); + } + } + if ("isTorchButton".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isTorchButton(); + } + } + if ("title".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.title((java.lang.String) adaptedArgs[0]); + } + } + if ("torchButton".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return typedTarget.torchButton(((Boolean) adaptedArgs[0]).booleanValue()); + } + } + if ("visionOptions".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ai.vision.VisionOptions.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.VisionOptions.class}, false); + return typedTarget.visionOptions((com.codename1.ai.vision.VisionOptions) adaptedArgs[0]); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke2(com.codename1.ai.vision.DocumentScanResult typedTarget, String name, Object[] safeArgs) throws Exception { if ("getMetadata".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getMetadata(); @@ -612,12 +816,18 @@ private static Object invoke1(com.codename1.ai.vision.DocumentScanResult typedTa throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke2(com.codename1.ai.vision.Face typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke3(com.codename1.ai.vision.Face typedTarget, String name, Object[] safeArgs) throws Exception { if ("getBounds".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getBounds(); } } + if ("getLandmark".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getLandmark((java.lang.String) adaptedArgs[0]); + } + } if ("getLandmarks".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getLandmarks(); @@ -656,7 +866,7 @@ private static Object invoke2(com.codename1.ai.vision.Face typedTarget, String n throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke3(com.codename1.ai.vision.ImageLabel typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke4(com.codename1.ai.vision.ImageLabel typedTarget, String name, Object[] safeArgs) throws Exception { if ("getConfidence".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getConfidence(); @@ -680,7 +890,13 @@ private static Object invoke3(com.codename1.ai.vision.ImageLabel typedTarget, St throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke4(com.codename1.ai.vision.Pose typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke5(com.codename1.ai.vision.Pose typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getLandmark".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getLandmark((java.lang.String) adaptedArgs[0]); + } + } if ("getLandmarks".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getLandmarks(); @@ -694,7 +910,7 @@ private static Object invoke4(com.codename1.ai.vision.Pose typedTarget, String n throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke5(com.codename1.ai.vision.Pose.Landmark typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke6(com.codename1.ai.vision.Pose.Landmark typedTarget, String name, Object[] safeArgs) throws Exception { if ("getConfidence".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getConfidence(); @@ -713,12 +929,24 @@ private static Object invoke5(com.codename1.ai.vision.Pose.Landmark typedTarget, throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke6(com.codename1.ai.vision.SegmentationMask typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke7(com.codename1.ai.vision.SegmentationMask typedTarget, String name, Object[] safeArgs) throws Exception { + if ("cutOut".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Image.class, java.lang.Float.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Image.class, java.lang.Float.class}, false); + return typedTarget.cutOut((com.codename1.ui.Image) adaptedArgs[0], ((Number) adaptedArgs[1]).floatValue()); + } + } if ("getConfidence".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getConfidence(); } } + if ("getConfidenceAt".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.getConfidenceAt(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } if ("getHeight".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getHeight(); @@ -734,10 +962,16 @@ private static Object invoke6(com.codename1.ai.vision.SegmentationMask typedTarg return typedTarget.getWidth(); } } + if ("toMaskImage".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.toMaskImage(toIntValue(adaptedArgs[0])); + } + } throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke7(com.codename1.ai.vision.TextRecognitionResult typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke8(com.codename1.ai.vision.TextRecognitionResult typedTarget, String name, Object[] safeArgs) throws Exception { if ("getBlocks".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getBlocks(); @@ -756,7 +990,7 @@ private static Object invoke7(com.codename1.ai.vision.TextRecognitionResult type throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke8(com.codename1.ai.vision.TextRecognitionResult.TextBlock typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke9(com.codename1.ai.vision.TextRecognitionResult.TextBlock typedTarget, String name, Object[] safeArgs) throws Exception { if ("getBounds".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getBounds(); @@ -780,186 +1014,2291 @@ private static Object invoke8(com.codename1.ai.vision.TextRecognitionResult.Text throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke9(com.codename1.ai.vision.VisionException typedTarget, String name, Object[] safeArgs) throws Exception { - if ("getCode".equals(name)) { + private static Object invoke10(com.codename1.ai.vision.TextScript typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getId".equals(name)) { if (safeArgs.length == 0) { - return typedTarget.getCode(); + return typedTarget.getId(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); } } throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke10(com.codename1.ai.vision.VisionImage typedTarget, String name, Object[] safeArgs) throws Exception { - if ("getEncodedBytes".equals(name)) { + private static Object invoke11(com.codename1.ai.vision.VisionCameraView typedTarget, String name, Object[] safeArgs) throws Exception { + if ("accessibilityChanged".equals(name)) { if (safeArgs.length == 0) { - return typedTarget.getEncodedBytes(); + typedTarget.accessibilityChanged(); return null; + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.accessibilityChanged(toIntValue(adaptedArgs[0])); return null; } } - if ("getEncodedBytesUnsafe".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getEncodedBytesUnsafe(); + if ("add".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + return typedTarget.add((com.codename1.ui.Component) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{com.codename1.ui.Image.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Image.class}, false); + return typedTarget.add((com.codename1.ui.Image) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.add((java.lang.String) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{java.lang.Object.class, com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Object.class, com.codename1.ui.Component.class}, false); + return typedTarget.add((java.lang.Object) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1]); + } + if (matches(safeArgs, new Class[]{java.lang.Object.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Object.class, java.lang.String.class}, false); + return typedTarget.add((java.lang.Object) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); + } + if (matches(safeArgs, new Class[]{java.lang.Object.class, com.codename1.ui.Image.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Object.class, com.codename1.ui.Image.class}, false); + return typedTarget.add((java.lang.Object) adaptedArgs[0], (com.codename1.ui.Image) adaptedArgs[1]); } } - if ("getFormat".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getFormat(); + if ("addAll".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component[].class}, true)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component[].class}, true); + com.codename1.ui.Component[] varArgs = new com.codename1.ui.Component[adaptedArgs.length - 0]; + for (int i = 0; i < adaptedArgs.length; i++) { + varArgs[i - 0] = (com.codename1.ui.Component) adaptedArgs[i]; + } + return typedTarget.addAll(varArgs); } } - if ("getHeight".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getHeight(); + if ("addComponent".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.addComponent((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class, com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, com.codename1.ui.Component.class}, false); + typedTarget.addComponent(toIntValue(adaptedArgs[0]), (com.codename1.ui.Component) adaptedArgs[1]); return null; + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Object.class, com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Object.class, com.codename1.ui.Component.class}, false); + typedTarget.addComponent(toIntValue(adaptedArgs[0]), (java.lang.Object) adaptedArgs[1], (com.codename1.ui.Component) adaptedArgs[2]); return null; } } - if ("getPixels".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getPixels(); + if ("addContextMenuListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addContextMenuListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("getPixelsUnsafe".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getPixelsUnsafe(); + if ("addDragFinishedListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addDragFinishedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("getRotationDegrees".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getRotationDegrees(); + if ("addDragOverListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addDragOverListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("getTimestampNanos".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getTimestampNanos(); + if ("addDropListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addDropListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("getWidth".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getWidth(); + if ("addFocusListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.FocusListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.FocusListener.class}, false); + typedTarget.addFocusListener((com.codename1.ui.events.FocusListener) adaptedArgs[0]); return null; } } - throw unsupportedInstance(typedTarget, name, safeArgs); - } - - private static Object invoke11(com.codename1.ai.vision.VisionMetadata typedTarget, String name, Object[] safeArgs) throws Exception { - if ("get".equals(name)) { - if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { - Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); - return typedTarget.get((java.lang.String) adaptedArgs[0]); + if ("addLongPressListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addLongPressListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("getBackendId".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getBackendId(); + if ("addMouseWheelListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addMouseWheelListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("getValues".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getValues(); + if ("addPointerDraggedListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addPointerDraggedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - throw unsupportedInstance(typedTarget, name, safeArgs); - } - - private static Object invoke12(com.codename1.ai.vision.VisionOptions typedTarget, String name, Object[] safeArgs) throws Exception { - if ("backend".equals(name)) { - if (matches(safeArgs, new Class[]{com.codename1.ai.vision.VisionBackend.class}, false)) { - Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.VisionBackend.class}, false); - return typedTarget.backend((com.codename1.ai.vision.VisionBackend) adaptedArgs[0]); + if ("addPointerPressedListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addPointerPressedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("getBackend".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getBackend(); + if ("addPointerReleasedListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addPointerReleasedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("getMaximumResults".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getMaximumResults(); + if ("addPullToRefresh".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Runnable.class}, false); + typedTarget.addPullToRefresh((java.lang.Runnable) adaptedArgs[0]); return null; } } - if ("getMinimumConfidence".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getMinimumConfidence(); + if ("addScrollListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ScrollListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ScrollListener.class}, false); + typedTarget.addScrollListener((com.codename1.ui.events.ScrollListener) adaptedArgs[0]); return null; } } - if ("maximumResults".equals(name)) { - if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { - Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); - return typedTarget.maximumResults(toIntValue(adaptedArgs[0])); + if ("addStateChangeListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addStateChangeListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - if ("minimumConfidence".equals(name)) { - if (matches(safeArgs, new Class[]{java.lang.Float.class}, false)) { - Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Float.class}, false); - return typedTarget.minimumConfidence(((Number) adaptedArgs[0]).floatValue()); + if ("addStylusListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addStylusListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } - throw unsupportedInstance(typedTarget, name, safeArgs); - } - - private static Object invoke13(com.codename1.ai.vision.VisionPipeline typedTarget, String name, Object[] safeArgs) throws Exception { - if ("close".equals(name)) { + if ("animate".equals(name)) { if (safeArgs.length == 0) { - typedTarget.close(); return null; + return typedTarget.animate(); } } - if ("isBusy".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.isBusy(); + if ("animateHierarchy".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.animateHierarchy(toIntValue(adaptedArgs[0])); return null; } } - throw unsupportedInstance(typedTarget, name, safeArgs); - } - - private static Object invoke14(com.codename1.ai.vision.VisionPoint typedTarget, String name, Object[] safeArgs) throws Exception { - if ("getX".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getX(); + if ("animateHierarchyAndWait".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.animateHierarchyAndWait(toIntValue(adaptedArgs[0])); return null; } } - if ("getY".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getY(); + if ("animateHierarchyFade".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.animateHierarchyFade(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; } } - throw unsupportedInstance(typedTarget, name, safeArgs); - } - - private static Object invoke15(com.codename1.ai.vision.VisionRect typedTarget, String name, Object[] safeArgs) throws Exception { - if ("getHeight".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getHeight(); + if ("animateHierarchyFadeAndWait".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.animateHierarchyFadeAndWait(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; } } - if ("getWidth".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getWidth(); + if ("animateLayout".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.animateLayout(toIntValue(adaptedArgs[0])); return null; } } - if ("getX".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getX(); + if ("animateLayoutAndWait".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.animateLayoutAndWait(toIntValue(adaptedArgs[0])); return null; } } - if ("getY".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.getY(); + if ("animateLayoutFade".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.animateLayoutFade(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; } } - throw unsupportedInstance(typedTarget, name, safeArgs); - } - - private static Object invoke16(com.codename1.ai.vision.VisionAnalyzer typedTarget, String name, Object[] safeArgs) throws Exception { - if ("close".equals(name)) { - if (safeArgs.length == 0) { - typedTarget.close(); return null; + if ("animateLayoutFadeAndWait".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.animateLayoutFadeAndWait(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; } } - if ("isSupported".equals(name)) { - if (safeArgs.length == 0) { - return typedTarget.isSupported(); + if ("animateUnlayout".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Runnable.class}, false); + typedTarget.animateUnlayout(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), (java.lang.Runnable) adaptedArgs[2]); return null; } } - if ("process".equals(name)) { - if (matches(safeArgs, new Class[]{com.codename1.ai.vision.VisionImage.class}, false)) { + if ("animateUnlayoutAndWait".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.animateUnlayoutAndWait(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; + } + } + if ("announceForAccessibility".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.announceForAccessibility((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("applyRTL".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.applyRTL(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("bindProperty".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, com.codename1.cloud.BindTarget.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, com.codename1.cloud.BindTarget.class}, false); + typedTarget.bindProperty((java.lang.String) adaptedArgs[0], (com.codename1.cloud.BindTarget) adaptedArgs[1]); return null; + } + } + if ("blocksSideSwipe".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.blocksSideSwipe(); + } + } + if ("clearClientProperties".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.clearClientProperties(); return null; + } + } + if ("close".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.close(); return null; + } + } + if ("contains".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + return typedTarget.contains((com.codename1.ui.Component) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.contains(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("containsOrOwns".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.containsOrOwns(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("createAnimateHierarchy".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.createAnimateHierarchy(toIntValue(adaptedArgs[0])); + } + } + if ("createAnimateHierarchyFade".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.createAnimateHierarchyFade(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("createAnimateLayout".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.createAnimateLayout(toIntValue(adaptedArgs[0])); + } + } + if ("createAnimateLayoutFade".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.createAnimateLayoutFade(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("createAnimateLayoutFadeAndWait".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.createAnimateLayoutFadeAndWait(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("createAnimateUnlayout".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Runnable.class}, false); + return typedTarget.createAnimateUnlayout(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), (java.lang.Runnable) adaptedArgs[2]); + } + } + if ("createReplaceTransition".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class}, false); + return typedTarget.createReplaceTransition((com.codename1.ui.Component) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1], (com.codename1.ui.animations.Transition) adaptedArgs[2]); + } + } + if ("createStyleAnimation".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false); + return typedTarget.createStyleAnimation((java.lang.String) adaptedArgs[0], toIntValue(adaptedArgs[1])); + } + } + if ("drop".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.drop((com.codename1.ui.Component) adaptedArgs[0], toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2])); return null; + } + } + if ("findDropTargetAt".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.findDropTargetAt(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("findFirstFocusable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.findFirstFocusable(); + } + } + if ("flushReplace".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.flushReplace(); return null; + } + } + if ("forceRevalidate".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.forceRevalidate(); return null; + } + } + if ("getAbsoluteX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAbsoluteX(); + } + } + if ("getAbsoluteY".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAbsoluteY(); + } + } + if ("getAccessibilityNode".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessibilityNode(); + } + } + if ("getAccessibilityText".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessibilityText(); + } + } + if ("getAllStyles".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAllStyles(); + } + } + if ("getAnimationManager".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAnimationManager(); + } + } + if ("getBaseline".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.getBaseline(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("getBaselineResizeBehavior".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBaselineResizeBehavior(); + } + } + if ("getBindablePropertyNames".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBindablePropertyNames(); + } + } + if ("getBindablePropertyTypes".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBindablePropertyTypes(); + } + } + if ("getBottomGap".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBottomGap(); + } + } + if ("getBoundPropertyValue".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getBoundPropertyValue((java.lang.String) adaptedArgs[0]); + } + } + if ("getBounds".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.geom.Rectangle.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.geom.Rectangle.class}, false); + return typedTarget.getBounds((com.codename1.ui.geom.Rectangle) adaptedArgs[0]); + } + } + if ("getChildrenAsList".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return typedTarget.getChildrenAsList(((Boolean) adaptedArgs[0]).booleanValue()); + } + } + if ("getClientProperty".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getClientProperty((java.lang.String) adaptedArgs[0]); + } + } + if ("getClosestComponentTo".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.getClosestComponentTo(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("getCloudBoundProperty".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getCloudBoundProperty(); + } + } + if ("getCloudDestinationProperty".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getCloudDestinationProperty(); + } + } + if ("getComponentAt".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.getComponentAt(toIntValue(adaptedArgs[0])); + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.getComponentAt(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("getComponentCount".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getComponentCount(); + } + } + if ("getComponentForm".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getComponentForm(); + } + } + if ("getComponentIndex".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + return typedTarget.getComponentIndex((com.codename1.ui.Component) adaptedArgs[0]); + } + } + if ("getComponentState".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getComponentState(); + } + } + if ("getCursor".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getCursor(); + } + } + if ("getDirtyRegion".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDirtyRegion(); + } + } + if ("getDisabledStyle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDisabledStyle(); + } + } + if ("getDragTransparency".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDragTransparency(); + } + } + if ("getDraggedx".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDraggedx(); + } + } + if ("getDraggedy".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDraggedy(); + } + } + if ("getEditingDelegate".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getEditingDelegate(); + } + } + if ("getFacing".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getFacing(); + } + } + if ("getHeight".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getHeight(); + } + } + if ("getInlineAllStyles".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInlineAllStyles(); + } + } + if ("getInlineDisabledStyles".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInlineDisabledStyles(); + } + } + if ("getInlinePressedStyles".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInlinePressedStyles(); + } + } + if ("getInlineSelectedStyles".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInlineSelectedStyles(); + } + } + if ("getInlineStylesTheme".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInlineStylesTheme(); + } + } + if ("getInlineUnselectedStyles".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInlineUnselectedStyles(); + } + } + if ("getInnerHeight".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInnerHeight(); + } + } + if ("getInnerPreferredH".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInnerPreferredH(); + } + } + if ("getInnerPreferredW".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInnerPreferredW(); + } + } + if ("getInnerWidth".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInnerWidth(); + } + } + if ("getInnerX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInnerX(); + } + } + if ("getInnerY".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInnerY(); + } + } + if ("getLabelForComponent".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getLabelForComponent(); + } + } + if ("getLayout".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getLayout(); + } + } + if ("getLayoutHeight".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getLayoutHeight(); + } + } + if ("getLayoutWidth".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getLayoutWidth(); + } + } + if ("getLeadComponent".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getLeadComponent(); + } + } + if ("getLeadParent".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getLeadParent(); + } + } + if ("getListener".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getListener(); + } + } + if ("getMaxFps".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMaxFps(); + } + } + if ("getName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getName(); + } + } + if ("getNativeOverlay".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getNativeOverlay(); + } + } + if ("getNextFocusDown".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getNextFocusDown(); + } + } + if ("getNextFocusLeft".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getNextFocusLeft(); + } + } + if ("getNextFocusRight".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getNextFocusRight(); + } + } + if ("getNextFocusUp".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getNextFocusUp(); + } + } + if ("getOuterHeight".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOuterHeight(); + } + } + if ("getOuterPreferredH".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOuterPreferredH(); + } + } + if ("getOuterPreferredW".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOuterPreferredW(); + } + } + if ("getOuterWidth".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOuterWidth(); + } + } + if ("getOuterX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOuterX(); + } + } + if ("getOuterY".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOuterY(); + } + } + if ("getOwner".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOwner(); + } + } + if ("getParent".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getParent(); + } + } + if ("getPreferredH".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPreferredH(); + } + } + if ("getPreferredSize".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPreferredSize(); + } + } + if ("getPreferredSizeStr".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPreferredSizeStr(); + } + } + if ("getPreferredTabIndex".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPreferredTabIndex(); + } + } + if ("getPreferredW".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPreferredW(); + } + } + if ("getPressedStyle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPressedStyle(); + } + } + if ("getPropertyNames".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPropertyNames(); + } + } + if ("getPropertyTypeNames".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPropertyTypeNames(); + } + } + if ("getPropertyTypes".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPropertyTypes(); + } + } + if ("getPropertyValue".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getPropertyValue((java.lang.String) adaptedArgs[0]); + } + } + if ("getResponderAt".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.getResponderAt(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + if ("getSafeAreaRoot".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSafeAreaRoot(); + } + } + if ("getSameHeight".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSameHeight(); + } + } + if ("getSameWidth".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSameWidth(); + } + } + if ("getScaleType".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScaleType(); + } + } + if ("getScrollAnimationSpeed".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScrollAnimationSpeed(); + } + } + if ("getScrollDimension".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScrollDimension(); + } + } + if ("getScrollIncrement".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScrollIncrement(); + } + } + if ("getScrollOpacity".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScrollOpacity(); + } + } + if ("getScrollOpacityChangeSpeed".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScrollOpacityChangeSpeed(); + } + } + if ("getScrollX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScrollX(); + } + } + if ("getScrollY".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScrollY(); + } + } + if ("getScrollable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScrollable(); + } + } + if ("getSelectCommandText".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSelectCommandText(); + } + } + if ("getSelectedRect".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSelectedRect(); + } + } + if ("getSelectedStyle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSelectedStyle(); + } + } + if ("getSemantics".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSemantics(); + } + } + if ("getSession".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSession(); + } + } + if ("getSideGap".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSideGap(); + } + } + if ("getStyle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStyle(); + } + } + if ("getTabIndex".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTabIndex(); + } + } + if ("getTensileLength".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTensileLength(); + } + } + if ("getTextSelectionSupport".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTextSelectionSupport(); + } + } + if ("getTooltip".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTooltip(); + } + } + if ("getUIID".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getUIID(); + } + } + if ("getUIManager".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getUIManager(); + } + } + if ("getUnselectedStyle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getUnselectedStyle(); + } + } + if ("getVisibleBounds".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.geom.Rectangle.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.geom.Rectangle.class}, false); + return typedTarget.getVisibleBounds((com.codename1.ui.geom.Rectangle) adaptedArgs[0]); + } + } + if ("getWidth".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getWidth(); + } + } + if ("getX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getX(); + } + } + if ("getY".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getY(); + } + } + if ("growShrink".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.growShrink(toIntValue(adaptedArgs[0])); return null; + } + } + if ("handlesInput".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.handlesInput(); + } + } + if ("hasFixedPreferredSize".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.hasFixedPreferredSize(); + } + } + if ("hasFocus".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.hasFocus(); + } + } + if ("invalidate".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.invalidate(); return null; + } + } + if ("isAlwaysTensile".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isAlwaysTensile(); + } + } + if ("isBlockLead".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isBlockLead(); + } + } + if ("isCellRenderer".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isCellRenderer(); + } + } + if ("isChildOf".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Container.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Container.class}, false); + return typedTarget.isChildOf((com.codename1.ui.Container) adaptedArgs[0]); + } + } + if ("isDraggable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isDraggable(); + } + } + if ("isDropTarget".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isDropTarget(); + } + } + if ("isEditable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isEditable(); + } + } + if ("isEditing".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isEditing(); + } + } + if ("isEnabled".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isEnabled(); + } + } + if ("isFlatten".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isFlatten(); + } + } + if ("isFocusable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isFocusable(); + } + } + if ("isGrabsPointerEvents".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isGrabsPointerEvents(); + } + } + if ("isHScrollThumbGrabbed".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHScrollThumbGrabbed(); + } + } + if ("isHScrollThumbHover".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHScrollThumbHover(); + } + } + if ("isHidden".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHidden(); + } + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return typedTarget.isHidden(((Boolean) adaptedArgs[0]).booleanValue()); + } + } + if ("isHideInLandscape".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHideInLandscape(); + } + } + if ("isHideInPortrait".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHideInPortrait(); + } + } + if ("isIgnorePointerEvents".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isIgnorePointerEvents(); + } + } + if ("isOpaque".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isOpaque(); + } + } + if ("isOwnedBy".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + return typedTarget.isOwnedBy((com.codename1.ui.Component) adaptedArgs[0]); + } + } + if ("isPinchBlocksDragAndDrop".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isPinchBlocksDragAndDrop(); + } + } + if ("isRTL".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isRTL(); + } + } + if ("isRippleEffect".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isRippleEffect(); + } + } + if ("isRunning".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isRunning(); + } + } + if ("isSafeArea".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSafeArea(); + } + } + if ("isSafeAreaRoot".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSafeAreaRoot(); + } + } + if ("isScrollVisible".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isScrollVisible(); + } + } + if ("isScrollableX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isScrollableX(); + } + } + if ("isScrollableY".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isScrollableY(); + } + } + if ("isSmoothScrolling".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSmoothScrolling(); + } + } + if ("isSnapToGrid".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSnapToGrid(); + } + } + if ("isSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSupported(); + } + } + if ("isSurface".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSurface(); + } + } + if ("isTactileTouch".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isTactileTouch(); + } + } + if ("isTensileDragEnabled".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isTensileDragEnabled(); + } + } + if ("isTraversable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isTraversable(); + } + } + if ("isVScrollThumbGrabbed".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isVScrollThumbGrabbed(); + } + } + if ("isVScrollThumbHover".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isVScrollThumbHover(); + } + } + if ("isVisible".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isVisible(); + } + } + if ("iterator".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.iterator(); + } + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return typedTarget.iterator(((Boolean) adaptedArgs[0]).booleanValue()); + } + } + if ("keyPressed".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.keyPressed(toIntValue(adaptedArgs[0])); return null; + } + } + if ("keyReleased".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.keyReleased(toIntValue(adaptedArgs[0])); return null; + } + } + if ("keyRepeated".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.keyRepeated(toIntValue(adaptedArgs[0])); return null; + } + } + if ("layoutContainer".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.layoutContainer(); return null; + } + } + if ("longPointerPress".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.longPointerPress(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; + } + } + if ("morph".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, java.lang.Integer.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, java.lang.Integer.class, java.lang.Runnable.class}, false); + typedTarget.morph((com.codename1.ui.Component) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1], toIntValue(adaptedArgs[2]), (java.lang.Runnable) adaptedArgs[3]); return null; + } + } + if ("morphAndWait".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, java.lang.Integer.class}, false); + typedTarget.morphAndWait((com.codename1.ui.Component) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1], toIntValue(adaptedArgs[2])); return null; + } + } + if ("paint".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false); + typedTarget.paint((com.codename1.ui.Graphics) adaptedArgs[0]); return null; + } + } + if ("paintBackgrounds".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false); + typedTarget.paintBackgrounds((com.codename1.ui.Graphics) adaptedArgs[0]); return null; + } + } + if ("paintComponent".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false); + typedTarget.paintComponent((com.codename1.ui.Graphics) adaptedArgs[0]); return null; + } + if (matches(safeArgs, new Class[]{com.codename1.ui.Graphics.class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Graphics.class, java.lang.Boolean.class}, false); + typedTarget.paintComponent((com.codename1.ui.Graphics) adaptedArgs[0], ((Boolean) adaptedArgs[1]).booleanValue()); return null; + } + } + if ("paintComponentBackground".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false); + typedTarget.paintComponentBackground((com.codename1.ui.Graphics) adaptedArgs[0]); return null; + } + } + if ("paintIntersectingComponentsAbove".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Graphics.class}, false); + typedTarget.paintIntersectingComponentsAbove((com.codename1.ui.Graphics) adaptedArgs[0]); return null; + } + } + if ("paintLock".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return typedTarget.paintLock(((Boolean) adaptedArgs[0]).booleanValue()); + } + } + if ("paintLockRelease".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.paintLockRelease(); return null; + } + } + if ("paintRippleOverlay".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Graphics.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Graphics.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.paintRippleOverlay((com.codename1.ui.Graphics) adaptedArgs[0], toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2]), toIntValue(adaptedArgs[3])); return null; + } + } + if ("paintShadows".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Graphics.class, java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Graphics.class, java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.paintShadows((com.codename1.ui.Graphics) adaptedArgs[0], toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2])); return null; + } + } + if ("pointerDragged".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.pointerDragged(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; + } + if (matches(safeArgs, new Class[]{int[].class, int[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{int[].class, int[].class}, false); + typedTarget.pointerDragged((int[]) adaptedArgs[0], (int[]) adaptedArgs[1]); return null; + } + } + if ("pointerHover".equals(name)) { + if (matches(safeArgs, new Class[]{int[].class, int[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{int[].class, int[].class}, false); + typedTarget.pointerHover((int[]) adaptedArgs[0], (int[]) adaptedArgs[1]); return null; + } + } + if ("pointerHoverPressed".equals(name)) { + if (matches(safeArgs, new Class[]{int[].class, int[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{int[].class, int[].class}, false); + typedTarget.pointerHoverPressed((int[]) adaptedArgs[0], (int[]) adaptedArgs[1]); return null; + } + } + if ("pointerHoverReleased".equals(name)) { + if (matches(safeArgs, new Class[]{int[].class, int[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{int[].class, int[].class}, false); + typedTarget.pointerHoverReleased((int[]) adaptedArgs[0], (int[]) adaptedArgs[1]); return null; + } + } + if ("pointerPressed".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.pointerPressed(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; + } + if (matches(safeArgs, new Class[]{int[].class, int[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{int[].class, int[].class}, false); + typedTarget.pointerPressed((int[]) adaptedArgs[0], (int[]) adaptedArgs[1]); return null; + } + } + if ("pointerReleased".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.pointerReleased(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); return null; + } + if (matches(safeArgs, new Class[]{int[].class, int[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{int[].class, int[].class}, false); + typedTarget.pointerReleased((int[]) adaptedArgs[0], (int[]) adaptedArgs[1]); return null; + } + } + if ("putClientProperty".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Object.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Object.class}, false); + typedTarget.putClientProperty((java.lang.String) adaptedArgs[0], (java.lang.Object) adaptedArgs[1]); return null; + } + } + if ("refreshTheme".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.refreshTheme(); return null; + } + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.refreshTheme(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("remove".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.remove(); return null; + } + } + if ("removeAll".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.removeAll(); return null; + } + } + if ("removeComponent".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.removeComponent((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + } + if ("removeContextMenuListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeContextMenuListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removeDragFinishedListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeDragFinishedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removeDragOverListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeDragOverListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removeDropListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeDropListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removeFocusListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.FocusListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.FocusListener.class}, false); + typedTarget.removeFocusListener((com.codename1.ui.events.FocusListener) adaptedArgs[0]); return null; + } + } + if ("removeLongPressListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeLongPressListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removeMouseWheelListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeMouseWheelListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removePointerDraggedListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removePointerDraggedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removePointerPressedListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removePointerPressedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removePointerReleasedListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removePointerReleasedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removeScrollListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ScrollListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ScrollListener.class}, false); + typedTarget.removeScrollListener((com.codename1.ui.events.ScrollListener) adaptedArgs[0]); return null; + } + } + if ("removeStateChangeListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeStateChangeListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("removeStylusListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeStylusListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } + if ("repaint".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.repaint(); return null; + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.repaint(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2]), toIntValue(adaptedArgs[3])); return null; + } + } + if ("replace".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class}, false); + typedTarget.replace((com.codename1.ui.Component) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1], (com.codename1.ui.animations.Transition) adaptedArgs[2]); return null; + } + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class, java.lang.Runnable.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class, java.lang.Runnable.class, java.lang.Integer.class}, false); + typedTarget.replace((com.codename1.ui.Component) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1], (com.codename1.ui.animations.Transition) adaptedArgs[2], (java.lang.Runnable) adaptedArgs[3], toIntValue(adaptedArgs[4])); return null; + } + } + if ("replaceAndWait".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class}, false); + typedTarget.replaceAndWait((com.codename1.ui.Component) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1], (com.codename1.ui.animations.Transition) adaptedArgs[2]); return null; + } + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class, java.lang.Integer.class}, false); + typedTarget.replaceAndWait((com.codename1.ui.Component) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1], (com.codename1.ui.animations.Transition) adaptedArgs[2], toIntValue(adaptedArgs[3])); return null; + } + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class, com.codename1.ui.Component.class, com.codename1.ui.animations.Transition.class, java.lang.Boolean.class}, false); + typedTarget.replaceAndWait((com.codename1.ui.Component) adaptedArgs[0], (com.codename1.ui.Component) adaptedArgs[1], (com.codename1.ui.animations.Transition) adaptedArgs[2], ((Boolean) adaptedArgs[3]).booleanValue()); return null; + } + } + if ("requestFocus".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.requestFocus(); return null; + } + } + if ("respondsToPointerEvents".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.respondsToPointerEvents(); + } + } + if ("revalidate".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.revalidate(); return null; + } + } + if ("revalidateLater".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.revalidateLater(); return null; + } + } + if ("revalidateWithAnimationSafety".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.revalidateWithAnimationSafety(); return null; + } + } + if ("scrollComponentToVisible".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.scrollComponentToVisible((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + } + if ("scrollRectToVisible".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, com.codename1.ui.Component.class}, false); + typedTarget.scrollRectToVisible(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2]), toIntValue(adaptedArgs[3]), (com.codename1.ui.Component) adaptedArgs[4]); return null; + } + } + if ("setAccessibilityText".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setAccessibilityText((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setAlwaysTensile".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setAlwaysTensile(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setBlockLead".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setBlockLead(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setBoundPropertyValue".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Object.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Object.class}, false); + typedTarget.setBoundPropertyValue((java.lang.String) adaptedArgs[0], (java.lang.Object) adaptedArgs[1]); return null; + } + } + if ("setCellRenderer".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setCellRenderer(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setCloudBoundProperty".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setCloudBoundProperty((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setCloudDestinationProperty".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setCloudDestinationProperty((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setComponentState".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Object.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Object.class}, false); + typedTarget.setComponentState((java.lang.Object) adaptedArgs[0]); return null; + } + } + if ("setCursor".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setCursor(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setDirtyRegion".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.geom.Rectangle.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.geom.Rectangle.class}, false); + typedTarget.setDirtyRegion((com.codename1.ui.geom.Rectangle) adaptedArgs[0]); return null; + } + } + if ("setDisabledStyle".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.plaf.Style.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.plaf.Style.class}, false); + typedTarget.setDisabledStyle((com.codename1.ui.plaf.Style) adaptedArgs[0]); return null; + } + } + if ("setDragTransparency".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Byte.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Byte.class}, false); + typedTarget.setDragTransparency((byte) toIntValue(adaptedArgs[0])); return null; + } + } + if ("setDraggable".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setDraggable(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setDropTarget".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setDropTarget(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setEditingDelegate".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Editable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Editable.class}, false); + typedTarget.setEditingDelegate((com.codename1.ui.Editable) adaptedArgs[0]); return null; + } + } + if ("setEnabled".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setEnabled(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setFacing".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.camera.CameraFacing.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.camera.CameraFacing.class}, false); + typedTarget.setFacing((com.codename1.camera.CameraFacing) adaptedArgs[0]); return null; + } + } + if ("setFlatten".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setFlatten(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setFocus".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setFocus(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setFocusable".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setFocusable(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setGrabsPointerEvents".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setGrabsPointerEvents(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setHandlesInput".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setHandlesInput(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setHeight".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setHeight(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setHidden".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setHidden(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + if (matches(safeArgs, new Class[]{java.lang.Boolean.class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class, java.lang.Boolean.class}, false); + typedTarget.setHidden(((Boolean) adaptedArgs[0]).booleanValue(), ((Boolean) adaptedArgs[1]).booleanValue()); return null; + } + } + if ("setHideInLandscape".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setHideInLandscape(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setHideInPortrait".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setHideInPortrait(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setHorizontalScrollBounds".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.setHorizontalScrollBounds(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2]), toIntValue(adaptedArgs[3]), toIntValue(adaptedArgs[4]), toIntValue(adaptedArgs[5]), toIntValue(adaptedArgs[6]), toIntValue(adaptedArgs[7])); return null; + } + } + if ("setIgnorePointerEvents".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setIgnorePointerEvents(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setInlineAllStyles".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setInlineAllStyles((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setInlineDisabledStyles".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setInlineDisabledStyles((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setInlinePressedStyles".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setInlinePressedStyles((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setInlineSelectedStyles".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setInlineSelectedStyles((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setInlineStylesTheme".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.util.Resources.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.util.Resources.class}, false); + typedTarget.setInlineStylesTheme((com.codename1.ui.util.Resources) adaptedArgs[0]); return null; + } + } + if ("setInlineUnselectedStyles".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setInlineUnselectedStyles((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setIsScrollVisible".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setIsScrollVisible(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setLabelForComponent".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Label.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Label.class}, false); + typedTarget.setLabelForComponent((com.codename1.ui.Label) adaptedArgs[0]); return null; + } + } + if ("setLayout".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.layouts.Layout.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.layouts.Layout.class}, false); + typedTarget.setLayout((com.codename1.ui.layouts.Layout) adaptedArgs[0]); return null; + } + } + if ("setLeadComponent".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.setLeadComponent((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + } + if ("setListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ai.vision.VisionPipelineListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.VisionPipelineListener.class}, false); + typedTarget.setListener((com.codename1.ai.vision.VisionPipelineListener) adaptedArgs[0]); return null; + } + } + if ("setMaxFps".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setMaxFps(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setName".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setName((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setNextFocusDown".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.setNextFocusDown((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + } + if ("setNextFocusLeft".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.setNextFocusLeft((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + } + if ("setNextFocusRight".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.setNextFocusRight((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + } + if ("setNextFocusUp".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.setNextFocusUp((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + } + if ("setOpaque".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setOpaque(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setOwner".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + typedTarget.setOwner((com.codename1.ui.Component) adaptedArgs[0]); return null; + } + } + if ("setPinchBlocksDragAndDrop".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setPinchBlocksDragAndDrop(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setPreferredH".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setPreferredH(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setPreferredSize".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.geom.Dimension.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.geom.Dimension.class}, false); + typedTarget.setPreferredSize((com.codename1.ui.geom.Dimension) adaptedArgs[0]); return null; + } + } + if ("setPreferredSizeStr".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setPreferredSizeStr((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setPreferredTabIndex".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setPreferredTabIndex(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setPreferredW".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setPreferredW(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setPressedStyle".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.plaf.Style.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.plaf.Style.class}, false); + typedTarget.setPressedStyle((com.codename1.ui.plaf.Style) adaptedArgs[0]); return null; + } + } + if ("setPropertyValue".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Object.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Object.class}, false); + return typedTarget.setPropertyValue((java.lang.String) adaptedArgs[0], (java.lang.Object) adaptedArgs[1]); + } + } + if ("setPullToRefresh".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Runnable.class}, false); + typedTarget.setPullToRefresh((java.lang.Runnable) adaptedArgs[0]); return null; + } + } + if ("setRTL".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setRTL(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setRippleEffect".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setRippleEffect(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setSafeArea".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setSafeArea(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setSafeAreaRoot".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setSafeAreaRoot(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setScaleType".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.camera.ScaleType.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.camera.ScaleType.class}, false); + typedTarget.setScaleType((com.codename1.camera.ScaleType) adaptedArgs[0]); return null; + } + } + if ("setScrollAnimationSpeed".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setScrollAnimationSpeed(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setScrollIncrement".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setScrollIncrement(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setScrollOpacityChangeSpeed".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setScrollOpacityChangeSpeed(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setScrollSize".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.geom.Dimension.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.geom.Dimension.class}, false); + typedTarget.setScrollSize((com.codename1.ui.geom.Dimension) adaptedArgs[0]); return null; + } + } + if ("setScrollVisible".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setScrollVisible(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setScrollable".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setScrollable(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setScrollableX".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setScrollableX(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setScrollableY".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setScrollableY(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setSelectCommandText".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setSelectCommandText((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setSelectedStyle".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.plaf.Style.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.plaf.Style.class}, false); + typedTarget.setSelectedStyle((com.codename1.ui.plaf.Style) adaptedArgs[0]); return null; + } + } + if ("setShouldCalcPreferredSize".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setShouldCalcPreferredSize(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setSize".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.geom.Dimension.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.geom.Dimension.class}, false); + typedTarget.setSize((com.codename1.ui.geom.Dimension) adaptedArgs[0]); return null; + } + } + if ("setSmoothScrolling".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setSmoothScrolling(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setSnapToGrid".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setSnapToGrid(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setTabIndex".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setTabIndex(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setTactileTouch".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setTactileTouch(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setTensileDragEnabled".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setTensileDragEnabled(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setTensileLength".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setTensileLength(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setTooltip".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setTooltip((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("setTorchEnabled".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setTorchEnabled(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setTraversable".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setTraversable(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setUIID".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.setUIID((java.lang.String) adaptedArgs[0]); return null; + } + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + typedTarget.setUIID((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); return null; + } + } + if ("setUIManager".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.plaf.UIManager.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.plaf.UIManager.class}, false); + typedTarget.setUIManager((com.codename1.ui.plaf.UIManager) adaptedArgs[0]); return null; + } + } + if ("setUnselectedStyle".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.plaf.Style.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.plaf.Style.class}, false); + typedTarget.setUnselectedStyle((com.codename1.ui.plaf.Style) adaptedArgs[0]); return null; + } + } + if ("setVerticalScrollBounds".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false); + typedTarget.setVerticalScrollBounds(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2]), toIntValue(adaptedArgs[3]), toIntValue(adaptedArgs[4]), toIntValue(adaptedArgs[5]), toIntValue(adaptedArgs[6]), toIntValue(adaptedArgs[7])); return null; + } + } + if ("setVisible".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setVisible(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } + if ("setWidth".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setWidth(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setX".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setX(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setY".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setY(toIntValue(adaptedArgs[0])); return null; + } + } + if ("start".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.start(); return null; + } + } + if ("startEditingAsync".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.startEditingAsync(); return null; + } + } + if ("stop".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.stop(); return null; + } + } + if ("stopEditing".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Runnable.class}, false); + typedTarget.stopEditing((java.lang.Runnable) adaptedArgs[0]); return null; + } + } + if ("stripMarginAndPadding".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.stripMarginAndPadding(); + } + } + if ("styleChanged".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, com.codename1.ui.plaf.Style.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, com.codename1.ui.plaf.Style.class}, false); + typedTarget.styleChanged((java.lang.String) adaptedArgs[0], (com.codename1.ui.plaf.Style) adaptedArgs[1]); return null; + } + } + if ("toImage".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toImage(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + if ("unbindProperty".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, com.codename1.cloud.BindTarget.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, com.codename1.cloud.BindTarget.class}, false); + typedTarget.unbindProperty((java.lang.String) adaptedArgs[0], (com.codename1.cloud.BindTarget) adaptedArgs[1]); return null; + } + } + if ("updateTabIndices".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.updateTabIndices(toIntValue(adaptedArgs[0])); + } + } + if ("visibleBoundsContains".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.visibleBoundsContains(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke12(com.codename1.ai.vision.VisionException typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getCode".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getCode(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke13(com.codename1.ai.vision.VisionImage typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getEncodedBytes".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getEncodedBytes(); + } + } + if ("getEncodedBytesUnsafe".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getEncodedBytesUnsafe(); + } + } + if ("getFormat".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getFormat(); + } + } + if ("getHeight".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getHeight(); + } + } + if ("getPixels".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPixels(); + } + } + if ("getPixelsUnsafe".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPixelsUnsafe(); + } + } + if ("getRotationDegrees".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRotationDegrees(); + } + } + if ("getTimestampNanos".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTimestampNanos(); + } + } + if ("getWidth".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getWidth(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke14(com.codename1.ai.vision.VisionMetadata typedTarget, String name, Object[] safeArgs) throws Exception { + if ("get".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.get((java.lang.String) adaptedArgs[0]); + } + } + if ("getBackendId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBackendId(); + } + } + if ("getValues".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getValues(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke15(com.codename1.ai.vision.VisionOptions typedTarget, String name, Object[] safeArgs) throws Exception { + if ("backend".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ai.vision.VisionBackend.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.VisionBackend.class}, false); + return typedTarget.backend((com.codename1.ai.vision.VisionBackend) adaptedArgs[0]); + } + } + if ("getBackend".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBackend(); + } + } + if ("getMaximumResults".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMaximumResults(); + } + } + if ("getMinimumConfidence".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMinimumConfidence(); + } + } + if ("getTextScript".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTextScript(); + } + } + if ("maximumResults".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.maximumResults(toIntValue(adaptedArgs[0])); + } + } + if ("minimumConfidence".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Float.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Float.class}, false); + return typedTarget.minimumConfidence(((Number) adaptedArgs[0]).floatValue()); + } + } + if ("textScript".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ai.vision.TextScript.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.TextScript.class}, false); + return typedTarget.textScript((com.codename1.ai.vision.TextScript) adaptedArgs[0]); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke16(com.codename1.ai.vision.VisionPipeline typedTarget, String name, Object[] safeArgs) throws Exception { + if ("close".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.close(); return null; + } + } + if ("isBusy".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isBusy(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke17(com.codename1.ai.vision.VisionPoint typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getX(); + } + } + if ("getY".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getY(); + } + } + if ("toPoint".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + return typedTarget.toPoint((com.codename1.ui.Component) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.toPoint(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2]), toIntValue(adaptedArgs[3])); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke18(com.codename1.ai.vision.VisionRect typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getHeight".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getHeight(); + } + } + if ("getWidth".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getWidth(); + } + } + if ("getX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getX(); + } + } + if ("getY".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getY(); + } + } + if ("isEmpty".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isEmpty(); + } + } + if ("toBounds".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.Component.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.Component.class}, false); + return typedTarget.toBounds((com.codename1.ui.Component) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class}, false); + return typedTarget.toBounds(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2]), toIntValue(adaptedArgs[3])); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke19(com.codename1.ai.vision.VisionAnalyzer typedTarget, String name, Object[] safeArgs) throws Exception { + if ("close".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.close(); return null; + } + } + if ("isSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSupported(); + } + } + if ("process".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ai.vision.VisionImage.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ai.vision.VisionImage.class}, false); return typedTarget.process((com.codename1.ai.vision.VisionImage) adaptedArgs[0]); } @@ -967,7 +3306,7 @@ private static Object invoke16(com.codename1.ai.vision.VisionAnalyzer typedTarge throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke17(com.codename1.ai.vision.VisionBackend typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke20(com.codename1.ai.vision.VisionBackend typedTarget, String name, Object[] safeArgs) throws Exception { if ("getId".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getId(); @@ -976,7 +3315,7 @@ private static Object invoke17(com.codename1.ai.vision.VisionBackend typedTarget throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke18(com.codename1.ai.vision.VisionPipelineListener typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke21(com.codename1.ai.vision.VisionPipelineListener typedTarget, String name, Object[] safeArgs) throws Exception { if ("error".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Throwable.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Throwable.class}, false); @@ -993,19 +3332,128 @@ private static Object invoke18(com.codename1.ai.vision.VisionPipelineListener ty } public static Object getStaticField(Class type, String name) throws Exception { - if (type == com.codename1.ai.vision.TextRecognitionResult.class) return getStaticField0(name); - if (type == com.codename1.ai.vision.VisionException.class) return getStaticField1(name); - if (type == com.codename1.ai.vision.VisionFeature.class) return getStaticField2(name); - if (type == com.codename1.ai.vision.VisionRect.class) return getStaticField3(name); + if (type == com.codename1.ai.vision.BarcodeFormat.class) return getStaticField0(name); + if (type == com.codename1.ai.vision.FaceLandmarks.class) return getStaticField1(name); + if (type == com.codename1.ai.vision.PoseLandmarks.class) return getStaticField2(name); + if (type == com.codename1.ai.vision.TextRecognitionResult.class) return getStaticField3(name); + if (type == com.codename1.ai.vision.VisionCameraView.class) return getStaticField4(name); + if (type == com.codename1.ai.vision.VisionException.class) return getStaticField5(name); + if (type == com.codename1.ai.vision.VisionFeature.class) return getStaticField6(name); + if (type == com.codename1.ai.vision.VisionRect.class) return getStaticField7(name); throw unsupportedStaticField(type, name); } private static Object getStaticField0(String name) throws Exception { + if ("AZTEC".equals(name)) return com.codename1.ai.vision.BarcodeFormat.AZTEC; + if ("CODABAR".equals(name)) return com.codename1.ai.vision.BarcodeFormat.CODABAR; + if ("CODE_128".equals(name)) return com.codename1.ai.vision.BarcodeFormat.CODE_128; + if ("CODE_39".equals(name)) return com.codename1.ai.vision.BarcodeFormat.CODE_39; + if ("CODE_93".equals(name)) return com.codename1.ai.vision.BarcodeFormat.CODE_93; + if ("DATA_MATRIX".equals(name)) return com.codename1.ai.vision.BarcodeFormat.DATA_MATRIX; + if ("EAN_13".equals(name)) return com.codename1.ai.vision.BarcodeFormat.EAN_13; + if ("EAN_8".equals(name)) return com.codename1.ai.vision.BarcodeFormat.EAN_8; + if ("ITF".equals(name)) return com.codename1.ai.vision.BarcodeFormat.ITF; + if ("PDF417".equals(name)) return com.codename1.ai.vision.BarcodeFormat.PDF417; + if ("QR_CODE".equals(name)) return com.codename1.ai.vision.BarcodeFormat.QR_CODE; + if ("UNKNOWN".equals(name)) return com.codename1.ai.vision.BarcodeFormat.UNKNOWN; + if ("UPC_A".equals(name)) return com.codename1.ai.vision.BarcodeFormat.UPC_A; + if ("UPC_E".equals(name)) return com.codename1.ai.vision.BarcodeFormat.UPC_E; + throw unsupportedStaticField(com.codename1.ai.vision.BarcodeFormat.class, name); + } + + private static Object getStaticField1(String name) throws Exception { + if ("LEFT_EYE".equals(name)) return com.codename1.ai.vision.FaceLandmarks.LEFT_EYE; + if ("MOUTH_LEFT".equals(name)) return com.codename1.ai.vision.FaceLandmarks.MOUTH_LEFT; + if ("MOUTH_RIGHT".equals(name)) return com.codename1.ai.vision.FaceLandmarks.MOUTH_RIGHT; + if ("NOSE_BASE".equals(name)) return com.codename1.ai.vision.FaceLandmarks.NOSE_BASE; + if ("RIGHT_EYE".equals(name)) return com.codename1.ai.vision.FaceLandmarks.RIGHT_EYE; + throw unsupportedStaticField(com.codename1.ai.vision.FaceLandmarks.class, name); + } + + private static Object getStaticField2(String name) throws Exception { + if ("LEFT_ANKLE".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_ANKLE; + if ("LEFT_EAR".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_EAR; + if ("LEFT_ELBOW".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_ELBOW; + if ("LEFT_EYE".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_EYE; + if ("LEFT_EYE_INNER".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_EYE_INNER; + if ("LEFT_EYE_OUTER".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_EYE_OUTER; + if ("LEFT_FOOT_INDEX".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_FOOT_INDEX; + if ("LEFT_HEEL".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_HEEL; + if ("LEFT_HIP".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_HIP; + if ("LEFT_INDEX".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_INDEX; + if ("LEFT_KNEE".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_KNEE; + if ("LEFT_MOUTH".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_MOUTH; + if ("LEFT_PINKY".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_PINKY; + if ("LEFT_SHOULDER".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_SHOULDER; + if ("LEFT_THUMB".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_THUMB; + if ("LEFT_WRIST".equals(name)) return com.codename1.ai.vision.PoseLandmarks.LEFT_WRIST; + if ("NECK".equals(name)) return com.codename1.ai.vision.PoseLandmarks.NECK; + if ("NOSE".equals(name)) return com.codename1.ai.vision.PoseLandmarks.NOSE; + if ("RIGHT_ANKLE".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_ANKLE; + if ("RIGHT_EAR".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_EAR; + if ("RIGHT_ELBOW".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_ELBOW; + if ("RIGHT_EYE".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_EYE; + if ("RIGHT_EYE_INNER".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_EYE_INNER; + if ("RIGHT_EYE_OUTER".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_EYE_OUTER; + if ("RIGHT_FOOT_INDEX".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_FOOT_INDEX; + if ("RIGHT_HEEL".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_HEEL; + if ("RIGHT_HIP".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_HIP; + if ("RIGHT_INDEX".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_INDEX; + if ("RIGHT_KNEE".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_KNEE; + if ("RIGHT_MOUTH".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_MOUTH; + if ("RIGHT_PINKY".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_PINKY; + if ("RIGHT_SHOULDER".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_SHOULDER; + if ("RIGHT_THUMB".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_THUMB; + if ("RIGHT_WRIST".equals(name)) return com.codename1.ai.vision.PoseLandmarks.RIGHT_WRIST; + if ("ROOT".equals(name)) return com.codename1.ai.vision.PoseLandmarks.ROOT; + if ("UNKNOWN".equals(name)) return com.codename1.ai.vision.PoseLandmarks.UNKNOWN; + throw unsupportedStaticField(com.codename1.ai.vision.PoseLandmarks.class, name); + } + + private static Object getStaticField3(String name) throws Exception { if ("EMPTY".equals(name)) return com.codename1.ai.vision.TextRecognitionResult.EMPTY; throw unsupportedStaticField(com.codename1.ai.vision.TextRecognitionResult.class, name); } - private static Object getStaticField1(String name) throws Exception { + private static Object getStaticField4(String name) throws Exception { + if ("BASELINE".equals(name)) return com.codename1.ai.vision.VisionCameraView.BASELINE; + if ("BOTTOM".equals(name)) return com.codename1.ai.vision.VisionCameraView.BOTTOM; + if ("BRB_CENTER_OFFSET".equals(name)) return com.codename1.ai.vision.VisionCameraView.BRB_CENTER_OFFSET; + if ("BRB_CONSTANT_ASCENT".equals(name)) return com.codename1.ai.vision.VisionCameraView.BRB_CONSTANT_ASCENT; + if ("BRB_CONSTANT_DESCENT".equals(name)) return com.codename1.ai.vision.VisionCameraView.BRB_CONSTANT_DESCENT; + if ("BRB_OTHER".equals(name)) return com.codename1.ai.vision.VisionCameraView.BRB_OTHER; + if ("CENTER".equals(name)) return com.codename1.ai.vision.VisionCameraView.CENTER; + if ("CROSSHAIR_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.CROSSHAIR_CURSOR; + if ("DEFAULT_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.DEFAULT_CURSOR; + if ("DRAG_REGION_IMMEDIATELY_DRAG_X".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_IMMEDIATELY_DRAG_X; + if ("DRAG_REGION_IMMEDIATELY_DRAG_XY".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_IMMEDIATELY_DRAG_XY; + if ("DRAG_REGION_IMMEDIATELY_DRAG_Y".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_IMMEDIATELY_DRAG_Y; + if ("DRAG_REGION_LIKELY_DRAG_X".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_LIKELY_DRAG_X; + if ("DRAG_REGION_LIKELY_DRAG_XY".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_LIKELY_DRAG_XY; + if ("DRAG_REGION_LIKELY_DRAG_Y".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_LIKELY_DRAG_Y; + if ("DRAG_REGION_NOT_DRAGGABLE".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_NOT_DRAGGABLE; + if ("DRAG_REGION_POSSIBLE_DRAG_X".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_POSSIBLE_DRAG_X; + if ("DRAG_REGION_POSSIBLE_DRAG_XY".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_POSSIBLE_DRAG_XY; + if ("DRAG_REGION_POSSIBLE_DRAG_Y".equals(name)) return com.codename1.ai.vision.VisionCameraView.DRAG_REGION_POSSIBLE_DRAG_Y; + if ("E_RESIZE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.E_RESIZE_CURSOR; + if ("HAND_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.HAND_CURSOR; + if ("LEFT".equals(name)) return com.codename1.ai.vision.VisionCameraView.LEFT; + if ("MOVE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.MOVE_CURSOR; + if ("NE_RESIZE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.NE_RESIZE_CURSOR; + if ("NW_RESIZE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.NW_RESIZE_CURSOR; + if ("N_RESIZE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.N_RESIZE_CURSOR; + if ("RIGHT".equals(name)) return com.codename1.ai.vision.VisionCameraView.RIGHT; + if ("SE_RESIZE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.SE_RESIZE_CURSOR; + if ("SW_RESIZE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.SW_RESIZE_CURSOR; + if ("S_RESIZE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.S_RESIZE_CURSOR; + if ("TEXT_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.TEXT_CURSOR; + if ("TOP".equals(name)) return com.codename1.ai.vision.VisionCameraView.TOP; + if ("WAIT_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.WAIT_CURSOR; + if ("W_RESIZE_CURSOR".equals(name)) return com.codename1.ai.vision.VisionCameraView.W_RESIZE_CURSOR; + throw unsupportedStaticField(com.codename1.ai.vision.VisionCameraView.class, name); + } + + private static Object getStaticField5(String name) throws Exception { if ("BACKEND_ERROR".equals(name)) return com.codename1.ai.vision.VisionException.BACKEND_ERROR; if ("CANCELLED".equals(name)) return com.codename1.ai.vision.VisionException.CANCELLED; if ("INVALID_IMAGE".equals(name)) return com.codename1.ai.vision.VisionException.INVALID_IMAGE; @@ -1014,7 +3462,7 @@ private static Object getStaticField1(String name) throws Exception { throw unsupportedStaticField(com.codename1.ai.vision.VisionException.class, name); } - private static Object getStaticField2(String name) throws Exception { + private static Object getStaticField6(String name) throws Exception { if ("BARCODE_SCANNING".equals(name)) return com.codename1.ai.vision.VisionFeature.BARCODE_SCANNING; if ("DOCUMENT_SCANNING".equals(name)) return com.codename1.ai.vision.VisionFeature.DOCUMENT_SCANNING; if ("FACE_DETECTION".equals(name)) return com.codename1.ai.vision.VisionFeature.FACE_DETECTION; @@ -1025,7 +3473,7 @@ private static Object getStaticField2(String name) throws Exception { throw unsupportedStaticField(com.codename1.ai.vision.VisionFeature.class, name); } - private static Object getStaticField3(String name) throws Exception { + private static Object getStaticField7(String name) throws Exception { if ("EMPTY".equals(name)) return com.codename1.ai.vision.VisionRect.EMPTY; throw unsupportedStaticField(com.codename1.ai.vision.VisionRect.class, name); } diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_annotations.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_annotations.java index 810d22da378..c464ef3bf8f 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_annotations.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_annotations.java @@ -52,6 +52,9 @@ public static Class findClassBySimpleName(String simpleName) { private static Class findClassChunk0(String simpleName) { + if ("AppIntent".equals(simpleName)) { + return com.codename1.annotations.AppIntent.class; + } if ("Async".equals(simpleName)) { return com.codename1.annotations.Async.class; } @@ -88,6 +91,24 @@ private static Class findClassChunk0(String simpleName) { if ("Entity".equals(simpleName)) { return com.codename1.annotations.Entity.class; } + if ("EntityId".equals(simpleName)) { + return com.codename1.annotations.EntityId.class; + } + if ("EntityImage".equals(simpleName)) { + return com.codename1.annotations.EntityImage.class; + } + if ("EntityQuery".equals(simpleName)) { + return com.codename1.annotations.EntityQuery.class; + } + if ("Kind".equals(simpleName)) { + return com.codename1.annotations.EntityQuery.Kind.class; + } + if ("EntitySubtitle".equals(simpleName)) { + return com.codename1.annotations.EntitySubtitle.class; + } + if ("EntityTitle".equals(simpleName)) { + return com.codename1.annotations.EntityTitle.class; + } if ("ExistIn".equals(simpleName)) { return com.codename1.annotations.ExistIn.class; } @@ -97,6 +118,12 @@ private static Class findClassChunk0(String simpleName) { if ("Id".equals(simpleName)) { return com.codename1.annotations.Id.class; } + if ("IntentEntity".equals(simpleName)) { + return com.codename1.annotations.IntentEntity.class; + } + if ("IntentParam".equals(simpleName)) { + return com.codename1.annotations.IntentParam.class; + } if ("JsonIgnore".equals(simpleName)) { return com.codename1.annotations.JsonIgnore.class; } @@ -160,142 +187,170 @@ public static Object invokeStatic(Class type, String name, Object[] args) thr public static Object invoke(Object target, String name, Object[] args) throws Exception { Object[] safeArgs = safeArgs(args); CN1AccessException unsupported = null; + if (target instanceof com.codename1.annotations.AppIntent) { + try { + return invoke0((com.codename1.annotations.AppIntent) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } if (target instanceof com.codename1.annotations.Bind) { try { - return invoke0((com.codename1.annotations.Bind) target, name, safeArgs); + return invoke1((com.codename1.annotations.Bind) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Column) { try { - return invoke1((com.codename1.annotations.Column) target, name, safeArgs); + return invoke2((com.codename1.annotations.Column) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Concrete) { try { - return invoke2((com.codename1.annotations.Concrete) target, name, safeArgs); + return invoke3((com.codename1.annotations.Concrete) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Email) { try { - return invoke3((com.codename1.annotations.Email) target, name, safeArgs); + return invoke4((com.codename1.annotations.Email) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Entity) { try { - return invoke4((com.codename1.annotations.Entity) target, name, safeArgs); + return invoke5((com.codename1.annotations.Entity) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.EntityQuery) { + try { + return invoke6((com.codename1.annotations.EntityQuery) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.ExistIn) { try { - return invoke5((com.codename1.annotations.ExistIn) target, name, safeArgs); + return invoke7((com.codename1.annotations.ExistIn) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Id) { try { - return invoke6((com.codename1.annotations.Id) target, name, safeArgs); + return invoke8((com.codename1.annotations.Id) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.IntentEntity) { + try { + return invoke9((com.codename1.annotations.IntentEntity) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.IntentParam) { + try { + return invoke10((com.codename1.annotations.IntentParam) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.JsonProperty) { try { - return invoke7((com.codename1.annotations.JsonProperty) target, name, safeArgs); + return invoke11((com.codename1.annotations.JsonProperty) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Length) { try { - return invoke8((com.codename1.annotations.Length) target, name, safeArgs); + return invoke12((com.codename1.annotations.Length) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Numeric) { try { - return invoke9((com.codename1.annotations.Numeric) target, name, safeArgs); + return invoke13((com.codename1.annotations.Numeric) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Regex) { try { - return invoke10((com.codename1.annotations.Regex) target, name, safeArgs); + return invoke14((com.codename1.annotations.Regex) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Required) { try { - return invoke11((com.codename1.annotations.Required) target, name, safeArgs); + return invoke15((com.codename1.annotations.Required) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Route) { try { - return invoke12((com.codename1.annotations.Route) target, name, safeArgs); + return invoke16((com.codename1.annotations.Route) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Route.Routes) { try { - return invoke13((com.codename1.annotations.Route.Routes) target, name, safeArgs); + return invoke17((com.codename1.annotations.Route.Routes) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.RouteParam) { try { - return invoke14((com.codename1.annotations.RouteParam) target, name, safeArgs); + return invoke18((com.codename1.annotations.RouteParam) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Url) { try { - return invoke15((com.codename1.annotations.Url) target, name, safeArgs); + return invoke19((com.codename1.annotations.Url) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.Validate) { try { - return invoke16((com.codename1.annotations.Validate) target, name, safeArgs); + return invoke20((com.codename1.annotations.Validate) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.XmlAttribute) { try { - return invoke17((com.codename1.annotations.XmlAttribute) target, name, safeArgs); + return invoke21((com.codename1.annotations.XmlAttribute) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.XmlElement) { try { - return invoke18((com.codename1.annotations.XmlElement) target, name, safeArgs); + return invoke22((com.codename1.annotations.XmlElement) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.annotations.XmlRoot) { try { - return invoke19((com.codename1.annotations.XmlRoot) target, name, safeArgs); + return invoke23((com.codename1.annotations.XmlRoot) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } @@ -306,7 +361,61 @@ public static Object invoke(Object target, String name, Object[] args) throws Ex throw unsupportedInstance(target, name, safeArgs); } - private static Object invoke0(com.codename1.annotations.Bind typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke0(com.codename1.annotations.AppIntent typedTarget, String name, Object[] safeArgs) throws Exception { + if ("description".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.description(); + } + } + if ("destructive".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.destructive(); + } + } + if ("discoverable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.discoverable(); + } + } + if ("exposure".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.exposure(); + } + } + if ("headless".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.headless(); + } + } + if ("opensRoute".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.opensRoute(); + } + } + if ("phrases".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.phrases(); + } + } + if ("timeoutSeconds".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.timeoutSeconds(); + } + } + if ("title".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.title(); + } + } + if ("value".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.value(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke1(com.codename1.annotations.Bind typedTarget, String name, Object[] safeArgs) throws Exception { if ("attr".equals(name)) { if (safeArgs.length == 0) { return typedTarget.attr(); @@ -335,7 +444,7 @@ private static Object invoke0(com.codename1.annotations.Bind typedTarget, String throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke1(com.codename1.annotations.Column typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke2(com.codename1.annotations.Column typedTarget, String name, Object[] safeArgs) throws Exception { if ("name".equals(name)) { if (safeArgs.length == 0) { return typedTarget.name(); @@ -354,7 +463,7 @@ private static Object invoke1(com.codename1.annotations.Column typedTarget, Stri throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke2(com.codename1.annotations.Concrete typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke3(com.codename1.annotations.Concrete typedTarget, String name, Object[] safeArgs) throws Exception { if ("linux".equals(name)) { if (safeArgs.length == 0) { return typedTarget.linux(); @@ -373,7 +482,7 @@ private static Object invoke2(com.codename1.annotations.Concrete typedTarget, St throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke3(com.codename1.annotations.Email typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke4(com.codename1.annotations.Email typedTarget, String name, Object[] safeArgs) throws Exception { if ("message".equals(name)) { if (safeArgs.length == 0) { return typedTarget.message(); @@ -382,7 +491,7 @@ private static Object invoke3(com.codename1.annotations.Email typedTarget, Strin throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke4(com.codename1.annotations.Entity typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke5(com.codename1.annotations.Entity typedTarget, String name, Object[] safeArgs) throws Exception { if ("table".equals(name)) { if (safeArgs.length == 0) { return typedTarget.table(); @@ -391,7 +500,16 @@ private static Object invoke4(com.codename1.annotations.Entity typedTarget, Stri throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke5(com.codename1.annotations.ExistIn typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke6(com.codename1.annotations.EntityQuery typedTarget, String name, Object[] safeArgs) throws Exception { + if ("value".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.value(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke7(com.codename1.annotations.ExistIn typedTarget, String name, Object[] safeArgs) throws Exception { if ("caseSensitive".equals(name)) { if (safeArgs.length == 0) { return typedTarget.caseSensitive(); @@ -410,7 +528,7 @@ private static Object invoke5(com.codename1.annotations.ExistIn typedTarget, Str throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke6(com.codename1.annotations.Id typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke8(com.codename1.annotations.Id typedTarget, String name, Object[] safeArgs) throws Exception { if ("autoIncrement".equals(name)) { if (safeArgs.length == 0) { return typedTarget.autoIncrement(); @@ -419,7 +537,17 @@ private static Object invoke6(com.codename1.annotations.Id typedTarget, String n throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke7(com.codename1.annotations.JsonProperty typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke9(com.codename1.annotations.IntentEntity typedTarget, String name, Object[] safeArgs) throws Exception { + if ("indexed".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.indexed(); + } + } + if ("title".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.title(); + } + } if ("value".equals(name)) { if (safeArgs.length == 0) { return typedTarget.value(); @@ -428,7 +556,45 @@ private static Object invoke7(com.codename1.annotations.JsonProperty typedTarget throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke8(com.codename1.annotations.Length typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke10(com.codename1.annotations.IntentParam typedTarget, String name, Object[] safeArgs) throws Exception { + if ("defaultValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.defaultValue(); + } + } + if ("options".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.options(); + } + } + if ("required".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.required(); + } + } + if ("title".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.title(); + } + } + if ("value".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.value(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke11(com.codename1.annotations.JsonProperty typedTarget, String name, Object[] safeArgs) throws Exception { + if ("value".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.value(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke12(com.codename1.annotations.Length typedTarget, String name, Object[] safeArgs) throws Exception { if ("message".equals(name)) { if (safeArgs.length == 0) { return typedTarget.message(); @@ -442,7 +608,7 @@ private static Object invoke8(com.codename1.annotations.Length typedTarget, Stri throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke9(com.codename1.annotations.Numeric typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke13(com.codename1.annotations.Numeric typedTarget, String name, Object[] safeArgs) throws Exception { if ("decimal".equals(name)) { if (safeArgs.length == 0) { return typedTarget.decimal(); @@ -466,7 +632,7 @@ private static Object invoke9(com.codename1.annotations.Numeric typedTarget, Str throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke10(com.codename1.annotations.Regex typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke14(com.codename1.annotations.Regex typedTarget, String name, Object[] safeArgs) throws Exception { if ("message".equals(name)) { if (safeArgs.length == 0) { return typedTarget.message(); @@ -480,7 +646,7 @@ private static Object invoke10(com.codename1.annotations.Regex typedTarget, Stri throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke11(com.codename1.annotations.Required typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke15(com.codename1.annotations.Required typedTarget, String name, Object[] safeArgs) throws Exception { if ("message".equals(name)) { if (safeArgs.length == 0) { return typedTarget.message(); @@ -489,7 +655,7 @@ private static Object invoke11(com.codename1.annotations.Required typedTarget, S throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke12(com.codename1.annotations.Route typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke16(com.codename1.annotations.Route typedTarget, String name, Object[] safeArgs) throws Exception { if ("value".equals(name)) { if (safeArgs.length == 0) { return typedTarget.value(); @@ -498,7 +664,7 @@ private static Object invoke12(com.codename1.annotations.Route typedTarget, Stri throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke13(com.codename1.annotations.Route.Routes typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke17(com.codename1.annotations.Route.Routes typedTarget, String name, Object[] safeArgs) throws Exception { if ("value".equals(name)) { if (safeArgs.length == 0) { return typedTarget.value(); @@ -507,7 +673,7 @@ private static Object invoke13(com.codename1.annotations.Route.Routes typedTarge throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke14(com.codename1.annotations.RouteParam typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke18(com.codename1.annotations.RouteParam typedTarget, String name, Object[] safeArgs) throws Exception { if ("required".equals(name)) { if (safeArgs.length == 0) { return typedTarget.required(); @@ -521,7 +687,7 @@ private static Object invoke14(com.codename1.annotations.RouteParam typedTarget, throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke15(com.codename1.annotations.Url typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke19(com.codename1.annotations.Url typedTarget, String name, Object[] safeArgs) throws Exception { if ("message".equals(name)) { if (safeArgs.length == 0) { return typedTarget.message(); @@ -530,7 +696,7 @@ private static Object invoke15(com.codename1.annotations.Url typedTarget, String throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke16(com.codename1.annotations.Validate typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke20(com.codename1.annotations.Validate typedTarget, String name, Object[] safeArgs) throws Exception { if ("value".equals(name)) { if (safeArgs.length == 0) { return typedTarget.value(); @@ -539,7 +705,7 @@ private static Object invoke16(com.codename1.annotations.Validate typedTarget, S throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke17(com.codename1.annotations.XmlAttribute typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke21(com.codename1.annotations.XmlAttribute typedTarget, String name, Object[] safeArgs) throws Exception { if ("value".equals(name)) { if (safeArgs.length == 0) { return typedTarget.value(); @@ -548,7 +714,7 @@ private static Object invoke17(com.codename1.annotations.XmlAttribute typedTarge throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke18(com.codename1.annotations.XmlElement typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke22(com.codename1.annotations.XmlElement typedTarget, String name, Object[] safeArgs) throws Exception { if ("value".equals(name)) { if (safeArgs.length == 0) { return typedTarget.value(); @@ -557,7 +723,7 @@ private static Object invoke18(com.codename1.annotations.XmlElement typedTarget, throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke19(com.codename1.annotations.XmlRoot typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke23(com.codename1.annotations.XmlRoot typedTarget, String name, Object[] safeArgs) throws Exception { if ("value".equals(name)) { if (safeArgs.length == 0) { return typedTarget.value(); @@ -567,9 +733,17 @@ private static Object invoke19(com.codename1.annotations.XmlRoot typedTarget, St } public static Object getStaticField(Class type, String name) throws Exception { + if (type == com.codename1.annotations.EntityQuery.Kind.class) return getStaticField0(name); throw unsupportedStaticField(type, name); } + private static Object getStaticField0(String name) throws Exception { + if ("BY_ID".equals(name)) return com.codename1.annotations.EntityQuery.Kind.BY_ID; + if ("SEARCH".equals(name)) return com.codename1.annotations.EntityQuery.Kind.SEARCH; + if ("SUGGESTED".equals(name)) return com.codename1.annotations.EntityQuery.Kind.SUGGESTED; + throw unsupportedStaticField(com.codename1.annotations.EntityQuery.Kind.class, name); + } + public static Object getField(Object target, String name) throws Exception { throw unsupportedField(target, name); } diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_annotations_buildhints.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_annotations_buildhints.java new file mode 100644 index 00000000000..3dbf762dd66 --- /dev/null +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_annotations_buildhints.java @@ -0,0 +1,1153 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package bsh.cn1.gen; + +import bsh.cn1.CN1AccessException; + +public final class GeneratedAccess_com_codename1_annotations_buildhints { + private GeneratedAccess_com_codename1_annotations_buildhints() { + } + + public static Class findClass(String name) { + if (name == null) { + return null; + } + int dot = name.lastIndexOf('.'); + int dollar = name.lastIndexOf('$'); + int sep = dot > dollar ? dot : dollar; + if (sep < 0 || sep == name.length() - 1) { + return null; + } + return findClassBySimpleName(name.substring(sep + 1)); + } + + public static Class findClassBySimpleName(String simpleName) { + Class found0 = findClassChunk0(simpleName); + if (found0 != null) { + return found0; + } + return null; + } + + + private static Class findClassChunk0(String simpleName) { + if ("Android".equals(simpleName)) { + return com.codename1.annotations.buildhints.Android.class; + } + if ("AndroidThemeMode".equals(simpleName)) { + return com.codename1.annotations.buildhints.AndroidThemeMode.class; + } + if ("Build".equals(simpleName)) { + return com.codename1.annotations.buildhints.Build.class; + } + if ("Desktop".equals(simpleName)) { + return com.codename1.annotations.buildhints.Desktop.class; + } + if ("DesktopTitleBar".equals(simpleName)) { + return com.codename1.annotations.buildhints.DesktopTitleBar.class; + } + if ("HardenControlFlow".equals(simpleName)) { + return com.codename1.annotations.buildhints.HardenControlFlow.class; + } + if ("HardenLevel".equals(simpleName)) { + return com.codename1.annotations.buildhints.HardenLevel.class; + } + if ("HardenStrings".equals(simpleName)) { + return com.codename1.annotations.buildhints.HardenStrings.class; + } + if ("Hardening".equals(simpleName)) { + return com.codename1.annotations.buildhints.Hardening.class; + } + if ("InstallLocation".equals(simpleName)) { + return com.codename1.annotations.buildhints.InstallLocation.class; + } + if ("Ios".equals(simpleName)) { + return com.codename1.annotations.buildhints.Ios.class; + } + if ("IosDependencyManager".equals(simpleName)) { + return com.codename1.annotations.buildhints.IosDependencyManager.class; + } + if ("IosPrivacy".equals(simpleName)) { + return com.codename1.annotations.buildhints.IosPrivacy.class; + } + if ("IosProjectType".equals(simpleName)) { + return com.codename1.annotations.buildhints.IosProjectType.class; + } + if ("IosThemeMode".equals(simpleName)) { + return com.codename1.annotations.buildhints.IosThemeMode.class; + } + if ("NativeThemeMode".equals(simpleName)) { + return com.codename1.annotations.buildhints.NativeThemeMode.class; + } + if ("OnDeviceDebug".equals(simpleName)) { + return com.codename1.annotations.buildhints.OnDeviceDebug.class; + } + return null; + } + public static Object construct(Class type, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + throw unsupportedConstruct(type, safeArgs); + } + + public static Object invokeStatic(Class type, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + throw unsupportedStatic(type, name, safeArgs); + } + + public static Object invoke(Object target, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + CN1AccessException unsupported = null; + if (target instanceof com.codename1.annotations.buildhints.AndroidThemeMode) { + try { + return invoke0((com.codename1.annotations.buildhints.AndroidThemeMode) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.DesktopTitleBar) { + try { + return invoke1((com.codename1.annotations.buildhints.DesktopTitleBar) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.HardenControlFlow) { + try { + return invoke2((com.codename1.annotations.buildhints.HardenControlFlow) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.HardenLevel) { + try { + return invoke3((com.codename1.annotations.buildhints.HardenLevel) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.HardenStrings) { + try { + return invoke4((com.codename1.annotations.buildhints.HardenStrings) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.InstallLocation) { + try { + return invoke5((com.codename1.annotations.buildhints.InstallLocation) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.IosDependencyManager) { + try { + return invoke6((com.codename1.annotations.buildhints.IosDependencyManager) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.IosProjectType) { + try { + return invoke7((com.codename1.annotations.buildhints.IosProjectType) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.IosThemeMode) { + try { + return invoke8((com.codename1.annotations.buildhints.IosThemeMode) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.NativeThemeMode) { + try { + return invoke9((com.codename1.annotations.buildhints.NativeThemeMode) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.Android) { + try { + return invoke10((com.codename1.annotations.buildhints.Android) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.Build) { + try { + return invoke11((com.codename1.annotations.buildhints.Build) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.Desktop) { + try { + return invoke12((com.codename1.annotations.buildhints.Desktop) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.Hardening) { + try { + return invoke13((com.codename1.annotations.buildhints.Hardening) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.Ios) { + try { + return invoke14((com.codename1.annotations.buildhints.Ios) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.IosPrivacy) { + try { + return invoke15((com.codename1.annotations.buildhints.IosPrivacy) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.annotations.buildhints.OnDeviceDebug) { + try { + return invoke16((com.codename1.annotations.buildhints.OnDeviceDebug) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (unsupported != null) { + throw unsupported; + } + throw unsupportedInstance(target, name, safeArgs); + } + + private static Object invoke0(com.codename1.annotations.buildhints.AndroidThemeMode typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke1(com.codename1.annotations.buildhints.DesktopTitleBar typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke2(com.codename1.annotations.buildhints.HardenControlFlow typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke3(com.codename1.annotations.buildhints.HardenLevel typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke4(com.codename1.annotations.buildhints.HardenStrings typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke5(com.codename1.annotations.buildhints.InstallLocation typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke6(com.codename1.annotations.buildhints.IosDependencyManager typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke7(com.codename1.annotations.buildhints.IosProjectType typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke8(com.codename1.annotations.buildhints.IosThemeMode typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke9(com.codename1.annotations.buildhints.NativeThemeMode typedTarget, String name, Object[] safeArgs) throws Exception { + if ("wireValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wireValue(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke10(com.codename1.annotations.buildhints.Android typedTarget, String name, Object[] safeArgs) throws Exception { + if ("activityLaunchMode".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.activityLaunchMode(); + } + } + if ("appBundle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.appBundle(); + } + } + if ("buildToolsVersion".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.buildToolsVersion(); + } + } + if ("captureRecord".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.captureRecord(); + } + } + if ("debug".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.debug(); + } + } + if ("disableR8".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.disableR8(); + } + } + if ("enableProguard".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.enableProguard(); + } + } + if ("gradleDep".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.gradleDep(); + } + } + if ("hideStatusBar".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.hideStatusBar(); + } + } + if ("installLocation".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.installLocation(); + } + } + if ("licenseKey".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.licenseKey(); + } + } + if ("minSdkVersion".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.minSdkVersion(); + } + } + if ("multidex".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.multidex(); + } + } + if ("newFirebaseMessaging".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.newFirebaseMessaging(); + } + } + if ("proguardKeep".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.proguardKeep(); + } + } + if ("release".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.release(); + } + } + if ("repositories".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.repositories(); + } + } + if ("targetSDKVersion".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.targetSDKVersion(); + } + } + if ("themeMode".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.themeMode(); + } + } + if ("topDependency".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.topDependency(); + } + } + if ("useAndroidX".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.useAndroidX(); + } + } + if ("xapplication".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.xapplication(); + } + } + if ("xgradle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.xgradle(); + } + } + if ("xpermissions".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.xpermissions(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke11(com.codename1.annotations.buildhints.Build typedTarget, String name, Object[] safeArgs) throws Exception { + if ("facebookAppId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.facebookAppId(); + } + } + if ("gcmSenderId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.gcmSenderId(); + } + } + if ("nativeTheme".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.nativeTheme(); + } + } + if ("noExtraResources".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.noExtraResources(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke12(com.codename1.annotations.buildhints.Desktop typedTarget, String name, Object[] safeArgs) throws Exception { + if ("adaptToRetina".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.adaptToRetina(); + } + } + if ("fullscreen".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.fullscreen(); + } + } + if ("height".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.height(); + } + } + if ("interactiveScrollbars".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.interactiveScrollbars(); + } + } + if ("resizable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.resizable(); + } + } + if ("titleBar".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.titleBar(); + } + } + if ("width".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.width(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke13(com.codename1.annotations.buildhints.Hardening typedTarget, String name, Object[] safeArgs) throws Exception { + if ("allowUnhardenedLocalBuild".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.allowUnhardenedLocalBuild(); + } + } + if ("controlFlow".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.controlFlow(); + } + } + if ("keep".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.keep(); + } + } + if ("level".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.level(); + } + } + if ("rename".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.rename(); + } + } + if ("strings".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.strings(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke14(com.codename1.annotations.buildhints.Ios typedTarget, String name, Object[] safeArgs) throws Exception { + if ("addLibs".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.addLibs(); + } + } + if ("applicationQueriesSchemes".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.applicationQueriesSchemes(); + } + } + if ("beforeFinishLaunching".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.beforeFinishLaunching(); + } + } + if ("bundleVersion".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.bundleVersion(); + } + } + if ("dependencyManager".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.dependencyManager(); + } + } + if ("deploymentTarget".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.deploymentTarget(); + } + } + if ("glAppDelegateHeader".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.glAppDelegateHeader(); + } + } + if ("includePush".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.includePush(); + } + } + if ("interfaceOrientation".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.interfaceOrientation(); + } + } + if ("minDeploymentTarget".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.minDeploymentTarget(); + } + } + if ("newStorageLocation".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.newStorageLocation(); + } + } + if ("objC".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.objC(); + } + } + if ("plistInject".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.plistInject(); + } + } + if ("pods".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.pods(); + } + } + if ("podsPlatform".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.podsPlatform(); + } + } + if ("podsSources".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.podsSources(); + } + } + if ("prerenderedIcon".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.prerenderedIcon(); + } + } + if ("projectType".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.projectType(); + } + } + if ("spmPackages".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.spmPackages(); + } + } + if ("teamId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.teamId(); + } + } + if ("themeMode".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.themeMode(); + } + } + if ("uiscene".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.uiscene(); + } + } + if ("urlScheme".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.urlScheme(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke15(com.codename1.annotations.buildhints.IosPrivacy typedTarget, String name, Object[] safeArgs) throws Exception { + if ("calendarsFullAccessUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.calendarsFullAccessUsageDescription(); + } + } + if ("calendarsUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.calendarsUsageDescription(); + } + } + if ("calendarsWriteOnlyAccessUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.calendarsWriteOnlyAccessUsageDescription(); + } + } + if ("cameraUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.cameraUsageDescription(); + } + } + if ("healthShareUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.healthShareUsageDescription(); + } + } + if ("healthUpdateUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.healthUpdateUsageDescription(); + } + } + if ("localNetworkUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.localNetworkUsageDescription(); + } + } + if ("locationAlwaysAndWhenInUseUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.locationAlwaysAndWhenInUseUsageDescription(); + } + } + if ("locationAlwaysUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.locationAlwaysUsageDescription(); + } + } + if ("locationWhenInUseUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.locationWhenInUseUsageDescription(); + } + } + if ("microphoneUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.microphoneUsageDescription(); + } + } + if ("remindersFullAccessUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.remindersFullAccessUsageDescription(); + } + } + if ("remindersUsageDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.remindersUsageDescription(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke16(com.codename1.annotations.buildhints.OnDeviceDebug typedTarget, String name, Object[] safeArgs) throws Exception { + if ("android".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.android(); + } + } + if ("ios".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.ios(); + } + } + if ("iosProxyHost".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.iosProxyHost(); + } + } + if ("iosProxyPort".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.iosProxyPort(); + } + } + if ("iosWaitForAttach".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.iosWaitForAttach(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + public static Object getStaticField(Class type, String name) throws Exception { + if (type == com.codename1.annotations.buildhints.AndroidThemeMode.class) return getStaticField0(name); + if (type == com.codename1.annotations.buildhints.DesktopTitleBar.class) return getStaticField1(name); + if (type == com.codename1.annotations.buildhints.HardenControlFlow.class) return getStaticField2(name); + if (type == com.codename1.annotations.buildhints.HardenLevel.class) return getStaticField3(name); + if (type == com.codename1.annotations.buildhints.HardenStrings.class) return getStaticField4(name); + if (type == com.codename1.annotations.buildhints.InstallLocation.class) return getStaticField5(name); + if (type == com.codename1.annotations.buildhints.IosDependencyManager.class) return getStaticField6(name); + if (type == com.codename1.annotations.buildhints.IosProjectType.class) return getStaticField7(name); + if (type == com.codename1.annotations.buildhints.IosThemeMode.class) return getStaticField8(name); + if (type == com.codename1.annotations.buildhints.NativeThemeMode.class) return getStaticField9(name); + throw unsupportedStaticField(type, name); + } + + private static Object getStaticField0(String name) throws Exception { + if ("AUTO".equals(name)) return com.codename1.annotations.buildhints.AndroidThemeMode.AUTO; + if ("HOLOLIGHT".equals(name)) return com.codename1.annotations.buildhints.AndroidThemeMode.HOLOLIGHT; + if ("LEGACY".equals(name)) return com.codename1.annotations.buildhints.AndroidThemeMode.LEGACY; + if ("MODERN".equals(name)) return com.codename1.annotations.buildhints.AndroidThemeMode.MODERN; + throw unsupportedStaticField(com.codename1.annotations.buildhints.AndroidThemeMode.class, name); + } + + private static Object getStaticField1(String name) throws Exception { + if ("CUSTOM".equals(name)) return com.codename1.annotations.buildhints.DesktopTitleBar.CUSTOM; + if ("NATIVE".equals(name)) return com.codename1.annotations.buildhints.DesktopTitleBar.NATIVE; + if ("TOOLBAR".equals(name)) return com.codename1.annotations.buildhints.DesktopTitleBar.TOOLBAR; + throw unsupportedStaticField(com.codename1.annotations.buildhints.DesktopTitleBar.class, name); + } + + private static Object getStaticField2(String name) throws Exception { + if ("OFF".equals(name)) return com.codename1.annotations.buildhints.HardenControlFlow.OFF; + if ("ON".equals(name)) return com.codename1.annotations.buildhints.HardenControlFlow.ON; + throw unsupportedStaticField(com.codename1.annotations.buildhints.HardenControlFlow.class, name); + } + + private static Object getStaticField3(String name) throws Exception { + if ("AGGRESSIVE".equals(name)) return com.codename1.annotations.buildhints.HardenLevel.AGGRESSIVE; + if ("OFF".equals(name)) return com.codename1.annotations.buildhints.HardenLevel.OFF; + if ("PARANOID".equals(name)) return com.codename1.annotations.buildhints.HardenLevel.PARANOID; + if ("STANDARD".equals(name)) return com.codename1.annotations.buildhints.HardenLevel.STANDARD; + throw unsupportedStaticField(com.codename1.annotations.buildhints.HardenLevel.class, name); + } + + private static Object getStaticField4(String name) throws Exception { + if ("ALL".equals(name)) return com.codename1.annotations.buildhints.HardenStrings.ALL; + if ("CONSTANTS".equals(name)) return com.codename1.annotations.buildhints.HardenStrings.CONSTANTS; + if ("OFF".equals(name)) return com.codename1.annotations.buildhints.HardenStrings.OFF; + throw unsupportedStaticField(com.codename1.annotations.buildhints.HardenStrings.class, name); + } + + private static Object getStaticField5(String name) throws Exception { + if ("AUTO".equals(name)) return com.codename1.annotations.buildhints.InstallLocation.AUTO; + if ("INTERNAL_ONLY".equals(name)) return com.codename1.annotations.buildhints.InstallLocation.INTERNAL_ONLY; + if ("PREFER_EXTERNAL".equals(name)) return com.codename1.annotations.buildhints.InstallLocation.PREFER_EXTERNAL; + throw unsupportedStaticField(com.codename1.annotations.buildhints.InstallLocation.class, name); + } + + private static Object getStaticField6(String name) throws Exception { + if ("AUTO".equals(name)) return com.codename1.annotations.buildhints.IosDependencyManager.AUTO; + if ("BOTH".equals(name)) return com.codename1.annotations.buildhints.IosDependencyManager.BOTH; + if ("COCOAPODS".equals(name)) return com.codename1.annotations.buildhints.IosDependencyManager.COCOAPODS; + if ("NONE".equals(name)) return com.codename1.annotations.buildhints.IosDependencyManager.NONE; + if ("SPM".equals(name)) return com.codename1.annotations.buildhints.IosDependencyManager.SPM; + throw unsupportedStaticField(com.codename1.annotations.buildhints.IosDependencyManager.class, name); + } + + private static Object getStaticField7(String name) throws Exception { + if ("IOS".equals(name)) return com.codename1.annotations.buildhints.IosProjectType.IOS; + if ("IPAD".equals(name)) return com.codename1.annotations.buildhints.IosProjectType.IPAD; + if ("IPHONE".equals(name)) return com.codename1.annotations.buildhints.IosProjectType.IPHONE; + throw unsupportedStaticField(com.codename1.annotations.buildhints.IosProjectType.class, name); + } + + private static Object getStaticField8(String name) throws Exception { + if ("AUTO".equals(name)) return com.codename1.annotations.buildhints.IosThemeMode.AUTO; + if ("IOS7".equals(name)) return com.codename1.annotations.buildhints.IosThemeMode.IOS7; + if ("LEGACY".equals(name)) return com.codename1.annotations.buildhints.IosThemeMode.LEGACY; + if ("MODERN".equals(name)) return com.codename1.annotations.buildhints.IosThemeMode.MODERN; + throw unsupportedStaticField(com.codename1.annotations.buildhints.IosThemeMode.class, name); + } + + private static Object getStaticField9(String name) throws Exception { + if ("CUSTOM".equals(name)) return com.codename1.annotations.buildhints.NativeThemeMode.CUSTOM; + if ("LEGACY".equals(name)) return com.codename1.annotations.buildhints.NativeThemeMode.LEGACY; + if ("MODERN".equals(name)) return com.codename1.annotations.buildhints.NativeThemeMode.MODERN; + throw unsupportedStaticField(com.codename1.annotations.buildhints.NativeThemeMode.class, name); + } + + public static Object getField(Object target, String name) throws Exception { + throw unsupportedField(target, name); + } + + public static void setStaticField(Class type, String name, Object value) throws Exception { + throw unsupportedStaticFieldWrite(type, name, value); + } + + public static void setField(Object target, String name, Object value) throws Exception { + throw unsupportedFieldWrite(target, name, value); + } + + private static Object[] safeArgs(Object[] args) { + return args == null ? new Object[0] : args; + } + + private static Object[] adaptArgs(Object[] args, Class[] paramTypes, boolean varArgs) { + if (args == null || args.length == 0) { + return args == null ? new Object[0] : args; + } + Object[] adapted = args.clone(); + if (!varArgs) { + for (int i = 0; i < Math.min(adapted.length, paramTypes.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + return adapted; + } + if (paramTypes.length == 0) { + return adapted; + } + int fixedCount = paramTypes.length - 1; + for (int i = 0; i < Math.min(fixedCount, adapted.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < adapted.length; i++) { + adapted[i] = adaptValue(adapted[i], componentType); + } + return adapted; + } + + private static boolean isSamInterface(Class type) { + if (type == com.codename1.util.OnComplete.class) { + return true; + } + if (type == com.codename1.util.SuccessCallback.class) { + return true; + } + if (type == com.codename1.util.FailureCallback.class) { + return true; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return true; + } + if (type == java.lang.Runnable.class) { + return true; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return true; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return true; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return true; + } + return false; + } + + private static Object adaptLambdaValue(final bsh.cn1.CN1LambdaSupport.LambdaValue lambda, Class type) { + if (type == com.codename1.util.OnComplete.class) { + return new com.codename1.util.OnComplete() { + public void completed(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.SuccessCallback.class) { + return new com.codename1.util.SuccessCallback() { + public void onSucess(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.FailureCallback.class) { + return new com.codename1.util.FailureCallback() { + public void onError(java.lang.Object arg0, java.lang.Throwable arg1, int arg2, java.lang.String arg3) { + try { + lambda.invoke(new Object[]{arg0, arg1, arg2, arg3}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == java.lang.Runnable.class) { + return new java.lang.Runnable() { + public void run() { + try { + lambda.invoke(new Object[0]); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return new com.codename1.ui.events.DataChangedListener() { + public void dataChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return new com.codename1.ui.events.SelectionListener() { + public void selectionChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return new com.codename1.printing.PrintResultListener() { + public void onResult(com.codename1.printing.PrintResult arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + return lambda; + } + + private static Object adaptValue(Object value, Class type) { + if (!(value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue)) { + return value; + } + // Direct fit when LambdaValue already implements the target SAM + // (Runnable, Function, Comparator, ...). + if (type.isInstance(value)) { + return value; + } + return adaptLambdaValue((bsh.cn1.CN1LambdaSupport.LambdaValue) value, type); + } + + private static int toIntValue(Object value) { + if (value instanceof Number) return ((Number) value).intValue(); + if (value instanceof Character) return (int) ((Character) value).charValue(); + throw new ClassCastException("Cannot coerce " + + (value == null ? "null" : value.getClass().getName()) + " to int"); + } + + private static boolean matches(Object[] args, Class[] paramTypes, boolean varArgs) { + if (!varArgs) { + if (args.length != paramTypes.length) { + return false; + } + for (int i = 0; i < paramTypes.length; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + return true; + } + if (paramTypes.length == 0) { + return true; + } + int fixedCount = paramTypes.length - 1; + if (args.length < fixedCount) { + return false; + } + for (int i = 0; i < fixedCount; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < args.length; i++) { + if (!matchesType(args[i], componentType)) { + return false; + } + } + return true; + } + + private static boolean matchesType(Object value, Class type) { + if (type == Object.class) { + return true; + } + if (value == null) { + return !type.isPrimitive(); + } + if (type.isArray()) { + return type.isInstance(value); + } + if ("boolean".equals(type.getName()) || type == Boolean.class) { + return value instanceof Boolean; + } + if ("char".equals(type.getName()) || type == Character.class) { + return value instanceof Character; + } + if ("byte".equals(type.getName()) || type == Byte.class || "short".equals(type.getName()) || type == Short.class + || "int".equals(type.getName()) || type == Integer.class || "long".equals(type.getName()) || type == Long.class + || "float".equals(type.getName()) || type == Float.class || "double".equals(type.getName()) || type == Double.class) { + // Java widens char to int implicitly, so accept Character + // for any int-or-larger numeric slot. + return value instanceof Number || value instanceof Character; + } + if (value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue) { + // LambdaValue implements common SAMs directly (Runnable, + // Function, Predicate, Comparator, ...). Also accept any + // CN1 SAM the listener-bridge knows how to wrap. + return type.isInstance(value) || isSamInterface(type); + } + return type.isInstance(value); + } + + private static CN1AccessException unsupportedConstruct(Class type, Object[] args) { + return new CN1AccessException("Generated constructor dispatch not implemented for " + type.getName() + describeArgs(args)); + } + + private static CN1AccessException unsupportedStatic(Class type, String name, Object[] args) { + return new CN1AccessException("Generated static dispatch not implemented for " + type.getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedInstance(Object target, String name, Object[] args) { + return new CN1AccessException("Generated instance dispatch not implemented for " + target.getClass().getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedStaticField(Class type, String name) { + return new CN1AccessException("Generated static field access not implemented for " + type.getName() + "." + name); + } + + private static CN1AccessException unsupportedField(Object target, String name) { + return new CN1AccessException("Generated field access not implemented for " + target.getClass().getName() + "." + name); + } + + private static CN1AccessException unsupportedStaticFieldWrite(Class type, String name, Object value) { + return new CN1AccessException("Generated static field write not implemented for " + type.getName() + "." + name + " value=" + describeValue(value)); + } + + private static CN1AccessException unsupportedFieldWrite(Object target, String name, Object value) { + return new CN1AccessException("Generated field write not implemented for " + target.getClass().getName() + "." + name + " value=" + describeValue(value)); + } + + private static String describeArgs(Object[] args) { + if (args == null || args.length == 0) { + return "()"; + } + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(describeValue(args[i])); + } + sb.append(')'); + return sb.toString(); + } + + private static String describeValue(Object value) { + return value == null ? "null" : value.getClass().getName(); + } +} diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_crash.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_crash.java index a89227ff424..b6a1894fd65 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_crash.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_crash.java @@ -143,6 +143,12 @@ private static Object invoke0(com.codename1.crash.PiiScrubber typedTarget, Strin return typedTarget.scrubMessage((java.lang.String) adaptedArgs[0]); } } + if ("scrubRawStack".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.scrubRawStack((java.lang.String) adaptedArgs[0]); + } + } throw unsupportedInstance(typedTarget, name, safeArgs); } diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_db.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_db.java index e6cb467b589..f0230a10e9f 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_db.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_db.java @@ -165,6 +165,12 @@ private static Object invokeStatic0(String name, Object[] safeArgs) throws Excep return com.codename1.db.Database.isCustomPathSupported(); } } + if ("isDatabaseBeingDeleted".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.db.Database.isDatabaseBeingDeleted((java.lang.String) adaptedArgs[0]); + } + } if ("isEncrypted".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); @@ -181,6 +187,12 @@ private static Object invokeStatic0(String name, Object[] safeArgs) throws Excep return com.codename1.db.Database.isLegacyBehavior(); } } + if ("normalizeDatabaseKey".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.db.Database.normalizeDatabaseKey((java.lang.String) adaptedArgs[0]); + } + } if ("openOrCreate".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home.java new file mode 100644 index 00000000000..2d9ba9422db --- /dev/null +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home.java @@ -0,0 +1,2561 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package bsh.cn1.gen; + +import bsh.cn1.CN1AccessException; + +public final class GeneratedAccess_com_codename1_home { + private GeneratedAccess_com_codename1_home() { + } + + public static Class findClass(String name) { + if (name == null) { + return null; + } + int dot = name.lastIndexOf('.'); + int dollar = name.lastIndexOf('$'); + int sep = dot > dollar ? dot : dollar; + if (sep < 0 || sep == name.length() - 1) { + return null; + } + return findClassBySimpleName(name.substring(sep + 1)); + } + + public static Class findClassBySimpleName(String simpleName) { + Class found0 = findClassChunk0(simpleName); + if (found0 != null) { + return found0; + } + return null; + } + + + private static Class findClassChunk0(String simpleName) { + if ("Accessory".equals(simpleName)) { + return com.codename1.home.Accessory.class; + } + if ("AccessoryCategory".equals(simpleName)) { + return com.codename1.home.AccessoryCategory.class; + } + if ("AccessoryService".equals(simpleName)) { + return com.codename1.home.AccessoryService.class; + } + if ("AirQualityLevel".equals(simpleName)) { + return com.codename1.home.AirQualityLevel.class; + } + if ("AlarmState".equals(simpleName)) { + return com.codename1.home.AlarmState.class; + } + if ("ChargingState".equals(simpleName)) { + return com.codename1.home.ChargingState.class; + } + if ("DoorState".equals(simpleName)) { + return com.codename1.home.DoorState.class; + } + if ("FanMode".equals(simpleName)) { + return com.codename1.home.FanMode.class; + } + if ("HeatingCoolingMode".equals(simpleName)) { + return com.codename1.home.HeatingCoolingMode.class; + } + if ("HomeAuthorizationStatus".equals(simpleName)) { + return com.codename1.home.HomeAuthorizationStatus.class; + } + if ("HomeAvailability".equals(simpleName)) { + return com.codename1.home.HomeAvailability.class; + } + if ("HomeBackend".equals(simpleName)) { + return com.codename1.home.HomeBackend.class; + } + if ("HomeChangeListener".equals(simpleName)) { + return com.codename1.home.HomeChangeListener.class; + } + if ("HomeConfigurationException".equals(simpleName)) { + return com.codename1.home.HomeConfigurationException.class; + } + if ("HomeError".equals(simpleName)) { + return com.codename1.home.HomeError.class; + } + if ("HomeException".equals(simpleName)) { + return com.codename1.home.HomeException.class; + } + if ("HomeRoom".equals(simpleName)) { + return com.codename1.home.HomeRoom.class; + } + if ("HomeStructure".equals(simpleName)) { + return com.codename1.home.HomeStructure.class; + } + if ("HomeStructureEvent".equals(simpleName)) { + return com.codename1.home.HomeStructureEvent.class; + } + if ("HomeStructureListener".equals(simpleName)) { + return com.codename1.home.HomeStructureListener.class; + } + if ("HomeZone".equals(simpleName)) { + return com.codename1.home.HomeZone.class; + } + if ("LockState".equals(simpleName)) { + return com.codename1.home.LockState.class; + } + if ("PositionState".equals(simpleName)) { + return com.codename1.home.PositionState.class; + } + if ("Scene".equals(simpleName)) { + return com.codename1.home.Scene.class; + } + if ("SceneAction".equals(simpleName)) { + return com.codename1.home.SceneAction.class; + } + if ("SceneType".equals(simpleName)) { + return com.codename1.home.SceneType.class; + } + if ("ServiceType".equals(simpleName)) { + return com.codename1.home.ServiceType.class; + } + if ("SmartHome".equals(simpleName)) { + return com.codename1.home.SmartHome.class; + } + if ("StructureChangeKind".equals(simpleName)) { + return com.codename1.home.StructureChangeKind.class; + } + if ("SubscriptionRequest".equals(simpleName)) { + return com.codename1.home.SubscriptionRequest.class; + } + if ("Trait".equals(simpleName)) { + return com.codename1.home.Trait.class; + } + if ("TraitChangeBatch".equals(simpleName)) { + return com.codename1.home.TraitChangeBatch.class; + } + if ("TraitConstraint".equals(simpleName)) { + return com.codename1.home.TraitConstraint.class; + } + if ("TraitReadRequest".equals(simpleName)) { + return com.codename1.home.TraitReadRequest.class; + } + if ("TraitReading".equals(simpleName)) { + return com.codename1.home.TraitReading.class; + } + if ("TraitSubscription".equals(simpleName)) { + return com.codename1.home.TraitSubscription.class; + } + if ("TraitUnit".equals(simpleName)) { + return com.codename1.home.TraitUnit.class; + } + if ("TraitUnitDimension".equals(simpleName)) { + return com.codename1.home.TraitUnitDimension.class; + } + if ("TraitValue".equals(simpleName)) { + return com.codename1.home.TraitValue.class; + } + if ("TraitValueKind".equals(simpleName)) { + return com.codename1.home.TraitValueKind.class; + } + if ("TraitWrite".equals(simpleName)) { + return com.codename1.home.TraitWrite.class; + } + if ("TraitWriteResult".equals(simpleName)) { + return com.codename1.home.TraitWriteResult.class; + } + return null; + } + public static Object construct(Class type, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + if (type == com.codename1.home.Accessory.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class, com.codename1.home.AccessoryCategory.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Boolean.class, java.lang.String.class, java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class, com.codename1.home.AccessoryCategory.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Boolean.class, java.lang.String.class, java.util.List.class}, false); + return new com.codename1.home.Accessory((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], (com.codename1.home.AccessoryCategory) adaptedArgs[3], (java.lang.String) adaptedArgs[4], (java.lang.String) adaptedArgs[5], (java.lang.String) adaptedArgs[6], ((Boolean) adaptedArgs[7]).booleanValue(), (java.lang.String) adaptedArgs[8], (java.util.List) adaptedArgs[9]); + } + } + if (type == com.codename1.home.AccessoryService.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.ServiceType.class, java.lang.Boolean.class, java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.ServiceType.class, java.lang.Boolean.class, java.util.List.class}, false); + return new com.codename1.home.AccessoryService((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.home.ServiceType) adaptedArgs[2], ((Boolean) adaptedArgs[3]).booleanValue(), (java.util.List) adaptedArgs[4]); + } + } + if (type == com.codename1.home.HomeConfigurationException.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return new com.codename1.home.HomeConfigurationException((java.lang.String) adaptedArgs[0]); + } + } + if (type == com.codename1.home.HomeException.class) { + if (matches(safeArgs, new Class[]{com.codename1.home.HomeError.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeError.class, java.lang.String.class}, false); + return new com.codename1.home.HomeException((com.codename1.home.HomeError) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); + } + if (matches(safeArgs, new Class[]{com.codename1.home.HomeError.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeError.class, java.lang.String.class, java.lang.String.class}, false); + return new com.codename1.home.HomeException((com.codename1.home.HomeError) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); + } + if (matches(safeArgs, new Class[]{com.codename1.home.HomeError.class, java.lang.String.class, java.lang.String.class, java.lang.Throwable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeError.class, java.lang.String.class, java.lang.String.class, java.lang.Throwable.class}, false); + return new com.codename1.home.HomeException((com.codename1.home.HomeError) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], (java.lang.Throwable) adaptedArgs[3]); + } + } + if (type == com.codename1.home.HomeRoom.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class}, false); + return new com.codename1.home.HomeRoom((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); + } + } + if (type == com.codename1.home.HomeStructure.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class, java.util.List.class, java.util.List.class, java.util.List.class, java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class, java.util.List.class, java.util.List.class, java.util.List.class, java.util.List.class}, false); + return new com.codename1.home.HomeStructure((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], ((Boolean) adaptedArgs[2]).booleanValue(), ((Boolean) adaptedArgs[3]).booleanValue(), ((Boolean) adaptedArgs[4]).booleanValue(), (java.util.List) adaptedArgs[5], (java.util.List) adaptedArgs[6], (java.util.List) adaptedArgs[7], (java.util.List) adaptedArgs[8]); + } + } + if (type == com.codename1.home.HomeStructureEvent.class) { + if (matches(safeArgs, new Class[]{com.codename1.home.StructureChangeKind.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.StructureChangeKind.class, java.lang.String.class, java.lang.String.class}, false); + return new com.codename1.home.HomeStructureEvent((com.codename1.home.StructureChangeKind) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); + } + } + if (type == com.codename1.home.HomeZone.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.util.List.class}, false); + return new com.codename1.home.HomeZone((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.util.List) adaptedArgs[2]); + } + } + if (type == com.codename1.home.Scene.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class, com.codename1.home.SceneType.class, java.lang.Boolean.class, java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class, com.codename1.home.SceneType.class, java.lang.Boolean.class, java.util.List.class}, false); + return new com.codename1.home.Scene((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], (com.codename1.home.SceneType) adaptedArgs[3], ((Boolean) adaptedArgs[4]).booleanValue(), (java.util.List) adaptedArgs[5]); + } + } + if (type == com.codename1.home.SceneAction.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class, com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class, com.codename1.home.TraitValue.class}, false); + return new com.codename1.home.SceneAction((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2], (com.codename1.home.TraitValue) adaptedArgs[3]); + } + } + if (type == com.codename1.home.SubscriptionRequest.class) { + if (matches(safeArgs, new Class[0], false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[0], false); + return new com.codename1.home.SubscriptionRequest(); + } + } + if (type == com.codename1.home.TraitChangeBatch.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.util.List.class, java.lang.Boolean.class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.util.List.class, java.lang.Boolean.class, java.lang.Boolean.class}, false); + return new com.codename1.home.TraitChangeBatch((java.lang.String) adaptedArgs[0], (java.util.List) adaptedArgs[1], ((Boolean) adaptedArgs[2]).booleanValue(), ((Boolean) adaptedArgs[3]).booleanValue()); + } + } + if (type == com.codename1.home.TraitReadRequest.class) { + if (matches(safeArgs, new Class[0], false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[0], false); + return new com.codename1.home.TraitReadRequest(); + } + } + if (type == com.codename1.home.TraitWrite.class) { + if (matches(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class, com.codename1.home.Trait.class, com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class, com.codename1.home.Trait.class, com.codename1.home.TraitValue.class}, false); + return new com.codename1.home.TraitWrite((com.codename1.home.Accessory) adaptedArgs[0], (com.codename1.home.AccessoryService) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2], (com.codename1.home.TraitValue) adaptedArgs[3]); + } + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class, com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class, com.codename1.home.TraitValue.class}, false); + return new com.codename1.home.TraitWrite((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2], (com.codename1.home.TraitValue) adaptedArgs[3]); + } + } + throw unsupportedConstruct(type, safeArgs); + } + + public static Object invokeStatic(Class type, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + if (type == com.codename1.home.AirQualityLevel.class) return invokeStatic0(name, safeArgs); + if (type == com.codename1.home.AlarmState.class) return invokeStatic1(name, safeArgs); + if (type == com.codename1.home.ChargingState.class) return invokeStatic2(name, safeArgs); + if (type == com.codename1.home.DoorState.class) return invokeStatic3(name, safeArgs); + if (type == com.codename1.home.FanMode.class) return invokeStatic4(name, safeArgs); + if (type == com.codename1.home.HeatingCoolingMode.class) return invokeStatic5(name, safeArgs); + if (type == com.codename1.home.HomeError.class) return invokeStatic6(name, safeArgs); + if (type == com.codename1.home.LockState.class) return invokeStatic7(name, safeArgs); + if (type == com.codename1.home.PositionState.class) return invokeStatic8(name, safeArgs); + if (type == com.codename1.home.SmartHome.class) return invokeStatic9(name, safeArgs); + if (type == com.codename1.home.Trait.class) return invokeStatic10(name, safeArgs); + if (type == com.codename1.home.TraitConstraint.class) return invokeStatic11(name, safeArgs); + if (type == com.codename1.home.TraitReading.class) return invokeStatic12(name, safeArgs); + if (type == com.codename1.home.TraitUnit.class) return invokeStatic13(name, safeArgs); + if (type == com.codename1.home.TraitValue.class) return invokeStatic14(name, safeArgs); + if (type == com.codename1.home.TraitWriteResult.class) return invokeStatic15(name, safeArgs); + throw unsupportedStatic(type, name, safeArgs); + } + + private static Object invokeStatic0(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return com.codename1.home.AirQualityLevel.of((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.AirQualityLevel.class, name, safeArgs); + } + + private static Object invokeStatic1(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return com.codename1.home.AlarmState.of((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.AlarmState.class, name, safeArgs); + } + + private static Object invokeStatic2(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return com.codename1.home.ChargingState.of((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.ChargingState.class, name, safeArgs); + } + + private static Object invokeStatic3(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return com.codename1.home.DoorState.of((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.DoorState.class, name, safeArgs); + } + + private static Object invokeStatic4(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return com.codename1.home.FanMode.of((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.FanMode.class, name, safeArgs); + } + + private static Object invokeStatic5(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return com.codename1.home.HeatingCoolingMode.of((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.HeatingCoolingMode.class, name, safeArgs); + } + + private static Object invokeStatic6(String name, Object[] safeArgs) throws Exception { + if ("forName".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.home.HomeError.forName((java.lang.String) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.HomeError.class, name, safeArgs); + } + + private static Object invokeStatic7(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return com.codename1.home.LockState.of((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.LockState.class, name, safeArgs); + } + + private static Object invokeStatic8(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return com.codename1.home.PositionState.of((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.PositionState.class, name, safeArgs); + } + + private static Object invokeStatic9(String name, Object[] safeArgs) throws Exception { + if ("deliverAuthorization".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverAuthorization(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), (java.lang.String) adaptedArgs[2]); return null; + } + } + if ("deliverChanges".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String[].class}, false); + com.codename1.home.SmartHome.deliverChanges((java.lang.String) adaptedArgs[0], (java.lang.String[]) adaptedArgs[1]); return null; + } + } + if ("deliverCommissioningResult".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Integer.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Integer.class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverCommissioningResult(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], (java.lang.String) adaptedArgs[3], toIntValue(adaptedArgs[4]), (java.lang.String) adaptedArgs[5]); return null; + } + } + if ("deliverDrained".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverDrained(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), (java.lang.String) adaptedArgs[2]); return null; + } + } + if ("deliverIdentifyResult".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverIdentifyResult(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1]); return null; + } + } + if ("deliverReadings".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String[].class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String[].class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverReadings(toIntValue(adaptedArgs[0]), (java.lang.String[]) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); return null; + } + } + if ("deliverRefreshed".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverRefreshed(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1]); return null; + } + } + if ("deliverResyncRequired".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverResyncRequired((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("deliverSceneResult".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverSceneResult(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], (java.lang.String) adaptedArgs[3]); return null; + } + } + if ("deliverStarted".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverStarted(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1]), (java.lang.String) adaptedArgs[2]); return null; + } + } + if ("deliverWriteResults".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String[].class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String[].class, java.lang.String.class}, false); + com.codename1.home.SmartHome.deliverWriteResults(toIntValue(adaptedArgs[0]), (java.lang.String[]) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); return null; + } + } + if ("getInstance".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.home.SmartHome.getInstance(); + } + } + if ("notifyStructureChanged".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class}, false); + com.codename1.home.SmartHome.notifyStructureChanged(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); return null; + } + } + throw unsupportedStatic(com.codename1.home.SmartHome.class, name, safeArgs); + } + + private static Object invokeStatic10(String name, Object[] safeArgs) throws Exception { + if ("all".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.home.Trait.all(); + } + } + if ("forId".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.home.Trait.forId((java.lang.String) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.Trait.class, name, safeArgs); + } + + private static Object invokeStatic11(String name, Object[] safeArgs) throws Exception { + if ("choices".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Trait.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class, int[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Trait.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class, int[].class}, false); + return com.codename1.home.TraitConstraint.choices((com.codename1.home.Trait) adaptedArgs[0], ((Boolean) adaptedArgs[1]).booleanValue(), ((Boolean) adaptedArgs[2]).booleanValue(), ((Boolean) adaptedArgs[3]).booleanValue(), (int[]) adaptedArgs[4]); + } + } + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Trait.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Trait.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class}, false); + return com.codename1.home.TraitConstraint.of((com.codename1.home.Trait) adaptedArgs[0], ((Boolean) adaptedArgs[1]).booleanValue(), ((Boolean) adaptedArgs[2]).booleanValue(), ((Boolean) adaptedArgs[3]).booleanValue()); + } + } + if ("ranged".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Trait.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Double.class, java.lang.Double.class, java.lang.Double.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Trait.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Double.class, java.lang.Double.class, java.lang.Double.class}, false); + return com.codename1.home.TraitConstraint.ranged((com.codename1.home.Trait) adaptedArgs[0], ((Boolean) adaptedArgs[1]).booleanValue(), ((Boolean) adaptedArgs[2]).booleanValue(), ((Boolean) adaptedArgs[3]).booleanValue(), ((Number) adaptedArgs[4]).doubleValue(), ((Number) adaptedArgs[5]).doubleValue(), ((Number) adaptedArgs[6]).doubleValue()); + } + } + throw unsupportedStatic(com.codename1.home.TraitConstraint.class, name, safeArgs); + } + + private static Object invokeStatic12(String name, Object[] safeArgs) throws Exception { + if ("absent".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class}, false); + return com.codename1.home.TraitReading.absent((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2]); + } + } + if ("failed".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class, com.codename1.home.HomeError.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class, com.codename1.home.HomeError.class, java.lang.String.class}, false); + return com.codename1.home.TraitReading.failed((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2], (com.codename1.home.HomeError) adaptedArgs[3], (java.lang.String) adaptedArgs[4]); + } + } + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class, com.codename1.home.TraitValue.class, java.lang.Long.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class, com.codename1.home.TraitValue.class, java.lang.Long.class}, false); + return com.codename1.home.TraitReading.of((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2], (com.codename1.home.TraitValue) adaptedArgs[3], ((Number) adaptedArgs[4]).longValue()); + } + } + throw unsupportedStatic(com.codename1.home.TraitReading.class, name, safeArgs); + } + + private static Object invokeStatic13(String name, Object[] safeArgs) throws Exception { + if ("convert".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Double.class, com.codename1.home.TraitUnit.class, com.codename1.home.TraitUnit.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Double.class, com.codename1.home.TraitUnit.class, com.codename1.home.TraitUnit.class}, false); + return com.codename1.home.TraitUnit.convert(((Number) adaptedArgs[0]).doubleValue(), (com.codename1.home.TraitUnit) adaptedArgs[1], (com.codename1.home.TraitUnit) adaptedArgs[2]); + } + } + if ("forWireId".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return com.codename1.home.TraitUnit.forWireId(toIntValue(adaptedArgs[0])); + } + } + if ("kelvinToMired".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Double.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Double.class}, false); + return com.codename1.home.TraitUnit.kelvinToMired(((Number) adaptedArgs[0]).doubleValue()); + } + } + if ("miredToKelvin".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Double.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Double.class}, false); + return com.codename1.home.TraitUnit.miredToKelvin(((Number) adaptedArgs[0]).doubleValue()); + } + } + throw unsupportedStatic(com.codename1.home.TraitUnit.class, name, safeArgs); + } + + private static Object invokeStatic14(String name, Object[] safeArgs) throws Exception { + if ("of".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return com.codename1.home.TraitValue.of(((Boolean) adaptedArgs[0]).booleanValue()); + } + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return com.codename1.home.TraitValue.of(toIntValue(adaptedArgs[0])); + } + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.home.TraitValue.of((java.lang.String) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{java.lang.Double.class, com.codename1.home.TraitUnit.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Double.class, com.codename1.home.TraitUnit.class}, false); + return com.codename1.home.TraitValue.of(((Number) adaptedArgs[0]).doubleValue(), (com.codename1.home.TraitUnit) adaptedArgs[1]); + } + } + if ("ofEnum".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Enum.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Enum.class}, false); + return com.codename1.home.TraitValue.ofEnum((java.lang.Enum) adaptedArgs[0]); + } + } + if ("ofEnumOrdinal".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return com.codename1.home.TraitValue.ofEnumOrdinal(toIntValue(adaptedArgs[0])); + } + } + throw unsupportedStatic(com.codename1.home.TraitValue.class, name, safeArgs); + } + + private static Object invokeStatic15(String name, Object[] safeArgs) throws Exception { + if ("applied".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitWrite.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitWrite.class}, false); + return com.codename1.home.TraitWriteResult.applied((com.codename1.home.TraitWrite) adaptedArgs[0]); + } + } + if ("failed".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitWrite.class, com.codename1.home.HomeError.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitWrite.class, com.codename1.home.HomeError.class, java.lang.String.class}, false); + return com.codename1.home.TraitWriteResult.failed((com.codename1.home.TraitWrite) adaptedArgs[0], (com.codename1.home.HomeError) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); + } + } + throw unsupportedStatic(com.codename1.home.TraitWriteResult.class, name, safeArgs); + } + + public static Object invoke(Object target, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + CN1AccessException unsupported = null; + if (target instanceof com.codename1.home.HomeConfigurationException) { + try { + return invoke0((com.codename1.home.HomeConfigurationException) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.Accessory) { + try { + return invoke1((com.codename1.home.Accessory) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.AccessoryService) { + try { + return invoke2((com.codename1.home.AccessoryService) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.DoorState) { + try { + return invoke3((com.codename1.home.DoorState) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.HeatingCoolingMode) { + try { + return invoke4((com.codename1.home.HeatingCoolingMode) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.HomeException) { + try { + return invoke5((com.codename1.home.HomeException) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.HomeRoom) { + try { + return invoke6((com.codename1.home.HomeRoom) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.HomeStructure) { + try { + return invoke7((com.codename1.home.HomeStructure) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.HomeStructureEvent) { + try { + return invoke8((com.codename1.home.HomeStructureEvent) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.HomeZone) { + try { + return invoke9((com.codename1.home.HomeZone) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.LockState) { + try { + return invoke10((com.codename1.home.LockState) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.Scene) { + try { + return invoke11((com.codename1.home.Scene) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.SceneAction) { + try { + return invoke12((com.codename1.home.SceneAction) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.SmartHome) { + try { + return invoke13((com.codename1.home.SmartHome) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.SubscriptionRequest) { + try { + return invoke14((com.codename1.home.SubscriptionRequest) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.Trait) { + try { + return invoke15((com.codename1.home.Trait) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitChangeBatch) { + try { + return invoke16((com.codename1.home.TraitChangeBatch) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitConstraint) { + try { + return invoke17((com.codename1.home.TraitConstraint) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitReadRequest) { + try { + return invoke18((com.codename1.home.TraitReadRequest) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitReading) { + try { + return invoke19((com.codename1.home.TraitReading) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitSubscription) { + try { + return invoke20((com.codename1.home.TraitSubscription) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitUnit) { + try { + return invoke21((com.codename1.home.TraitUnit) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitValue) { + try { + return invoke22((com.codename1.home.TraitValue) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitWrite) { + try { + return invoke23((com.codename1.home.TraitWrite) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.TraitWriteResult) { + try { + return invoke24((com.codename1.home.TraitWriteResult) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.HomeChangeListener) { + try { + return invoke25((com.codename1.home.HomeChangeListener) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.HomeStructureListener) { + try { + return invoke26((com.codename1.home.HomeStructureListener) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (unsupported != null) { + throw unsupported; + } + throw unsupportedInstance(target, name, safeArgs); + } + + private static Object invoke0(com.codename1.home.HomeConfigurationException typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getAccessoryId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryId(); + } + } + if ("getError".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getError(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke1(com.codename1.home.Accessory typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getBridgeAccessoryId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBridgeAccessoryId(); + } + } + if ("getCategory".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getCategory(); + } + } + if ("getFirmwareVersion".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getFirmwareVersion(); + } + } + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getManufacturer".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getManufacturer(); + } + } + if ("getModel".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getModel(); + } + } + if ("getName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getName(); + } + } + if ("getPrimaryService".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPrimaryService(); + } + } + if ("getRoomId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRoomId(); + } + } + if ("getService".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getService((java.lang.String) adaptedArgs[0]); + } + } + if ("getServices".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getServices(); + } + } + if ("getServicesSupporting".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Trait.class}, false); + return typedTarget.getServicesSupporting((com.codename1.home.Trait) adaptedArgs[0]); + } + } + if ("isBridged".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isBridged(); + } + } + if ("isReachable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isReachable(); + } + } + if ("supports".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Trait.class}, false); + return typedTarget.supports((com.codename1.home.Trait) adaptedArgs[0]); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke2(com.codename1.home.AccessoryService typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getConstraint".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Trait.class}, false); + return typedTarget.getConstraint((com.codename1.home.Trait) adaptedArgs[0]); + } + } + if ("getConstraints".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getConstraints(); + } + } + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getName(); + } + } + if ("getTraits".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTraits(); + } + } + if ("getType".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getType(); + } + } + if ("isPrimary".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isPrimary(); + } + } + if ("supports".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Trait.class}, false); + return typedTarget.supports((com.codename1.home.Trait) adaptedArgs[0]); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke3(com.codename1.home.DoorState typedTarget, String name, Object[] safeArgs) throws Exception { + if ("isWritable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isWritable(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke4(com.codename1.home.HeatingCoolingMode typedTarget, String name, Object[] safeArgs) throws Exception { + if ("isWritable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isWritable(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke5(com.codename1.home.HomeException typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getAccessoryId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryId(); + } + } + if ("getError".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getError(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke6(com.codename1.home.HomeRoom typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getName(); + } + } + if ("getStructureId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStructureId(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke7(com.codename1.home.HomeStructure typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getAccessories".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessories(); + } + } + if ("getAccessoriesInRoom".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getAccessoriesInRoom((java.lang.String) adaptedArgs[0]); + } + } + if ("getAccessoriesSupporting".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Trait.class}, false); + return typedTarget.getAccessoriesSupporting((com.codename1.home.Trait) adaptedArgs[0]); + } + } + if ("getAccessory".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getAccessory((java.lang.String) adaptedArgs[0]); + } + } + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getName(); + } + } + if ("getRoom".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getRoom((java.lang.String) adaptedArgs[0]); + } + } + if ("getRooms".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRooms(); + } + } + if ("getScenes".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getScenes(); + } + } + if ("getZones".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getZones(); + } + } + if ("isOwner".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isOwner(); + } + } + if ("isPrimary".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isPrimary(); + } + } + if ("isSceneAuthoringSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSceneAuthoringSupported(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke8(com.codename1.home.HomeStructureEvent typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getAccessoryId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryId(); + } + } + if ("getKind".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getKind(); + } + } + if ("getStructureId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStructureId(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke9(com.codename1.home.HomeZone typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getName(); + } + } + if ("getRoomIds".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRoomIds(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke10(com.codename1.home.LockState typedTarget, String name, Object[] safeArgs) throws Exception { + if ("isWritable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isWritable(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke11(com.codename1.home.Scene typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getActions".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getActions(); + } + } + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getName(); + } + } + if ("getStructureId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStructureId(); + } + } + if ("getType".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getType(); + } + } + if ("isExecutable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isExecutable(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke12(com.codename1.home.SceneAction typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getAccessoryId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryId(); + } + } + if ("getServiceId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getServiceId(); + } + } + if ("getTrait".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTrait(); + } + } + if ("getValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getValue(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke13(com.codename1.home.SmartHome typedTarget, String name, Object[] safeArgs) throws Exception { + if ("addStructureListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.HomeStructureListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeStructureListener.class}, false); + typedTarget.addStructureListener((com.codename1.home.HomeStructureListener) adaptedArgs[0]); return null; + } + } + if ("areIdsPersistent".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.areIdsPersistent(); + } + } + if ("createScene".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.HomeStructure.class, java.lang.String.class, java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeStructure.class, java.lang.String.class, java.util.List.class}, false); + return typedTarget.createScene((com.codename1.home.HomeStructure) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.util.List) adaptedArgs[2]); + } + } + if ("deleteScene".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Scene.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Scene.class}, false); + return typedTarget.deleteScene((com.codename1.home.Scene) adaptedArgs[0]); + } + } + if ("drainChanges".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.drainChanges(); + } + } + if ("executeScene".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Scene.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Scene.class}, false); + return typedTarget.executeScene((com.codename1.home.Scene) adaptedArgs[0]); + } + } + if ("findAccessory".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.findAccessory((java.lang.String) adaptedArgs[0]); + } + } + if ("getAuthorizationStatus".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAuthorizationStatus(); + } + } + if ("getAvailability".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAvailability(); + } + } + if ("getBackend".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBackend(); + } + } + if ("getCommissioner".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getCommissioner(); + } + } + if ("getConfigurationProblems".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getConfigurationProblems(); + } + } + if ("getMaxReadBatchSize".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMaxReadBatchSize(); + } + } + if ("getMaxWriteBatchSize".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMaxWriteBatchSize(); + } + } + if ("getPrimaryStructure".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPrimaryStructure(); + } + } + if ("getStructures".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStructures(); + } + } + if ("identify".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Accessory.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Accessory.class}, false); + return typedTarget.identify((com.codename1.home.Accessory) adaptedArgs[0]); + } + } + if ("isAutomationSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isAutomationSupported(); + } + } + if ("isSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSupported(); + } + } + if ("openEcosystemApp".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.openEcosystemApp(); + } + } + if ("openHomeSettings".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.openHomeSettings(); + } + } + if ("openProviderSetup".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.openProviderSetup(); + } + } + if ("read".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitReadRequest.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitReadRequest.class}, false); + return typedTarget.read((com.codename1.home.TraitReadRequest) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class, com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class, com.codename1.home.Trait.class}, false); + return typedTarget.read((com.codename1.home.Accessory) adaptedArgs[0], (com.codename1.home.AccessoryService) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2]); + } + } + if ("refresh".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.refresh(); + } + } + if ("removeStructureListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.HomeStructureListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeStructureListener.class}, false); + typedTarget.removeStructureListener((com.codename1.home.HomeStructureListener) adaptedArgs[0]); return null; + } + } + if ("requestAuthorization".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.requestAuthorization(); + } + } + if ("subscribe".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.SubscriptionRequest.class, com.codename1.home.HomeChangeListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.SubscriptionRequest.class, com.codename1.home.HomeChangeListener.class}, false); + return typedTarget.subscribe((com.codename1.home.SubscriptionRequest) adaptedArgs[0], (com.codename1.home.HomeChangeListener) adaptedArgs[1]); + } + } + if ("write".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitWrite.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitWrite.class}, false); + return typedTarget.write((com.codename1.home.TraitWrite) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.List.class}, false); + return typedTarget.write((java.util.List) adaptedArgs[0]); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke14(com.codename1.home.SubscriptionRequest typedTarget, String name, Object[] safeArgs) throws Exception { + if ("add".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class, com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class, com.codename1.home.Trait.class}, false); + return typedTarget.add((com.codename1.home.Accessory) adaptedArgs[0], (com.codename1.home.AccessoryService) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2]); + } + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class}, false); + return typedTarget.add((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2]); + } + } + if ("getAccessoryIds".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryIds(); + } + } + if ("getMinIntervalMillis".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMinIntervalMillis(); + } + } + if ("getServiceIds".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getServiceIds(); + } + } + if ("getTraits".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTraits(); + } + } + if ("isDeliverInitialValues".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isDeliverInitialValues(); + } + } + if ("isEmpty".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isEmpty(); + } + } + if ("setDeliverInitialValues".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return typedTarget.setDeliverInitialValues(((Boolean) adaptedArgs[0]).booleanValue()); + } + } + if ("setMinIntervalMillis".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.setMinIntervalMillis(toIntValue(adaptedArgs[0])); + } + } + if ("size".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.size(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke15(com.codename1.home.Trait typedTarget, String name, Object[] safeArgs) throws Exception { + if ("acceptsEnumValue".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return typedTarget.acceptsEnumValue((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + if ("acceptsEnumWrite".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return typedTarget.acceptsEnumWrite((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + if ("acceptsUnit".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitUnit.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitUnit.class}, false); + return typedTarget.acceptsUnit((com.codename1.home.TraitUnit) adaptedArgs[0]); + } + } + if ("enumValue".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.enumValue(toIntValue(adaptedArgs[0])); + } + } + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getNominalMaximum".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getNominalMaximum(); + } + } + if ("getNominalMinimum".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getNominalMinimum(); + } + } + if ("getUnit".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getUnit(); + } + } + if ("getValueKind".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getValueKind(); + } + } + if ("hasNominalRange".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.hasNominalRange(); + } + } + if ("isReadOnly".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isReadOnly(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke16(com.codename1.home.TraitChangeBatch typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getReadings".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getReadings(); + } + } + if ("getSubscriptionId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSubscriptionId(); + } + } + if ("isEmpty".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isEmpty(); + } + } + if ("isInitialDelivery".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isInitialDelivery(); + } + } + if ("isResyncRequired".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isResyncRequired(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke17(com.codename1.home.TraitConstraint typedTarget, String name, Object[] safeArgs) throws Exception { + if ("accepts".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitValue.class}, false); + return typedTarget.accepts((com.codename1.home.TraitValue) adaptedArgs[0]); + } + } + if ("getMaximum".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMaximum(); + } + } + if ("getMinimum".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMinimum(); + } + } + if ("getStep".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStep(); + } + } + if ("getTrait".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTrait(); + } + } + if ("getValidOrdinals".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getValidOrdinals(); + } + } + if ("hasRange".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.hasRange(); + } + } + if ("isReadable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isReadable(); + } + } + if ("isWritable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isWritable(); + } + } + if ("notifiesOnChange".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.notifiesOnChange(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke18(com.codename1.home.TraitReadRequest typedTarget, String name, Object[] safeArgs) throws Exception { + if ("add".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class, com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class, com.codename1.home.Trait.class}, false); + return typedTarget.add((com.codename1.home.Accessory) adaptedArgs[0], (com.codename1.home.AccessoryService) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2]); + } + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.home.Trait.class}, false); + return typedTarget.add((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.home.Trait) adaptedArgs[2]); + } + } + if ("addAll".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.Accessory.class, com.codename1.home.AccessoryService.class}, false); + return typedTarget.addAll((com.codename1.home.Accessory) adaptedArgs[0], (com.codename1.home.AccessoryService) adaptedArgs[1]); + } + } + if ("getAccessoryIds".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryIds(); + } + } + if ("getServiceIds".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getServiceIds(); + } + } + if ("getTraits".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTraits(); + } + } + if ("isAllowCached".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isAllowCached(); + } + } + if ("isEmpty".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isEmpty(); + } + } + if ("setAllowCached".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return typedTarget.setAllowCached(((Boolean) adaptedArgs[0]).booleanValue()); + } + } + if ("size".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.size(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke19(com.codename1.home.TraitReading typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getAccessoryId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryId(); + } + } + if ("getError".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getError(); + } + } + if ("getErrorMessage".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getErrorMessage(); + } + } + if ("getServiceId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getServiceId(); + } + } + if ("getTimestampMillis".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTimestampMillis(); + } + } + if ("getTrait".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTrait(); + } + } + if ("getValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getValue(); + } + } + if ("hasValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.hasValue(); + } + } + if ("isFailed".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isFailed(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke20(com.codename1.home.TraitSubscription typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("isActive".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isActive(); + } + } + if ("isPushDelivery".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isPushDelivery(); + } + } + if ("stop".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.stop(); return null; + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke21(com.codename1.home.TraitUnit typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getDimension".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDimension(); + } + } + if ("getWireId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getWireId(); + } + } + if ("isCompatibleWith".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitUnit.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitUnit.class}, false); + return typedTarget.isCompatibleWith((com.codename1.home.TraitUnit) adaptedArgs[0]); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke22(com.codename1.home.TraitValue typedTarget, String name, Object[] safeArgs) throws Exception { + if ("equals".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Object.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Object.class}, false); + return typedTarget.equals((java.lang.Object) adaptedArgs[0]); + } + } + if ("getBoolean".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBoolean(); + } + } + if ("getColorTemperatureKelvin".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getColorTemperatureKelvin(); + } + } + if ("getDouble".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitUnit.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitUnit.class}, false); + return typedTarget.getDouble((com.codename1.home.TraitUnit) adaptedArgs[0]); + } + } + if ("getEnumName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getEnumName(); + } + } + if ("getEnumOrdinal".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getEnumOrdinal(); + } + } + if ("getInt".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getInt(); + } + } + if ("getKind".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getKind(); + } + } + if ("getRawDouble".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRawDouble(); + } + } + if ("getRawPlatformValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRawPlatformValue(); + } + } + if ("getString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getString(); + } + } + if ("getUnit".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getUnit(); + } + } + if ("hasRawPlatformValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.hasRawPlatformValue(); + } + } + if ("hashCode".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.hashCode(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + if ("withRawPlatformValue".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.withRawPlatformValue(toIntValue(adaptedArgs[0])); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke23(com.codename1.home.TraitWrite typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getAccessoryId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryId(); + } + } + if ("getAuthorizationData".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAuthorizationData(); + } + } + if ("getServiceId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getServiceId(); + } + } + if ("getTrait".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTrait(); + } + } + if ("getValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getValue(); + } + } + if ("setAuthorizationData".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.setAuthorizationData((java.lang.String) adaptedArgs[0]); + } + } + if ("toSceneAction".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toSceneAction(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke24(com.codename1.home.TraitWriteResult typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getError".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getError(); + } + } + if ("getErrorMessage".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getErrorMessage(); + } + } + if ("getWrite".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getWrite(); + } + } + if ("isApplied".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isApplied(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke25(com.codename1.home.HomeChangeListener typedTarget, String name, Object[] safeArgs) throws Exception { + if ("traitsChanged".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.TraitChangeBatch.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.TraitChangeBatch.class}, false); + typedTarget.traitsChanged((com.codename1.home.TraitChangeBatch) adaptedArgs[0]); return null; + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke26(com.codename1.home.HomeStructureListener typedTarget, String name, Object[] safeArgs) throws Exception { + if ("structureChanged".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.HomeStructureEvent.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeStructureEvent.class}, false); + typedTarget.structureChanged((com.codename1.home.HomeStructureEvent) adaptedArgs[0]); return null; + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + public static Object getStaticField(Class type, String name) throws Exception { + if (type == com.codename1.home.AccessoryCategory.class) return getStaticField0(name); + if (type == com.codename1.home.AirQualityLevel.class) return getStaticField1(name); + if (type == com.codename1.home.AlarmState.class) return getStaticField2(name); + if (type == com.codename1.home.ChargingState.class) return getStaticField3(name); + if (type == com.codename1.home.DoorState.class) return getStaticField4(name); + if (type == com.codename1.home.FanMode.class) return getStaticField5(name); + if (type == com.codename1.home.HeatingCoolingMode.class) return getStaticField6(name); + if (type == com.codename1.home.HomeAuthorizationStatus.class) return getStaticField7(name); + if (type == com.codename1.home.HomeAvailability.class) return getStaticField8(name); + if (type == com.codename1.home.HomeBackend.class) return getStaticField9(name); + if (type == com.codename1.home.HomeError.class) return getStaticField10(name); + if (type == com.codename1.home.LockState.class) return getStaticField11(name); + if (type == com.codename1.home.PositionState.class) return getStaticField12(name); + if (type == com.codename1.home.SceneType.class) return getStaticField13(name); + if (type == com.codename1.home.ServiceType.class) return getStaticField14(name); + if (type == com.codename1.home.StructureChangeKind.class) return getStaticField15(name); + if (type == com.codename1.home.SubscriptionRequest.class) return getStaticField16(name); + if (type == com.codename1.home.Trait.class) return getStaticField17(name); + if (type == com.codename1.home.TraitUnit.class) return getStaticField18(name); + if (type == com.codename1.home.TraitUnitDimension.class) return getStaticField19(name); + if (type == com.codename1.home.TraitValueKind.class) return getStaticField20(name); + throw unsupportedStaticField(type, name); + } + + private static Object getStaticField0(String name) throws Exception { + if ("AIR_PURIFIER".equals(name)) return com.codename1.home.AccessoryCategory.AIR_PURIFIER; + if ("BRIDGE".equals(name)) return com.codename1.home.AccessoryCategory.BRIDGE; + if ("CAMERA".equals(name)) return com.codename1.home.AccessoryCategory.CAMERA; + if ("DOORBELL".equals(name)) return com.codename1.home.AccessoryCategory.DOORBELL; + if ("FAN".equals(name)) return com.codename1.home.AccessoryCategory.FAN; + if ("GARAGE_DOOR_OPENER".equals(name)) return com.codename1.home.AccessoryCategory.GARAGE_DOOR_OPENER; + if ("LIGHT".equals(name)) return com.codename1.home.AccessoryCategory.LIGHT; + if ("LOCK".equals(name)) return com.codename1.home.AccessoryCategory.LOCK; + if ("OTHER".equals(name)) return com.codename1.home.AccessoryCategory.OTHER; + if ("OUTLET".equals(name)) return com.codename1.home.AccessoryCategory.OUTLET; + if ("SECURITY_SYSTEM".equals(name)) return com.codename1.home.AccessoryCategory.SECURITY_SYSTEM; + if ("SENSOR".equals(name)) return com.codename1.home.AccessoryCategory.SENSOR; + if ("SPEAKER".equals(name)) return com.codename1.home.AccessoryCategory.SPEAKER; + if ("SWITCH".equals(name)) return com.codename1.home.AccessoryCategory.SWITCH; + if ("TELEVISION".equals(name)) return com.codename1.home.AccessoryCategory.TELEVISION; + if ("THERMOSTAT".equals(name)) return com.codename1.home.AccessoryCategory.THERMOSTAT; + if ("WINDOW_COVERING".equals(name)) return com.codename1.home.AccessoryCategory.WINDOW_COVERING; + throw unsupportedStaticField(com.codename1.home.AccessoryCategory.class, name); + } + + private static Object getStaticField1(String name) throws Exception { + if ("EXTREMELY_POOR".equals(name)) return com.codename1.home.AirQualityLevel.EXTREMELY_POOR; + if ("FAIR".equals(name)) return com.codename1.home.AirQualityLevel.FAIR; + if ("GOOD".equals(name)) return com.codename1.home.AirQualityLevel.GOOD; + if ("MODERATE".equals(name)) return com.codename1.home.AirQualityLevel.MODERATE; + if ("POOR".equals(name)) return com.codename1.home.AirQualityLevel.POOR; + if ("UNKNOWN".equals(name)) return com.codename1.home.AirQualityLevel.UNKNOWN; + if ("VERY_POOR".equals(name)) return com.codename1.home.AirQualityLevel.VERY_POOR; + throw unsupportedStaticField(com.codename1.home.AirQualityLevel.class, name); + } + + private static Object getStaticField2(String name) throws Exception { + if ("CRITICAL".equals(name)) return com.codename1.home.AlarmState.CRITICAL; + if ("NORMAL".equals(name)) return com.codename1.home.AlarmState.NORMAL; + if ("UNKNOWN".equals(name)) return com.codename1.home.AlarmState.UNKNOWN; + if ("WARNING".equals(name)) return com.codename1.home.AlarmState.WARNING; + throw unsupportedStaticField(com.codename1.home.AlarmState.class, name); + } + + private static Object getStaticField3(String name) throws Exception { + if ("CHARGING".equals(name)) return com.codename1.home.ChargingState.CHARGING; + if ("FULL".equals(name)) return com.codename1.home.ChargingState.FULL; + if ("NOT_CHARGEABLE".equals(name)) return com.codename1.home.ChargingState.NOT_CHARGEABLE; + if ("NOT_CHARGING".equals(name)) return com.codename1.home.ChargingState.NOT_CHARGING; + if ("UNKNOWN".equals(name)) return com.codename1.home.ChargingState.UNKNOWN; + throw unsupportedStaticField(com.codename1.home.ChargingState.class, name); + } + + private static Object getStaticField4(String name) throws Exception { + if ("CLOSED".equals(name)) return com.codename1.home.DoorState.CLOSED; + if ("CLOSING".equals(name)) return com.codename1.home.DoorState.CLOSING; + if ("OPEN".equals(name)) return com.codename1.home.DoorState.OPEN; + if ("OPENING".equals(name)) return com.codename1.home.DoorState.OPENING; + if ("STOPPED".equals(name)) return com.codename1.home.DoorState.STOPPED; + if ("UNKNOWN".equals(name)) return com.codename1.home.DoorState.UNKNOWN; + throw unsupportedStaticField(com.codename1.home.DoorState.class, name); + } + + private static Object getStaticField5(String name) throws Exception { + if ("AUTO".equals(name)) return com.codename1.home.FanMode.AUTO; + if ("HIGH".equals(name)) return com.codename1.home.FanMode.HIGH; + if ("LOW".equals(name)) return com.codename1.home.FanMode.LOW; + if ("MEDIUM".equals(name)) return com.codename1.home.FanMode.MEDIUM; + if ("OFF".equals(name)) return com.codename1.home.FanMode.OFF; + if ("ON".equals(name)) return com.codename1.home.FanMode.ON; + if ("SMART".equals(name)) return com.codename1.home.FanMode.SMART; + throw unsupportedStaticField(com.codename1.home.FanMode.class, name); + } + + private static Object getStaticField6(String name) throws Exception { + if ("AUTO".equals(name)) return com.codename1.home.HeatingCoolingMode.AUTO; + if ("COOL".equals(name)) return com.codename1.home.HeatingCoolingMode.COOL; + if ("HEAT".equals(name)) return com.codename1.home.HeatingCoolingMode.HEAT; + if ("OFF".equals(name)) return com.codename1.home.HeatingCoolingMode.OFF; + if ("OTHER".equals(name)) return com.codename1.home.HeatingCoolingMode.OTHER; + throw unsupportedStaticField(com.codename1.home.HeatingCoolingMode.class, name); + } + + private static Object getStaticField7(String name) throws Exception { + if ("AUTHORIZED".equals(name)) return com.codename1.home.HomeAuthorizationStatus.AUTHORIZED; + if ("DENIED".equals(name)) return com.codename1.home.HomeAuthorizationStatus.DENIED; + if ("NOT_DETERMINED".equals(name)) return com.codename1.home.HomeAuthorizationStatus.NOT_DETERMINED; + if ("RESTRICTED".equals(name)) return com.codename1.home.HomeAuthorizationStatus.RESTRICTED; + if ("UNKNOWN".equals(name)) return com.codename1.home.HomeAuthorizationStatus.UNKNOWN; + throw unsupportedStaticField(com.codename1.home.HomeAuthorizationStatus.class, name); + } + + private static Object getStaticField8(String name) throws Exception { + if ("AVAILABLE".equals(name)) return com.codename1.home.HomeAvailability.AVAILABLE; + if ("COMMISSIONING_ONLY".equals(name)) return com.codename1.home.HomeAvailability.COMMISSIONING_ONLY; + if ("LOCAL_ONLY".equals(name)) return com.codename1.home.HomeAvailability.LOCAL_ONLY; + if ("NOT_CONFIGURED".equals(name)) return com.codename1.home.HomeAvailability.NOT_CONFIGURED; + if ("NOT_STARTED".equals(name)) return com.codename1.home.HomeAvailability.NOT_STARTED; + if ("NOT_SUPPORTED".equals(name)) return com.codename1.home.HomeAvailability.NOT_SUPPORTED; + if ("PERMISSION_DENIED".equals(name)) return com.codename1.home.HomeAvailability.PERMISSION_DENIED; + if ("PERMISSION_REQUIRED".equals(name)) return com.codename1.home.HomeAvailability.PERMISSION_REQUIRED; + if ("PROVIDER_NOT_INSTALLED".equals(name)) return com.codename1.home.HomeAvailability.PROVIDER_NOT_INSTALLED; + if ("PROVIDER_UPDATE_REQUIRED".equals(name)) return com.codename1.home.HomeAvailability.PROVIDER_UPDATE_REQUIRED; + if ("RESTRICTED".equals(name)) return com.codename1.home.HomeAvailability.RESTRICTED; + if ("SIGN_IN_REQUIRED".equals(name)) return com.codename1.home.HomeAvailability.SIGN_IN_REQUIRED; + throw unsupportedStaticField(com.codename1.home.HomeAvailability.class, name); + } + + private static Object getStaticField9(String name) throws Exception { + if ("GOOGLE_HOME".equals(name)) return com.codename1.home.HomeBackend.GOOGLE_HOME; + if ("HOMEKIT".equals(name)) return com.codename1.home.HomeBackend.HOMEKIT; + if ("LOCAL".equals(name)) return com.codename1.home.HomeBackend.LOCAL; + if ("MATTER_COMMISSIONING_ONLY".equals(name)) return com.codename1.home.HomeBackend.MATTER_COMMISSIONING_ONLY; + if ("NONE".equals(name)) return com.codename1.home.HomeBackend.NONE; + throw unsupportedStaticField(com.codename1.home.HomeBackend.class, name); + } + + private static Object getStaticField10(String name) throws Exception { + if ("ACCESSORY_NOT_FOUND".equals(name)) return com.codename1.home.HomeError.ACCESSORY_NOT_FOUND; + if ("ACCESSORY_UNREACHABLE".equals(name)) return com.codename1.home.HomeError.ACCESSORY_UNREACHABLE; + if ("AUTHORIZATION_REQUIRED".equals(name)) return com.codename1.home.HomeError.AUTHORIZATION_REQUIRED; + if ("BUSY".equals(name)) return com.codename1.home.HomeError.BUSY; + if ("COMMISSIONING_FAILED".equals(name)) return com.codename1.home.HomeError.COMMISSIONING_FAILED; + if ("COMMISSIONING_UNAVAILABLE".equals(name)) return com.codename1.home.HomeError.COMMISSIONING_UNAVAILABLE; + if ("ECOSYSTEM_APP_MISSING".equals(name)) return com.codename1.home.HomeError.ECOSYSTEM_APP_MISSING; + if ("INVALID_ARGUMENT".equals(name)) return com.codename1.home.HomeError.INVALID_ARGUMENT; + if ("INVALID_DATA".equals(name)) return com.codename1.home.HomeError.INVALID_DATA; + if ("NOT_CONFIGURED".equals(name)) return com.codename1.home.HomeError.NOT_CONFIGURED; + if ("NOT_SUPPORTED".equals(name)) return com.codename1.home.HomeError.NOT_SUPPORTED; + if ("PIN_REJECTED".equals(name)) return com.codename1.home.HomeError.PIN_REJECTED; + if ("PIN_REQUIRED".equals(name)) return com.codename1.home.HomeError.PIN_REQUIRED; + if ("PROVIDER_UNAVAILABLE".equals(name)) return com.codename1.home.HomeError.PROVIDER_UNAVAILABLE; + if ("PROVIDER_UPDATE_REQUIRED".equals(name)) return com.codename1.home.HomeError.PROVIDER_UPDATE_REQUIRED; + if ("RATE_LIMITED".equals(name)) return com.codename1.home.HomeError.RATE_LIMITED; + if ("READ_ONLY_TRAIT".equals(name)) return com.codename1.home.HomeError.READ_ONLY_TRAIT; + if ("RESTRICTED".equals(name)) return com.codename1.home.HomeError.RESTRICTED; + if ("SIGN_IN_REQUIRED".equals(name)) return com.codename1.home.HomeError.SIGN_IN_REQUIRED; + if ("TIMEOUT".equals(name)) return com.codename1.home.HomeError.TIMEOUT; + if ("TRAIT_NOT_SUPPORTED".equals(name)) return com.codename1.home.HomeError.TRAIT_NOT_SUPPORTED; + if ("UNAUTHORIZED".equals(name)) return com.codename1.home.HomeError.UNAUTHORIZED; + if ("UNIT_MISMATCH".equals(name)) return com.codename1.home.HomeError.UNIT_MISMATCH; + if ("UNKNOWN".equals(name)) return com.codename1.home.HomeError.UNKNOWN; + if ("USER_CANCELED".equals(name)) return com.codename1.home.HomeError.USER_CANCELED; + if ("VALUE_OUT_OF_RANGE".equals(name)) return com.codename1.home.HomeError.VALUE_OUT_OF_RANGE; + if ("WRITE_ONLY_TRAIT".equals(name)) return com.codename1.home.HomeError.WRITE_ONLY_TRAIT; + throw unsupportedStaticField(com.codename1.home.HomeError.class, name); + } + + private static Object getStaticField11(String name) throws Exception { + if ("JAMMED".equals(name)) return com.codename1.home.LockState.JAMMED; + if ("PARTIALLY_LOCKED".equals(name)) return com.codename1.home.LockState.PARTIALLY_LOCKED; + if ("SECURED".equals(name)) return com.codename1.home.LockState.SECURED; + if ("UNKNOWN".equals(name)) return com.codename1.home.LockState.UNKNOWN; + if ("UNSECURED".equals(name)) return com.codename1.home.LockState.UNSECURED; + throw unsupportedStaticField(com.codename1.home.LockState.class, name); + } + + private static Object getStaticField12(String name) throws Exception { + if ("CLOSING".equals(name)) return com.codename1.home.PositionState.CLOSING; + if ("OPENING".equals(name)) return com.codename1.home.PositionState.OPENING; + if ("STOPPED".equals(name)) return com.codename1.home.PositionState.STOPPED; + if ("UNKNOWN".equals(name)) return com.codename1.home.PositionState.UNKNOWN; + throw unsupportedStaticField(com.codename1.home.PositionState.class, name); + } + + private static Object getStaticField13(String name) throws Exception { + if ("ARRIVAL".equals(name)) return com.codename1.home.SceneType.ARRIVAL; + if ("DEPARTURE".equals(name)) return com.codename1.home.SceneType.DEPARTURE; + if ("SLEEP".equals(name)) return com.codename1.home.SceneType.SLEEP; + if ("TRIGGER_OWNED".equals(name)) return com.codename1.home.SceneType.TRIGGER_OWNED; + if ("USER_DEFINED".equals(name)) return com.codename1.home.SceneType.USER_DEFINED; + if ("WAKE_UP".equals(name)) return com.codename1.home.SceneType.WAKE_UP; + throw unsupportedStaticField(com.codename1.home.SceneType.class, name); + } + + private static Object getStaticField14(String name) throws Exception { + if ("AIR_PURIFIER".equals(name)) return com.codename1.home.ServiceType.AIR_PURIFIER; + if ("AIR_QUALITY_SENSOR".equals(name)) return com.codename1.home.ServiceType.AIR_QUALITY_SENSOR; + if ("BATTERY".equals(name)) return com.codename1.home.ServiceType.BATTERY; + if ("CARBON_MONOXIDE_SENSOR".equals(name)) return com.codename1.home.ServiceType.CARBON_MONOXIDE_SENSOR; + if ("CONTACT_SENSOR".equals(name)) return com.codename1.home.ServiceType.CONTACT_SENSOR; + if ("DOOR".equals(name)) return com.codename1.home.ServiceType.DOOR; + if ("FAN".equals(name)) return com.codename1.home.ServiceType.FAN; + if ("GARAGE_DOOR_OPENER".equals(name)) return com.codename1.home.ServiceType.GARAGE_DOOR_OPENER; + if ("HUMIDITY_SENSOR".equals(name)) return com.codename1.home.ServiceType.HUMIDITY_SENSOR; + if ("LEAK_SENSOR".equals(name)) return com.codename1.home.ServiceType.LEAK_SENSOR; + if ("LIGHTBULB".equals(name)) return com.codename1.home.ServiceType.LIGHTBULB; + if ("LIGHT_SENSOR".equals(name)) return com.codename1.home.ServiceType.LIGHT_SENSOR; + if ("LOCK_MECHANISM".equals(name)) return com.codename1.home.ServiceType.LOCK_MECHANISM; + if ("MOTION_SENSOR".equals(name)) return com.codename1.home.ServiceType.MOTION_SENSOR; + if ("OCCUPANCY_SENSOR".equals(name)) return com.codename1.home.ServiceType.OCCUPANCY_SENSOR; + if ("OTHER".equals(name)) return com.codename1.home.ServiceType.OTHER; + if ("OUTLET".equals(name)) return com.codename1.home.ServiceType.OUTLET; + if ("SMOKE_SENSOR".equals(name)) return com.codename1.home.ServiceType.SMOKE_SENSOR; + if ("SPEAKER".equals(name)) return com.codename1.home.ServiceType.SPEAKER; + if ("SWITCH".equals(name)) return com.codename1.home.ServiceType.SWITCH; + if ("TEMPERATURE_SENSOR".equals(name)) return com.codename1.home.ServiceType.TEMPERATURE_SENSOR; + if ("THERMOSTAT".equals(name)) return com.codename1.home.ServiceType.THERMOSTAT; + if ("WINDOW_COVERING".equals(name)) return com.codename1.home.ServiceType.WINDOW_COVERING; + throw unsupportedStaticField(com.codename1.home.ServiceType.class, name); + } + + private static Object getStaticField15(String name) throws Exception { + if ("ACCESSORY_ADDED".equals(name)) return com.codename1.home.StructureChangeKind.ACCESSORY_ADDED; + if ("ACCESSORY_MOVED".equals(name)) return com.codename1.home.StructureChangeKind.ACCESSORY_MOVED; + if ("ACCESSORY_REMOVED".equals(name)) return com.codename1.home.StructureChangeKind.ACCESSORY_REMOVED; + if ("ACCESSORY_RENAMED".equals(name)) return com.codename1.home.StructureChangeKind.ACCESSORY_RENAMED; + if ("AVAILABILITY_CHANGED".equals(name)) return com.codename1.home.StructureChangeKind.AVAILABILITY_CHANGED; + if ("REACHABILITY_CHANGED".equals(name)) return com.codename1.home.StructureChangeKind.REACHABILITY_CHANGED; + if ("SCENES_CHANGED".equals(name)) return com.codename1.home.StructureChangeKind.SCENES_CHANGED; + if ("STRUCTURES_CHANGED".equals(name)) return com.codename1.home.StructureChangeKind.STRUCTURES_CHANGED; + throw unsupportedStaticField(com.codename1.home.StructureChangeKind.class, name); + } + + private static Object getStaticField16(String name) throws Exception { + if ("DEFAULT_MIN_INTERVAL_MILLIS".equals(name)) return com.codename1.home.SubscriptionRequest.DEFAULT_MIN_INTERVAL_MILLIS; + throw unsupportedStaticField(com.codename1.home.SubscriptionRequest.class, name); + } + + private static Object getStaticField17(String name) throws Exception { + if ("AIR_QUALITY".equals(name)) return com.codename1.home.Trait.AIR_QUALITY; + if ("BATTERY_CHARGING".equals(name)) return com.codename1.home.Trait.BATTERY_CHARGING; + if ("BATTERY_LEVEL".equals(name)) return com.codename1.home.Trait.BATTERY_LEVEL; + if ("BATTERY_LOW".equals(name)) return com.codename1.home.Trait.BATTERY_LOW; + if ("BRIGHTNESS".equals(name)) return com.codename1.home.Trait.BRIGHTNESS; + if ("CO2_LEVEL".equals(name)) return com.codename1.home.Trait.CO2_LEVEL; + if ("COLOR_TEMPERATURE".equals(name)) return com.codename1.home.Trait.COLOR_TEMPERATURE; + if ("CONTACT_DETECTED".equals(name)) return com.codename1.home.Trait.CONTACT_DETECTED; + if ("COVERING_MOTION".equals(name)) return com.codename1.home.Trait.COVERING_MOTION; + if ("COVERING_POSITION".equals(name)) return com.codename1.home.Trait.COVERING_POSITION; + if ("COVERING_TILT".equals(name)) return com.codename1.home.Trait.COVERING_TILT; + if ("CO_DETECTED".equals(name)) return com.codename1.home.Trait.CO_DETECTED; + if ("CO_LEVEL".equals(name)) return com.codename1.home.Trait.CO_LEVEL; + if ("CURRENT_HEATING_COOLING".equals(name)) return com.codename1.home.Trait.CURRENT_HEATING_COOLING; + if ("CURRENT_HUMIDITY".equals(name)) return com.codename1.home.Trait.CURRENT_HUMIDITY; + if ("CURRENT_LIGHT_LEVEL".equals(name)) return com.codename1.home.Trait.CURRENT_LIGHT_LEVEL; + if ("CURRENT_TEMPERATURE".equals(name)) return com.codename1.home.Trait.CURRENT_TEMPERATURE; + if ("DOOR_STATE".equals(name)) return com.codename1.home.Trait.DOOR_STATE; + if ("FAN_MODE".equals(name)) return com.codename1.home.Trait.FAN_MODE; + if ("FAN_SPEED".equals(name)) return com.codename1.home.Trait.FAN_SPEED; + if ("HUE".equals(name)) return com.codename1.home.Trait.HUE; + if ("LEAK_DETECTED".equals(name)) return com.codename1.home.Trait.LEAK_DETECTED; + if ("LOCK_STATE".equals(name)) return com.codename1.home.Trait.LOCK_STATE; + if ("MOTION_DETECTED".equals(name)) return com.codename1.home.Trait.MOTION_DETECTED; + if ("MUTE".equals(name)) return com.codename1.home.Trait.MUTE; + if ("OBSTRUCTION_DETECTED".equals(name)) return com.codename1.home.Trait.OBSTRUCTION_DETECTED; + if ("OCCUPANCY_DETECTED".equals(name)) return com.codename1.home.Trait.OCCUPANCY_DETECTED; + if ("ON_OFF".equals(name)) return com.codename1.home.Trait.ON_OFF; + if ("OUTLET_IN_USE".equals(name)) return com.codename1.home.Trait.OUTLET_IN_USE; + if ("PM10_DENSITY".equals(name)) return com.codename1.home.Trait.PM10_DENSITY; + if ("PM2_5_DENSITY".equals(name)) return com.codename1.home.Trait.PM2_5_DENSITY; + if ("SATURATION".equals(name)) return com.codename1.home.Trait.SATURATION; + if ("SMOKE_DETECTED".equals(name)) return com.codename1.home.Trait.SMOKE_DETECTED; + if ("TARGET_COOLING_TEMPERATURE".equals(name)) return com.codename1.home.Trait.TARGET_COOLING_TEMPERATURE; + if ("TARGET_COVERING_POSITION".equals(name)) return com.codename1.home.Trait.TARGET_COVERING_POSITION; + if ("TARGET_COVERING_TILT".equals(name)) return com.codename1.home.Trait.TARGET_COVERING_TILT; + if ("TARGET_DOOR_STATE".equals(name)) return com.codename1.home.Trait.TARGET_DOOR_STATE; + if ("TARGET_HEATING_COOLING".equals(name)) return com.codename1.home.Trait.TARGET_HEATING_COOLING; + if ("TARGET_HEATING_TEMPERATURE".equals(name)) return com.codename1.home.Trait.TARGET_HEATING_TEMPERATURE; + if ("TARGET_HUMIDITY".equals(name)) return com.codename1.home.Trait.TARGET_HUMIDITY; + if ("TARGET_LOCK_STATE".equals(name)) return com.codename1.home.Trait.TARGET_LOCK_STATE; + if ("TARGET_TEMPERATURE".equals(name)) return com.codename1.home.Trait.TARGET_TEMPERATURE; + if ("VOC_DENSITY".equals(name)) return com.codename1.home.Trait.VOC_DENSITY; + if ("VOLUME".equals(name)) return com.codename1.home.Trait.VOLUME; + throw unsupportedStaticField(com.codename1.home.Trait.class, name); + } + + private static Object getStaticField18(String name) throws Exception { + if ("ARC_DEGREE".equals(name)) return com.codename1.home.TraitUnit.ARC_DEGREE; + if ("CELSIUS".equals(name)) return com.codename1.home.TraitUnit.CELSIUS; + if ("FAHRENHEIT".equals(name)) return com.codename1.home.TraitUnit.FAHRENHEIT; + if ("LUX".equals(name)) return com.codename1.home.TraitUnit.LUX; + if ("MICROGRAM_PER_CUBIC_METER".equals(name)) return com.codename1.home.TraitUnit.MICROGRAM_PER_CUBIC_METER; + if ("MIRED".equals(name)) return com.codename1.home.TraitUnit.MIRED; + if ("NONE".equals(name)) return com.codename1.home.TraitUnit.NONE; + if ("PERCENT".equals(name)) return com.codename1.home.TraitUnit.PERCENT; + if ("PPB".equals(name)) return com.codename1.home.TraitUnit.PPB; + if ("PPM".equals(name)) return com.codename1.home.TraitUnit.PPM; + throw unsupportedStaticField(com.codename1.home.TraitUnit.class, name); + } + + private static Object getStaticField19(String name) throws Exception { + if ("ANGLE".equals(name)) return com.codename1.home.TraitUnitDimension.ANGLE; + if ("COLOR_TEMPERATURE".equals(name)) return com.codename1.home.TraitUnitDimension.COLOR_TEMPERATURE; + if ("CONCENTRATION_MASS".equals(name)) return com.codename1.home.TraitUnitDimension.CONCENTRATION_MASS; + if ("CONCENTRATION_PARTS".equals(name)) return com.codename1.home.TraitUnitDimension.CONCENTRATION_PARTS; + if ("DIMENSIONLESS".equals(name)) return com.codename1.home.TraitUnitDimension.DIMENSIONLESS; + if ("ILLUMINANCE".equals(name)) return com.codename1.home.TraitUnitDimension.ILLUMINANCE; + if ("RATIO".equals(name)) return com.codename1.home.TraitUnitDimension.RATIO; + if ("TEMPERATURE".equals(name)) return com.codename1.home.TraitUnitDimension.TEMPERATURE; + throw unsupportedStaticField(com.codename1.home.TraitUnitDimension.class, name); + } + + private static Object getStaticField20(String name) throws Exception { + if ("BOOLEAN".equals(name)) return com.codename1.home.TraitValueKind.BOOLEAN; + if ("DOUBLE".equals(name)) return com.codename1.home.TraitValueKind.DOUBLE; + if ("ENUM".equals(name)) return com.codename1.home.TraitValueKind.ENUM; + if ("INT".equals(name)) return com.codename1.home.TraitValueKind.INT; + if ("STRING".equals(name)) return com.codename1.home.TraitValueKind.STRING; + throw unsupportedStaticField(com.codename1.home.TraitValueKind.class, name); + } + + public static Object getField(Object target, String name) throws Exception { + throw unsupportedField(target, name); + } + + public static void setStaticField(Class type, String name, Object value) throws Exception { + throw unsupportedStaticFieldWrite(type, name, value); + } + + public static void setField(Object target, String name, Object value) throws Exception { + throw unsupportedFieldWrite(target, name, value); + } + + private static Object[] safeArgs(Object[] args) { + return args == null ? new Object[0] : args; + } + + private static Object[] adaptArgs(Object[] args, Class[] paramTypes, boolean varArgs) { + if (args == null || args.length == 0) { + return args == null ? new Object[0] : args; + } + Object[] adapted = args.clone(); + if (!varArgs) { + for (int i = 0; i < Math.min(adapted.length, paramTypes.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + return adapted; + } + if (paramTypes.length == 0) { + return adapted; + } + int fixedCount = paramTypes.length - 1; + for (int i = 0; i < Math.min(fixedCount, adapted.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < adapted.length; i++) { + adapted[i] = adaptValue(adapted[i], componentType); + } + return adapted; + } + + private static boolean isSamInterface(Class type) { + if (type == com.codename1.util.OnComplete.class) { + return true; + } + if (type == com.codename1.util.SuccessCallback.class) { + return true; + } + if (type == com.codename1.util.FailureCallback.class) { + return true; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return true; + } + if (type == java.lang.Runnable.class) { + return true; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return true; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return true; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return true; + } + return false; + } + + private static Object adaptLambdaValue(final bsh.cn1.CN1LambdaSupport.LambdaValue lambda, Class type) { + if (type == com.codename1.util.OnComplete.class) { + return new com.codename1.util.OnComplete() { + public void completed(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.SuccessCallback.class) { + return new com.codename1.util.SuccessCallback() { + public void onSucess(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.FailureCallback.class) { + return new com.codename1.util.FailureCallback() { + public void onError(java.lang.Object arg0, java.lang.Throwable arg1, int arg2, java.lang.String arg3) { + try { + lambda.invoke(new Object[]{arg0, arg1, arg2, arg3}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == java.lang.Runnable.class) { + return new java.lang.Runnable() { + public void run() { + try { + lambda.invoke(new Object[0]); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return new com.codename1.ui.events.DataChangedListener() { + public void dataChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return new com.codename1.ui.events.SelectionListener() { + public void selectionChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return new com.codename1.printing.PrintResultListener() { + public void onResult(com.codename1.printing.PrintResult arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + return lambda; + } + + private static Object adaptValue(Object value, Class type) { + if (!(value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue)) { + return value; + } + // Direct fit when LambdaValue already implements the target SAM + // (Runnable, Function, Comparator, ...). + if (type.isInstance(value)) { + return value; + } + return adaptLambdaValue((bsh.cn1.CN1LambdaSupport.LambdaValue) value, type); + } + + private static int toIntValue(Object value) { + if (value instanceof Number) return ((Number) value).intValue(); + if (value instanceof Character) return (int) ((Character) value).charValue(); + throw new ClassCastException("Cannot coerce " + + (value == null ? "null" : value.getClass().getName()) + " to int"); + } + + private static boolean matches(Object[] args, Class[] paramTypes, boolean varArgs) { + if (!varArgs) { + if (args.length != paramTypes.length) { + return false; + } + for (int i = 0; i < paramTypes.length; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + return true; + } + if (paramTypes.length == 0) { + return true; + } + int fixedCount = paramTypes.length - 1; + if (args.length < fixedCount) { + return false; + } + for (int i = 0; i < fixedCount; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < args.length; i++) { + if (!matchesType(args[i], componentType)) { + return false; + } + } + return true; + } + + private static boolean matchesType(Object value, Class type) { + if (type == Object.class) { + return true; + } + if (value == null) { + return !type.isPrimitive(); + } + if (type.isArray()) { + return type.isInstance(value); + } + if ("boolean".equals(type.getName()) || type == Boolean.class) { + return value instanceof Boolean; + } + if ("char".equals(type.getName()) || type == Character.class) { + return value instanceof Character; + } + if ("byte".equals(type.getName()) || type == Byte.class || "short".equals(type.getName()) || type == Short.class + || "int".equals(type.getName()) || type == Integer.class || "long".equals(type.getName()) || type == Long.class + || "float".equals(type.getName()) || type == Float.class || "double".equals(type.getName()) || type == Double.class) { + // Java widens char to int implicitly, so accept Character + // for any int-or-larger numeric slot. + return value instanceof Number || value instanceof Character; + } + if (value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue) { + // LambdaValue implements common SAMs directly (Runnable, + // Function, Predicate, Comparator, ...). Also accept any + // CN1 SAM the listener-bridge knows how to wrap. + return type.isInstance(value) || isSamInterface(type); + } + return type.isInstance(value); + } + + private static CN1AccessException unsupportedConstruct(Class type, Object[] args) { + return new CN1AccessException("Generated constructor dispatch not implemented for " + type.getName() + describeArgs(args)); + } + + private static CN1AccessException unsupportedStatic(Class type, String name, Object[] args) { + return new CN1AccessException("Generated static dispatch not implemented for " + type.getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedInstance(Object target, String name, Object[] args) { + return new CN1AccessException("Generated instance dispatch not implemented for " + target.getClass().getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedStaticField(Class type, String name) { + return new CN1AccessException("Generated static field access not implemented for " + type.getName() + "." + name); + } + + private static CN1AccessException unsupportedField(Object target, String name) { + return new CN1AccessException("Generated field access not implemented for " + target.getClass().getName() + "." + name); + } + + private static CN1AccessException unsupportedStaticFieldWrite(Class type, String name, Object value) { + return new CN1AccessException("Generated static field write not implemented for " + type.getName() + "." + name + " value=" + describeValue(value)); + } + + private static CN1AccessException unsupportedFieldWrite(Object target, String name, Object value) { + return new CN1AccessException("Generated field write not implemented for " + target.getClass().getName() + "." + name + " value=" + describeValue(value)); + } + + private static String describeArgs(Object[] args) { + if (args == null || args.length == 0) { + return "()"; + } + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(describeValue(args[i])); + } + sb.append(')'); + return sb.toString(); + } + + private static String describeValue(Object value) { + return value == null ? "null" : value.getClass().getName(); + } +} diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home_commissioning.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home_commissioning.java new file mode 100644 index 00000000000..9b699890a5e --- /dev/null +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home_commissioning.java @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package bsh.cn1.gen; + +import bsh.cn1.CN1AccessException; + +public final class GeneratedAccess_com_codename1_home_commissioning { + private GeneratedAccess_com_codename1_home_commissioning() { + } + + public static Class findClass(String name) { + if (name == null) { + return null; + } + int dot = name.lastIndexOf('.'); + int dollar = name.lastIndexOf('$'); + int sep = dot > dollar ? dot : dollar; + if (sep < 0 || sep == name.length() - 1) { + return null; + } + return findClassBySimpleName(name.substring(sep + 1)); + } + + public static Class findClassBySimpleName(String simpleName) { + Class found0 = findClassChunk0(simpleName); + if (found0 != null) { + return found0; + } + return null; + } + + + private static Class findClassChunk0(String simpleName) { + if ("Commissioner".equals(simpleName)) { + return com.codename1.home.commissioning.Commissioner.class; + } + if ("CommissioningRequest".equals(simpleName)) { + return com.codename1.home.commissioning.CommissioningRequest.class; + } + if ("CommissioningResult".equals(simpleName)) { + return com.codename1.home.commissioning.CommissioningResult.class; + } + if ("CommissioningStyle".equals(simpleName)) { + return com.codename1.home.commissioning.CommissioningStyle.class; + } + if ("SetupPayload".equals(simpleName)) { + return com.codename1.home.commissioning.SetupPayload.class; + } + return null; + } + public static Object construct(Class type, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + if (type == com.codename1.home.commissioning.CommissioningRequest.class) { + if (matches(safeArgs, new Class[0], false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[0], false); + return new com.codename1.home.commissioning.CommissioningRequest(); + } + } + if (type == com.codename1.home.commissioning.CommissioningResult.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Boolean.class}, false); + return new com.codename1.home.commissioning.CommissioningResult((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], ((Boolean) adaptedArgs[3]).booleanValue()); + } + } + throw unsupportedConstruct(type, safeArgs); + } + + public static Object invokeStatic(Class type, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + if (type == com.codename1.home.commissioning.SetupPayload.class) return invokeStatic0(name, safeArgs); + throw unsupportedStatic(type, name, safeArgs); + } + + private static Object invokeStatic0(String name, Object[] safeArgs) throws Exception { + if ("isValid".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.home.commissioning.SetupPayload.isValid((java.lang.String) adaptedArgs[0]); + } + } + if ("parse".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.home.commissioning.SetupPayload.parse((java.lang.String) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.home.commissioning.SetupPayload.class, name, safeArgs); + } + + public static Object invoke(Object target, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + CN1AccessException unsupported = null; + if (target instanceof com.codename1.home.commissioning.Commissioner) { + try { + return invoke0((com.codename1.home.commissioning.Commissioner) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.commissioning.CommissioningRequest) { + try { + return invoke1((com.codename1.home.commissioning.CommissioningRequest) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.commissioning.CommissioningResult) { + try { + return invoke2((com.codename1.home.commissioning.CommissioningResult) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.home.commissioning.SetupPayload) { + try { + return invoke3((com.codename1.home.commissioning.SetupPayload) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (unsupported != null) { + throw unsupported; + } + throw unsupportedInstance(target, name, safeArgs); + } + + private static Object invoke0(com.codename1.home.commissioning.Commissioner typedTarget, String name, Object[] safeArgs) throws Exception { + if ("commission".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.commissioning.CommissioningRequest.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.commissioning.CommissioningRequest.class}, false); + return typedTarget.commission((com.codename1.home.commissioning.CommissioningRequest) adaptedArgs[0]); + } + } + if ("getStyle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStyle(); + } + } + if ("isSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSupported(); + } + } + if ("openEcosystemApp".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.openEcosystemApp(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke1(com.codename1.home.commissioning.CommissioningRequest typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getRawSetupPayload".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRawSetupPayload(); + } + } + if ("getRoomId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRoomId(); + } + } + if ("getSetupPayload".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSetupPayload(); + } + } + if ("getStructureId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStructureId(); + } + } + if ("getSuggestedName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSuggestedName(); + } + } + if ("getTimeoutMillis".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTimeoutMillis(); + } + } + if ("isCommissionToThisApp".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isCommissionToThisApp(); + } + } + if ("setCommissionToThisApp".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + return typedTarget.setCommissionToThisApp(((Boolean) adaptedArgs[0]).booleanValue()); + } + } + if ("setRawSetupPayload".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.setRawSetupPayload((java.lang.String) adaptedArgs[0]); + } + } + if ("setRoom".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.HomeRoom.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeRoom.class}, false); + return typedTarget.setRoom((com.codename1.home.HomeRoom) adaptedArgs[0]); + } + } + if ("setRoomId".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.setRoomId((java.lang.String) adaptedArgs[0]); + } + } + if ("setSetupPayload".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.commissioning.SetupPayload.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.commissioning.SetupPayload.class}, false); + return typedTarget.setSetupPayload((com.codename1.home.commissioning.SetupPayload) adaptedArgs[0]); + } + } + if ("setStructure".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.home.HomeStructure.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.home.HomeStructure.class}, false); + return typedTarget.setStructure((com.codename1.home.HomeStructure) adaptedArgs[0]); + } + } + if ("setStructureId".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.setStructureId((java.lang.String) adaptedArgs[0]); + } + } + if ("setSuggestedName".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.setSuggestedName((java.lang.String) adaptedArgs[0]); + } + } + if ("setTimeoutMillis".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return typedTarget.setTimeoutMillis(toIntValue(adaptedArgs[0])); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke2(com.codename1.home.commissioning.CommissioningResult typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getAccessoryId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryId(); + } + } + if ("getAccessoryName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAccessoryName(); + } + } + if ("getStructureId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStructureId(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + if ("wasCommissionedToThisApp".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.wasCommissionedToThisApp(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke3(com.codename1.home.commissioning.SetupPayload typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getCustomFlow".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getCustomFlow(); + } + } + if ("getDiscoveryCapabilities".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDiscoveryCapabilities(); + } + } + if ("getDiscriminator".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDiscriminator(); + } + } + if ("getPasscode".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPasscode(); + } + } + if ("getProductId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getProductId(); + } + } + if ("getRaw".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRaw(); + } + } + if ("getVendorId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getVendorId(); + } + } + if ("getVersion".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getVersion(); + } + } + if ("isFromQrCode".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isFromQrCode(); + } + } + if ("isShortDiscriminator".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isShortDiscriminator(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + public static Object getStaticField(Class type, String name) throws Exception { + if (type == com.codename1.home.commissioning.CommissioningStyle.class) return getStaticField0(name); + if (type == com.codename1.home.commissioning.SetupPayload.class) return getStaticField1(name); + throw unsupportedStaticField(type, name); + } + + private static Object getStaticField0(String name) throws Exception { + if ("ECOSYSTEM_APP_HANDOFF".equals(name)) return com.codename1.home.commissioning.CommissioningStyle.ECOSYSTEM_APP_HANDOFF; + if ("NONE".equals(name)) return com.codename1.home.commissioning.CommissioningStyle.NONE; + if ("OS_OWNED_UI".equals(name)) return com.codename1.home.commissioning.CommissioningStyle.OS_OWNED_UI; + throw unsupportedStaticField(com.codename1.home.commissioning.CommissioningStyle.class, name); + } + + private static Object getStaticField1(String name) throws Exception { + if ("DISCOVERY_BLE".equals(name)) return com.codename1.home.commissioning.SetupPayload.DISCOVERY_BLE; + if ("DISCOVERY_ON_NETWORK".equals(name)) return com.codename1.home.commissioning.SetupPayload.DISCOVERY_ON_NETWORK; + if ("DISCOVERY_SOFT_AP".equals(name)) return com.codename1.home.commissioning.SetupPayload.DISCOVERY_SOFT_AP; + throw unsupportedStaticField(com.codename1.home.commissioning.SetupPayload.class, name); + } + + public static Object getField(Object target, String name) throws Exception { + throw unsupportedField(target, name); + } + + public static void setStaticField(Class type, String name, Object value) throws Exception { + throw unsupportedStaticFieldWrite(type, name, value); + } + + public static void setField(Object target, String name, Object value) throws Exception { + throw unsupportedFieldWrite(target, name, value); + } + + private static Object[] safeArgs(Object[] args) { + return args == null ? new Object[0] : args; + } + + private static Object[] adaptArgs(Object[] args, Class[] paramTypes, boolean varArgs) { + if (args == null || args.length == 0) { + return args == null ? new Object[0] : args; + } + Object[] adapted = args.clone(); + if (!varArgs) { + for (int i = 0; i < Math.min(adapted.length, paramTypes.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + return adapted; + } + if (paramTypes.length == 0) { + return adapted; + } + int fixedCount = paramTypes.length - 1; + for (int i = 0; i < Math.min(fixedCount, adapted.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < adapted.length; i++) { + adapted[i] = adaptValue(adapted[i], componentType); + } + return adapted; + } + + private static boolean isSamInterface(Class type) { + if (type == com.codename1.util.OnComplete.class) { + return true; + } + if (type == com.codename1.util.SuccessCallback.class) { + return true; + } + if (type == com.codename1.util.FailureCallback.class) { + return true; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return true; + } + if (type == java.lang.Runnable.class) { + return true; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return true; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return true; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return true; + } + return false; + } + + private static Object adaptLambdaValue(final bsh.cn1.CN1LambdaSupport.LambdaValue lambda, Class type) { + if (type == com.codename1.util.OnComplete.class) { + return new com.codename1.util.OnComplete() { + public void completed(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.SuccessCallback.class) { + return new com.codename1.util.SuccessCallback() { + public void onSucess(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.FailureCallback.class) { + return new com.codename1.util.FailureCallback() { + public void onError(java.lang.Object arg0, java.lang.Throwable arg1, int arg2, java.lang.String arg3) { + try { + lambda.invoke(new Object[]{arg0, arg1, arg2, arg3}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == java.lang.Runnable.class) { + return new java.lang.Runnable() { + public void run() { + try { + lambda.invoke(new Object[0]); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return new com.codename1.ui.events.DataChangedListener() { + public void dataChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return new com.codename1.ui.events.SelectionListener() { + public void selectionChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return new com.codename1.printing.PrintResultListener() { + public void onResult(com.codename1.printing.PrintResult arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + return lambda; + } + + private static Object adaptValue(Object value, Class type) { + if (!(value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue)) { + return value; + } + // Direct fit when LambdaValue already implements the target SAM + // (Runnable, Function, Comparator, ...). + if (type.isInstance(value)) { + return value; + } + return adaptLambdaValue((bsh.cn1.CN1LambdaSupport.LambdaValue) value, type); + } + + private static int toIntValue(Object value) { + if (value instanceof Number) return ((Number) value).intValue(); + if (value instanceof Character) return (int) ((Character) value).charValue(); + throw new ClassCastException("Cannot coerce " + + (value == null ? "null" : value.getClass().getName()) + " to int"); + } + + private static boolean matches(Object[] args, Class[] paramTypes, boolean varArgs) { + if (!varArgs) { + if (args.length != paramTypes.length) { + return false; + } + for (int i = 0; i < paramTypes.length; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + return true; + } + if (paramTypes.length == 0) { + return true; + } + int fixedCount = paramTypes.length - 1; + if (args.length < fixedCount) { + return false; + } + for (int i = 0; i < fixedCount; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < args.length; i++) { + if (!matchesType(args[i], componentType)) { + return false; + } + } + return true; + } + + private static boolean matchesType(Object value, Class type) { + if (type == Object.class) { + return true; + } + if (value == null) { + return !type.isPrimitive(); + } + if (type.isArray()) { + return type.isInstance(value); + } + if ("boolean".equals(type.getName()) || type == Boolean.class) { + return value instanceof Boolean; + } + if ("char".equals(type.getName()) || type == Character.class) { + return value instanceof Character; + } + if ("byte".equals(type.getName()) || type == Byte.class || "short".equals(type.getName()) || type == Short.class + || "int".equals(type.getName()) || type == Integer.class || "long".equals(type.getName()) || type == Long.class + || "float".equals(type.getName()) || type == Float.class || "double".equals(type.getName()) || type == Double.class) { + // Java widens char to int implicitly, so accept Character + // for any int-or-larger numeric slot. + return value instanceof Number || value instanceof Character; + } + if (value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue) { + // LambdaValue implements common SAMs directly (Runnable, + // Function, Predicate, Comparator, ...). Also accept any + // CN1 SAM the listener-bridge knows how to wrap. + return type.isInstance(value) || isSamInterface(type); + } + return type.isInstance(value); + } + + private static CN1AccessException unsupportedConstruct(Class type, Object[] args) { + return new CN1AccessException("Generated constructor dispatch not implemented for " + type.getName() + describeArgs(args)); + } + + private static CN1AccessException unsupportedStatic(Class type, String name, Object[] args) { + return new CN1AccessException("Generated static dispatch not implemented for " + type.getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedInstance(Object target, String name, Object[] args) { + return new CN1AccessException("Generated instance dispatch not implemented for " + target.getClass().getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedStaticField(Class type, String name) { + return new CN1AccessException("Generated static field access not implemented for " + type.getName() + "." + name); + } + + private static CN1AccessException unsupportedField(Object target, String name) { + return new CN1AccessException("Generated field access not implemented for " + target.getClass().getName() + "." + name); + } + + private static CN1AccessException unsupportedStaticFieldWrite(Class type, String name, Object value) { + return new CN1AccessException("Generated static field write not implemented for " + type.getName() + "." + name + " value=" + describeValue(value)); + } + + private static CN1AccessException unsupportedFieldWrite(Object target, String name, Object value) { + return new CN1AccessException("Generated field write not implemented for " + target.getClass().getName() + "." + name + " value=" + describeValue(value)); + } + + private static String describeArgs(Object[] args) { + if (args == null || args.length == 0) { + return "()"; + } + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(describeValue(args[i])); + } + sb.append(')'); + return sb.toString(); + } + + private static String describeValue(Object value) { + return value == null ? "null" : value.getClass().getName(); + } +} diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home_spi.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home_spi.java new file mode 100644 index 00000000000..7a97754b885 --- /dev/null +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_home_spi.java @@ -0,0 +1,580 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package bsh.cn1.gen; + +import bsh.cn1.CN1AccessException; + +public final class GeneratedAccess_com_codename1_home_spi { + private GeneratedAccess_com_codename1_home_spi() { + } + + public static Class findClass(String name) { + if (name == null) { + return null; + } + int dot = name.lastIndexOf('.'); + int dollar = name.lastIndexOf('$'); + int sep = dot > dollar ? dot : dollar; + if (sep < 0 || sep == name.length() - 1) { + return null; + } + return findClassBySimpleName(name.substring(sep + 1)); + } + + public static Class findClassBySimpleName(String simpleName) { + Class found0 = findClassChunk0(simpleName); + if (found0 != null) { + return found0; + } + return null; + } + + + private static Class findClassChunk0(String simpleName) { + if ("HomeBridge".equals(simpleName)) { + return com.codename1.home.spi.HomeBridge.class; + } + return null; + } + public static Object construct(Class type, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + throw unsupportedConstruct(type, safeArgs); + } + + public static Object invokeStatic(Class type, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + throw unsupportedStatic(type, name, safeArgs); + } + + public static Object invoke(Object target, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + CN1AccessException unsupported = null; + if (target instanceof com.codename1.home.spi.HomeBridge) { + try { + return invoke0((com.codename1.home.spi.HomeBridge) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (unsupported != null) { + throw unsupported; + } + throw unsupportedInstance(target, name, safeArgs); + } + + private static Object invoke0(com.codename1.home.spi.HomeBridge typedTarget, String name, Object[] safeArgs) throws Exception { + if ("areIdsPersistent".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.areIdsPersistent(); + } + } + if ("commission".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Integer.class}, false); + typedTarget.commission(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], (java.lang.String) adaptedArgs[3], (java.lang.String) adaptedArgs[4], toIntValue(adaptedArgs[5])); return null; + } + } + if ("createScene".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String[].class, java.lang.String[].class, java.lang.String[].class, int[].class, double[].class, java.lang.String[].class, int[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String[].class, java.lang.String[].class, java.lang.String[].class, int[].class, double[].class, java.lang.String[].class, int[].class}, false); + typedTarget.createScene(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], (java.lang.String[]) adaptedArgs[3], (java.lang.String[]) adaptedArgs[4], (java.lang.String[]) adaptedArgs[5], (int[]) adaptedArgs[6], (double[]) adaptedArgs[7], (java.lang.String[]) adaptedArgs[8], (int[]) adaptedArgs[9]); return null; + } + } + if ("deleteScene".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class}, false); + typedTarget.deleteScene(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); return null; + } + } + if ("drainChanges".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.drainChanges(toIntValue(adaptedArgs[0])); return null; + } + } + if ("executeScene".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String.class}, false); + typedTarget.executeScene(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); return null; + } + } + if ("getAccessories".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getAccessories((java.lang.String) adaptedArgs[0]); + } + } + if ("getAuthorizationStatus".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAuthorizationStatus(); + } + } + if ("getAvailability".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getAvailability(); + } + } + if ("getBackendId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBackendId(); + } + } + if ("getCommissioningStyle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getCommissioningStyle(); + } + } + if ("getConfigurationProblems".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getConfigurationProblems(); + } + } + if ("getMaxReadBatchSize".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMaxReadBatchSize(); + } + } + if ("getMaxWriteBatchSize".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getMaxWriteBatchSize(); + } + } + if ("getRooms".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getRooms((java.lang.String) adaptedArgs[0]); + } + } + if ("getSceneActions".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + return typedTarget.getSceneActions((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); + } + } + if ("getScenes".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getScenes((java.lang.String) adaptedArgs[0]); + } + } + if ("getServices".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getServices((java.lang.String) adaptedArgs[0]); + } + } + if ("getStructures".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getStructures(); + } + } + if ("getTraits".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + return typedTarget.getTraits((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); + } + } + if ("getZones".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getZones((java.lang.String) adaptedArgs[0]); + } + } + if ("identify".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class}, false); + typedTarget.identify(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1]); return null; + } + } + if ("isPushDelivery".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isPushDelivery(); + } + } + if ("isSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isSupported(); + } + } + if ("openEcosystemApp".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.openEcosystemApp(); + } + } + if ("openHomeSettings".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.openHomeSettings(); + } + } + if ("openProviderSetup".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.openProviderSetup(); + } + } + if ("readTraits".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String[].class, java.lang.String[].class, java.lang.String[].class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String[].class, java.lang.String[].class, java.lang.String[].class, java.lang.Boolean.class}, false); + typedTarget.readTraits(toIntValue(adaptedArgs[0]), (java.lang.String[]) adaptedArgs[1], (java.lang.String[]) adaptedArgs[2], (java.lang.String[]) adaptedArgs[3], ((Boolean) adaptedArgs[4]).booleanValue()); return null; + } + } + if ("refresh".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.refresh(toIntValue(adaptedArgs[0])); return null; + } + } + if ("requestAuthorization".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.requestAuthorization(toIntValue(adaptedArgs[0])); return null; + } + } + if ("start".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.start(toIntValue(adaptedArgs[0])); return null; + } + } + if ("stop".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.stop(); return null; + } + } + if ("subscribe".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String[].class, java.lang.String[].class, java.lang.String[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String.class, java.lang.String[].class, java.lang.String[].class, java.lang.String[].class}, false); + typedTarget.subscribe(toIntValue(adaptedArgs[0]), (java.lang.String) adaptedArgs[1], (java.lang.String[]) adaptedArgs[2], (java.lang.String[]) adaptedArgs[3], (java.lang.String[]) adaptedArgs[4]); return null; + } + } + if ("unsubscribe".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.unsubscribe((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("writeTraits".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String[].class, java.lang.String[].class, java.lang.String[].class, int[].class, double[].class, java.lang.String[].class, int[].class, java.lang.String[].class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.String[].class, java.lang.String[].class, java.lang.String[].class, int[].class, double[].class, java.lang.String[].class, int[].class, java.lang.String[].class}, false); + typedTarget.writeTraits(toIntValue(adaptedArgs[0]), (java.lang.String[]) adaptedArgs[1], (java.lang.String[]) adaptedArgs[2], (java.lang.String[]) adaptedArgs[3], (int[]) adaptedArgs[4], (double[]) adaptedArgs[5], (java.lang.String[]) adaptedArgs[6], (int[]) adaptedArgs[7], (java.lang.String[]) adaptedArgs[8]); return null; + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + public static Object getStaticField(Class type, String name) throws Exception { + throw unsupportedStaticField(type, name); + } + + public static Object getField(Object target, String name) throws Exception { + throw unsupportedField(target, name); + } + + public static void setStaticField(Class type, String name, Object value) throws Exception { + throw unsupportedStaticFieldWrite(type, name, value); + } + + public static void setField(Object target, String name, Object value) throws Exception { + throw unsupportedFieldWrite(target, name, value); + } + + private static Object[] safeArgs(Object[] args) { + return args == null ? new Object[0] : args; + } + + private static Object[] adaptArgs(Object[] args, Class[] paramTypes, boolean varArgs) { + if (args == null || args.length == 0) { + return args == null ? new Object[0] : args; + } + Object[] adapted = args.clone(); + if (!varArgs) { + for (int i = 0; i < Math.min(adapted.length, paramTypes.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + return adapted; + } + if (paramTypes.length == 0) { + return adapted; + } + int fixedCount = paramTypes.length - 1; + for (int i = 0; i < Math.min(fixedCount, adapted.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < adapted.length; i++) { + adapted[i] = adaptValue(adapted[i], componentType); + } + return adapted; + } + + private static boolean isSamInterface(Class type) { + if (type == com.codename1.util.OnComplete.class) { + return true; + } + if (type == com.codename1.util.SuccessCallback.class) { + return true; + } + if (type == com.codename1.util.FailureCallback.class) { + return true; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return true; + } + if (type == java.lang.Runnable.class) { + return true; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return true; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return true; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return true; + } + return false; + } + + private static Object adaptLambdaValue(final bsh.cn1.CN1LambdaSupport.LambdaValue lambda, Class type) { + if (type == com.codename1.util.OnComplete.class) { + return new com.codename1.util.OnComplete() { + public void completed(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.SuccessCallback.class) { + return new com.codename1.util.SuccessCallback() { + public void onSucess(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.FailureCallback.class) { + return new com.codename1.util.FailureCallback() { + public void onError(java.lang.Object arg0, java.lang.Throwable arg1, int arg2, java.lang.String arg3) { + try { + lambda.invoke(new Object[]{arg0, arg1, arg2, arg3}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == java.lang.Runnable.class) { + return new java.lang.Runnable() { + public void run() { + try { + lambda.invoke(new Object[0]); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return new com.codename1.ui.events.DataChangedListener() { + public void dataChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return new com.codename1.ui.events.SelectionListener() { + public void selectionChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return new com.codename1.printing.PrintResultListener() { + public void onResult(com.codename1.printing.PrintResult arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + return lambda; + } + + private static Object adaptValue(Object value, Class type) { + if (!(value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue)) { + return value; + } + // Direct fit when LambdaValue already implements the target SAM + // (Runnable, Function, Comparator, ...). + if (type.isInstance(value)) { + return value; + } + return adaptLambdaValue((bsh.cn1.CN1LambdaSupport.LambdaValue) value, type); + } + + private static int toIntValue(Object value) { + if (value instanceof Number) return ((Number) value).intValue(); + if (value instanceof Character) return (int) ((Character) value).charValue(); + throw new ClassCastException("Cannot coerce " + + (value == null ? "null" : value.getClass().getName()) + " to int"); + } + + private static boolean matches(Object[] args, Class[] paramTypes, boolean varArgs) { + if (!varArgs) { + if (args.length != paramTypes.length) { + return false; + } + for (int i = 0; i < paramTypes.length; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + return true; + } + if (paramTypes.length == 0) { + return true; + } + int fixedCount = paramTypes.length - 1; + if (args.length < fixedCount) { + return false; + } + for (int i = 0; i < fixedCount; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < args.length; i++) { + if (!matchesType(args[i], componentType)) { + return false; + } + } + return true; + } + + private static boolean matchesType(Object value, Class type) { + if (type == Object.class) { + return true; + } + if (value == null) { + return !type.isPrimitive(); + } + if (type.isArray()) { + return type.isInstance(value); + } + if ("boolean".equals(type.getName()) || type == Boolean.class) { + return value instanceof Boolean; + } + if ("char".equals(type.getName()) || type == Character.class) { + return value instanceof Character; + } + if ("byte".equals(type.getName()) || type == Byte.class || "short".equals(type.getName()) || type == Short.class + || "int".equals(type.getName()) || type == Integer.class || "long".equals(type.getName()) || type == Long.class + || "float".equals(type.getName()) || type == Float.class || "double".equals(type.getName()) || type == Double.class) { + // Java widens char to int implicitly, so accept Character + // for any int-or-larger numeric slot. + return value instanceof Number || value instanceof Character; + } + if (value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue) { + // LambdaValue implements common SAMs directly (Runnable, + // Function, Predicate, Comparator, ...). Also accept any + // CN1 SAM the listener-bridge knows how to wrap. + return type.isInstance(value) || isSamInterface(type); + } + return type.isInstance(value); + } + + private static CN1AccessException unsupportedConstruct(Class type, Object[] args) { + return new CN1AccessException("Generated constructor dispatch not implemented for " + type.getName() + describeArgs(args)); + } + + private static CN1AccessException unsupportedStatic(Class type, String name, Object[] args) { + return new CN1AccessException("Generated static dispatch not implemented for " + type.getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedInstance(Object target, String name, Object[] args) { + return new CN1AccessException("Generated instance dispatch not implemented for " + target.getClass().getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedStaticField(Class type, String name) { + return new CN1AccessException("Generated static field access not implemented for " + type.getName() + "." + name); + } + + private static CN1AccessException unsupportedField(Object target, String name) { + return new CN1AccessException("Generated field access not implemented for " + target.getClass().getName() + "." + name); + } + + private static CN1AccessException unsupportedStaticFieldWrite(Class type, String name, Object value) { + return new CN1AccessException("Generated static field write not implemented for " + type.getName() + "." + name + " value=" + describeValue(value)); + } + + private static CN1AccessException unsupportedFieldWrite(Object target, String name, Object value) { + return new CN1AccessException("Generated field write not implemented for " + target.getClass().getName() + "." + name + " value=" + describeValue(value)); + } + + private static String describeArgs(Object[] args) { + if (args == null || args.length == 0) { + return "()"; + } + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(describeValue(args[i])); + } + sb.append(')'); + return sb.toString(); + } + + private static String describeValue(Object value) { + return value == null ? "null" : value.getClass().getName(); + } +} diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_intents.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_intents.java new file mode 100644 index 00000000000..64145d54a8f --- /dev/null +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_intents.java @@ -0,0 +1,1168 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package bsh.cn1.gen; + +import bsh.cn1.CN1AccessException; + +public final class GeneratedAccess_com_codename1_intents { + private GeneratedAccess_com_codename1_intents() { + } + + public static Class findClass(String name) { + if (name == null) { + return null; + } + int dot = name.lastIndexOf('.'); + int dollar = name.lastIndexOf('$'); + int sep = dot > dollar ? dot : dollar; + if (sep < 0 || sep == name.length() - 1) { + return null; + } + return findClassBySimpleName(name.substring(sep + 1)); + } + + public static Class findClassBySimpleName(String simpleName) { + Class found0 = findClassChunk0(simpleName); + if (found0 != null) { + return found0; + } + return null; + } + + + private static Class findClassChunk0(String simpleName) { + if ("AppEntity".equals(simpleName)) { + return com.codename1.intents.AppEntity.class; + } + if ("DynamicIntent".equals(simpleName)) { + return com.codename1.intents.DynamicIntent.class; + } + if ("EntitySelectionHandler".equals(simpleName)) { + return com.codename1.intents.EntitySelectionHandler.class; + } + if ("Exposure".equals(simpleName)) { + return com.codename1.intents.Exposure.class; + } + if ("IntentCompletion".equals(simpleName)) { + return com.codename1.intents.IntentCompletion.class; + } + if ("IntentContext".equals(simpleName)) { + return com.codename1.intents.IntentContext.class; + } + if ("IntentDates".equals(simpleName)) { + return com.codename1.intents.IntentDates.class; + } + if ("IntentDeclaration".equals(simpleName)) { + return com.codename1.intents.IntentDeclaration.class; + } + if ("IntentDispatcher".equals(simpleName)) { + return com.codename1.intents.IntentDispatcher.class; + } + if ("IntentParameterInfo".equals(simpleName)) { + return com.codename1.intents.IntentParameterInfo.class; + } + if ("IntentParameterType".equals(simpleName)) { + return com.codename1.intents.IntentParameterType.class; + } + if ("IntentResult".equals(simpleName)) { + return com.codename1.intents.IntentResult.class; + } + if ("IntentSerializer".equals(simpleName)) { + return com.codename1.intents.IntentSerializer.class; + } + if ("IntentSource".equals(simpleName)) { + return com.codename1.intents.IntentSource.class; + } + if ("Intents".equals(simpleName)) { + return com.codename1.intents.Intents.class; + } + return null; + } + public static Object construct(Class type, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + if (type == com.codename1.intents.AppEntity.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + return new com.codename1.intents.AppEntity((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); + } + } + if (type == com.codename1.intents.DynamicIntent.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class}, false); + return new com.codename1.intents.DynamicIntent((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); + } + } + if (type == com.codename1.intents.IntentContext.class) { + if (matches(safeArgs, new Class[]{com.codename1.intents.IntentSource.class, java.lang.Boolean.class, java.lang.Long.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.IntentSource.class, java.lang.Boolean.class, java.lang.Long.class}, false); + return new com.codename1.intents.IntentContext((com.codename1.intents.IntentSource) adaptedArgs[0], ((Boolean) adaptedArgs[1]).booleanValue(), ((Number) adaptedArgs[2]).longValue()); + } + } + if (type == com.codename1.intents.IntentDeclaration.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.String.class, java.lang.Integer.class, java.util.List.class, java.util.List.class, java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.String.class, java.lang.Integer.class, java.util.List.class, java.util.List.class, java.util.List.class}, false); + return new com.codename1.intents.IntentDeclaration((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2], ((Boolean) adaptedArgs[3]).booleanValue(), ((Boolean) adaptedArgs[4]).booleanValue(), ((Boolean) adaptedArgs[5]).booleanValue(), (java.lang.String) adaptedArgs[6], toIntValue(adaptedArgs[7]), (java.util.List) adaptedArgs[8], (java.util.List) adaptedArgs[9], (java.util.List) adaptedArgs[10]); + } + } + if (type == com.codename1.intents.IntentParameterInfo.class) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.intents.IntentParameterType.class, java.lang.Boolean.class, java.lang.String.class, java.lang.String.class, java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.intents.IntentParameterType.class, java.lang.Boolean.class, java.lang.String.class, java.lang.String.class, java.util.List.class}, false); + return new com.codename1.intents.IntentParameterInfo((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.intents.IntentParameterType) adaptedArgs[2], ((Boolean) adaptedArgs[3]).booleanValue(), (java.lang.String) adaptedArgs[4], (java.lang.String) adaptedArgs[5], (java.util.List) adaptedArgs[6]); + } + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.intents.IntentParameterType.class, java.lang.Boolean.class, java.lang.String.class, java.lang.String.class, java.util.List.class, java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, com.codename1.intents.IntentParameterType.class, java.lang.Boolean.class, java.lang.String.class, java.lang.String.class, java.util.List.class, java.lang.Integer.class}, false); + return new com.codename1.intents.IntentParameterInfo((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (com.codename1.intents.IntentParameterType) adaptedArgs[2], ((Boolean) adaptedArgs[3]).booleanValue(), (java.lang.String) adaptedArgs[4], (java.lang.String) adaptedArgs[5], (java.util.List) adaptedArgs[6], toIntValue(adaptedArgs[7])); + } + } + throw unsupportedConstruct(type, safeArgs); + } + + public static Object invokeStatic(Class type, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + if (type == com.codename1.intents.IntentDates.class) return invokeStatic0(name, safeArgs); + if (type == com.codename1.intents.IntentResult.class) return invokeStatic1(name, safeArgs); + if (type == com.codename1.intents.IntentSerializer.class) return invokeStatic2(name, safeArgs); + if (type == com.codename1.intents.Intents.class) return invokeStatic3(name, safeArgs); + throw unsupportedStatic(type, name, safeArgs); + } + + private static Object invokeStatic0(String name, Object[] safeArgs) throws Exception { + if ("parse".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Object.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Object.class}, false); + return com.codename1.intents.IntentDates.parse((java.lang.Object) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.intents.IntentDates.class, name, safeArgs); + } + + private static Object invokeStatic1(String name, Object[] safeArgs) throws Exception { + if ("entity".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.AppEntity.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.AppEntity.class}, false); + return com.codename1.intents.IntentResult.entity((com.codename1.intents.AppEntity) adaptedArgs[0]); + } + } + if ("failed".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.intents.IntentResult.failed((java.lang.String) adaptedArgs[0]); + } + } + if ("ok".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.intents.IntentResult.ok(); + } + } + if ("opens".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.intents.IntentResult.opens((java.lang.String) adaptedArgs[0]); + } + } + if ("spoken".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.intents.IntentResult.spoken((java.lang.String) adaptedArgs[0]); + } + } + if ("value".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Object.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Object.class}, false); + return com.codename1.intents.IntentResult.value((java.lang.Object) adaptedArgs[0]); + } + } + throw unsupportedStatic(com.codename1.intents.IntentResult.class, name, safeArgs); + } + + private static Object invokeStatic2(String name, Object[] safeArgs) throws Exception { + if ("mergeParams".equals(name)) { + if (matches(safeArgs, new Class[]{java.util.Map.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.Map.class, java.lang.String.class}, false); + return com.codename1.intents.IntentSerializer.mergeParams((java.util.Map) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); + } + } + if ("parsePayload".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.intents.IntentSerializer.parsePayload((java.lang.String) adaptedArgs[0]); + } + } + if ("serializeDeclarations".equals(name)) { + if (matches(safeArgs, new Class[]{java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.List.class}, false); + return com.codename1.intents.IntentSerializer.serializeDeclarations((java.util.List) adaptedArgs[0]); + } + } + if ("serializeEntities".equals(name)) { + if (matches(safeArgs, new Class[]{java.util.List.class, java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.List.class, java.util.Map.class}, false); + return com.codename1.intents.IntentSerializer.serializeEntities((java.util.List) adaptedArgs[0], (java.util.Map) adaptedArgs[1]); + } + if (matches(safeArgs, new Class[]{java.util.List.class, java.util.Map.class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.List.class, java.util.Map.class, java.lang.Boolean.class}, false); + return com.codename1.intents.IntentSerializer.serializeEntities((java.util.List) adaptedArgs[0], (java.util.Map) adaptedArgs[1], ((Boolean) adaptedArgs[2]).booleanValue()); + } + } + if ("serializeEntityRef".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + return com.codename1.intents.IntentSerializer.serializeEntityRef((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); + } + } + if ("serializeParams".equals(name)) { + if (matches(safeArgs, new Class[]{java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.Map.class}, false); + return com.codename1.intents.IntentSerializer.serializeParams((java.util.Map) adaptedArgs[0]); + } + } + if ("serializeResult".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.IntentResult.class, java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.IntentResult.class, java.util.Map.class}, false); + return com.codename1.intents.IntentSerializer.serializeResult((com.codename1.intents.IntentResult) adaptedArgs[0], (java.util.Map) adaptedArgs[1]); + } + } + throw unsupportedStatic(com.codename1.intents.IntentSerializer.class, name, safeArgs); + } + + private static Object invokeStatic3(String name, Object[] safeArgs) throws Exception { + if ("areIntentsSupported".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.intents.Intents.areIntentsSupported(); + } + } + if ("asTools".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.intents.Intents.asTools(); + } + } + if ("clearIndex".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + com.codename1.intents.Intents.clearIndex((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("dispatchInvocation".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class, com.codename1.intents.IntentSource.class, java.lang.Boolean.class, com.codename1.intents.IntentCompletion.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class, com.codename1.intents.IntentSource.class, java.lang.Boolean.class, com.codename1.intents.IntentCompletion.class}, false); + com.codename1.intents.Intents.dispatchInvocation((java.lang.String) adaptedArgs[0], (java.util.Map) adaptedArgs[1], (com.codename1.intents.IntentSource) adaptedArgs[2], ((Boolean) adaptedArgs[3]).booleanValue(), (com.codename1.intents.IntentCompletion) adaptedArgs[4]); return null; + } + } + if ("dispatchSpotlightSelection".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + com.codename1.intents.Intents.dispatchSpotlightSelection((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("dispatchUserActivity".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class}, false); + return com.codename1.intents.Intents.dispatchUserActivity((java.lang.String) adaptedArgs[0], (java.util.Map) adaptedArgs[1]); + } + } + if ("donate".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class}, false); + com.codename1.intents.Intents.donate((java.lang.String) adaptedArgs[0], (java.util.Map) adaptedArgs[1]); return null; + } + } + if ("getDeclaration".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.intents.Intents.getDeclaration((java.lang.String) adaptedArgs[0]); + } + } + if ("getDeclarations".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.intents.Intents.getDeclarations(); + } + } + if ("getDefaultTimeout".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.intents.Intents.getDefaultTimeout(); + } + } + if ("getDynamicIntent".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return com.codename1.intents.Intents.getDynamicIntent((java.lang.String) adaptedArgs[0]); + } + } + if ("index".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.AppEntity.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.AppEntity.class}, false); + com.codename1.intents.Intents.index((com.codename1.intents.AppEntity) adaptedArgs[0]); return null; + } + if (matches(safeArgs, new Class[]{java.util.List.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.List.class}, false); + com.codename1.intents.Intents.index((java.util.List) adaptedArgs[0]); return null; + } + } + if ("invoke".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class}, false); + return com.codename1.intents.Intents.invoke((java.lang.String) adaptedArgs[0], (java.util.Map) adaptedArgs[1]); + } + } + if ("isHeadlessExecutionSupported".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.intents.Intents.isHeadlessExecutionSupported(); + } + } + if ("isIndexingSupported".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.intents.Intents.isIndexingSupported(); + } + } + if ("isVoiceInvocationSupported".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.intents.Intents.isVoiceInvocationSupported(); + } + } + if ("publishPendingDeclarations".equals(name)) { + if (safeArgs.length == 0) { + com.codename1.intents.Intents.publishPendingDeclarations(); return null; + } + } + if ("queryEntities".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class}, false); + return com.codename1.intents.Intents.queryEntities((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); + } + } + if ("registerDynamicIntent".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.DynamicIntent.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.DynamicIntent.class}, false); + com.codename1.intents.Intents.registerDynamicIntent((com.codename1.intents.DynamicIntent) adaptedArgs[0]); return null; + } + } + if ("removeFromIndex".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + com.codename1.intents.Intents.removeFromIndex((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); return null; + } + } + if ("setBridge".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.spi.IntentBridge.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.spi.IntentBridge.class}, false); + com.codename1.intents.Intents.setBridge((com.codename1.intents.spi.IntentBridge) adaptedArgs[0]); return null; + } + } + if ("setDefaultTimeout".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + com.codename1.intents.Intents.setDefaultTimeout(toIntValue(adaptedArgs[0])); return null; + } + } + if ("setDispatcher".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.IntentDispatcher.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.IntentDispatcher.class}, false); + com.codename1.intents.Intents.setDispatcher((com.codename1.intents.IntentDispatcher) adaptedArgs[0]); return null; + } + } + if ("setSelectionHandler".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.EntitySelectionHandler.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.EntitySelectionHandler.class}, false); + com.codename1.intents.Intents.setSelectionHandler((com.codename1.intents.EntitySelectionHandler) adaptedArgs[0]); return null; + } + } + throw unsupportedStatic(com.codename1.intents.Intents.class, name, safeArgs); + } + + public static Object invoke(Object target, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + CN1AccessException unsupported = null; + if (target instanceof com.codename1.intents.AppEntity) { + try { + return invoke0((com.codename1.intents.AppEntity) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.intents.DynamicIntent) { + try { + return invoke1((com.codename1.intents.DynamicIntent) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.intents.IntentContext) { + try { + return invoke2((com.codename1.intents.IntentContext) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.intents.IntentDeclaration) { + try { + return invoke3((com.codename1.intents.IntentDeclaration) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.intents.IntentParameterInfo) { + try { + return invoke4((com.codename1.intents.IntentParameterInfo) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.intents.IntentResult) { + try { + return invoke5((com.codename1.intents.IntentResult) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.intents.EntitySelectionHandler) { + try { + return invoke6((com.codename1.intents.EntitySelectionHandler) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.intents.IntentCompletion) { + try { + return invoke7((com.codename1.intents.IntentCompletion) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (target instanceof com.codename1.intents.IntentDispatcher) { + try { + return invoke8((com.codename1.intents.IntentDispatcher) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (unsupported != null) { + throw unsupported; + } + throw unsupportedInstance(target, name, safeArgs); + } + + private static Object invoke0(com.codename1.intents.AppEntity typedTarget, String name, Object[] safeArgs) throws Exception { + if ("addKeywords".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String[].class}, true)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String[].class}, true); + java.lang.String[] varArgs = new java.lang.String[adaptedArgs.length - 0]; + for (int i = 0; i < adaptedArgs.length; i++) { + varArgs[i - 0] = (java.lang.String) adaptedArgs[i]; + } + return typedTarget.addKeywords(varArgs); + } + } + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getImage".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getImage(); + } + } + if ("getKeywords".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getKeywords(); + } + } + if ("getSubtitle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSubtitle(); + } + } + if ("getTitle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTitle(); + } + } + if ("getType".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getType(); + } + } + if ("setImage".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.EncodedImage.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.EncodedImage.class}, false); + return typedTarget.setImage((com.codename1.ui.EncodedImage) adaptedArgs[0]); + } + } + if ("setSubtitle".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.setSubtitle((java.lang.String) adaptedArgs[0]); + } + } + if ("setTitle".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.setTitle((java.lang.String) adaptedArgs[0]); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke1(com.codename1.intents.DynamicIntent typedTarget, String name, Object[] safeArgs) throws Exception { + if ("bind".equals(name)) { + if (matches(safeArgs, new Class[]{java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.Map.class}, false); + return typedTarget.bind((java.util.Map) adaptedArgs[0]); + } + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Object.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Object.class}, false); + return typedTarget.bind((java.lang.String) adaptedArgs[0], (java.lang.Object) adaptedArgs[1]); + } + } + if ("getBaseIntentId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBaseIntentId(); + } + } + if ("getBoundParameters".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getBoundParameters(); + } + } + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getTitle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTitle(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke2(com.codename1.intents.IntentContext typedTarget, String name, Object[] safeArgs) throws Exception { + if ("cancel".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.cancel(); return null; + } + } + if ("getDeadline".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDeadline(); + } + } + if ("getRemainingTime".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getRemainingTime(); + } + } + if ("getSource".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSource(); + } + } + if ("isCancelled".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isCancelled(); + } + } + if ("isHeadless".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHeadless(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke3(com.codename1.intents.IntentDeclaration typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getDescription".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDescription(); + } + } + if ("getExposure".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getExposure(); + } + } + if ("getId".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getId(); + } + } + if ("getOpensRoute".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOpensRoute(); + } + } + if ("getParameter".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.getParameter((java.lang.String) adaptedArgs[0]); + } + } + if ("getParameters".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getParameters(); + } + } + if ("getPhrases".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getPhrases(); + } + } + if ("getTimeoutSeconds".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTimeoutSeconds(); + } + } + if ("getTitle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTitle(); + } + } + if ("isDestructive".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isDestructive(); + } + } + if ("isDiscoverable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isDiscoverable(); + } + } + if ("isExposedTo".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.Exposure.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.Exposure.class}, false); + return typedTarget.isExposedTo((com.codename1.intents.Exposure) adaptedArgs[0]); + } + } + if ("isHeadless".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHeadless(); + } + } + if ("runsHeadless".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.runsHeadless(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke4(com.codename1.intents.IntentParameterInfo typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getDefaultValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDefaultValue(); + } + } + if ("getEntityType".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getEntityType(); + } + } + if ("getName".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getName(); + } + } + if ("getNumericWidthBits".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getNumericWidthBits(); + } + } + if ("getOptions".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOptions(); + } + } + if ("getTitle".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTitle(); + } + } + if ("getType".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getType(); + } + } + if ("isRequired".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isRequired(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke5(com.codename1.intents.IntentResult typedTarget, String name, Object[] safeArgs) throws Exception { + if ("getDialog".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getDialog(); + } + } + if ("getEntity".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getEntity(); + } + } + if ("getErrorMessage".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getErrorMessage(); + } + } + if ("getOpenUrl".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getOpenUrl(); + } + } + if ("getSnippet".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getSnippet(); + } + } + if ("getValue".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getValue(); + } + } + if ("isFailed".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isFailed(); + } + } + if ("toString".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.toString(); + } + } + if ("withDialog".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.withDialog((java.lang.String) adaptedArgs[0]); + } + } + if ("withOpenUrl".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.withOpenUrl((java.lang.String) adaptedArgs[0]); + } + } + if ("withSnippet".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.surfaces.SurfaceNode.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.surfaces.SurfaceNode.class}, false); + return typedTarget.withSnippet((com.codename1.surfaces.SurfaceNode) adaptedArgs[0]); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke6(com.codename1.intents.EntitySelectionHandler typedTarget, String name, Object[] safeArgs) throws Exception { + if ("onEntitySelected".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.AppEntity.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.AppEntity.class}, false); + typedTarget.onEntitySelected((com.codename1.intents.AppEntity) adaptedArgs[0]); return null; + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke7(com.codename1.intents.IntentCompletion typedTarget, String name, Object[] safeArgs) throws Exception { + if ("onIntentResult".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.intents.IntentResult.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.intents.IntentResult.class}, false); + typedTarget.onIntentResult((com.codename1.intents.IntentResult) adaptedArgs[0]); return null; + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke8(com.codename1.intents.IntentDispatcher typedTarget, String name, Object[] safeArgs) throws Exception { + if ("describe".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.describe(); + } + } + if ("invoke".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class, com.codename1.intents.IntentContext.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class, com.codename1.intents.IntentContext.class}, false); + return typedTarget.invoke((java.lang.String) adaptedArgs[0], (java.util.Map) adaptedArgs[1], (com.codename1.intents.IntentContext) adaptedArgs[2]); + } + } + if ("queryEntities".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.lang.String.class}, false); + return typedTarget.queryEntities((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + public static Object getStaticField(Class type, String name) throws Exception { + if (type == com.codename1.intents.Exposure.class) return getStaticField0(name); + if (type == com.codename1.intents.IntentParameterType.class) return getStaticField1(name); + if (type == com.codename1.intents.IntentSource.class) return getStaticField2(name); + throw unsupportedStaticField(type, name); + } + + private static Object getStaticField0(String name) throws Exception { + if ("ASSISTANT".equals(name)) return com.codename1.intents.Exposure.ASSISTANT; + if ("MODEL".equals(name)) return com.codename1.intents.Exposure.MODEL; + throw unsupportedStaticField(com.codename1.intents.Exposure.class, name); + } + + private static Object getStaticField1(String name) throws Exception { + if ("BOOLEAN".equals(name)) return com.codename1.intents.IntentParameterType.BOOLEAN; + if ("DATE".equals(name)) return com.codename1.intents.IntentParameterType.DATE; + if ("ENTITY".equals(name)) return com.codename1.intents.IntentParameterType.ENTITY; + if ("INTEGER".equals(name)) return com.codename1.intents.IntentParameterType.INTEGER; + if ("NUMBER".equals(name)) return com.codename1.intents.IntentParameterType.NUMBER; + if ("STRING".equals(name)) return com.codename1.intents.IntentParameterType.STRING; + throw unsupportedStaticField(com.codename1.intents.IntentParameterType.class, name); + } + + private static Object getStaticField2(String name) throws Exception { + if ("IN_APP".equals(name)) return com.codename1.intents.IntentSource.IN_APP; + if ("MODEL".equals(name)) return com.codename1.intents.IntentSource.MODEL; + if ("SHORTCUT".equals(name)) return com.codename1.intents.IntentSource.SHORTCUT; + if ("SPOTLIGHT".equals(name)) return com.codename1.intents.IntentSource.SPOTLIGHT; + if ("UNKNOWN".equals(name)) return com.codename1.intents.IntentSource.UNKNOWN; + if ("VOICE".equals(name)) return com.codename1.intents.IntentSource.VOICE; + if ("WIDGET".equals(name)) return com.codename1.intents.IntentSource.WIDGET; + throw unsupportedStaticField(com.codename1.intents.IntentSource.class, name); + } + + public static Object getField(Object target, String name) throws Exception { + throw unsupportedField(target, name); + } + + public static void setStaticField(Class type, String name, Object value) throws Exception { + throw unsupportedStaticFieldWrite(type, name, value); + } + + public static void setField(Object target, String name, Object value) throws Exception { + throw unsupportedFieldWrite(target, name, value); + } + + private static Object[] safeArgs(Object[] args) { + return args == null ? new Object[0] : args; + } + + private static Object[] adaptArgs(Object[] args, Class[] paramTypes, boolean varArgs) { + if (args == null || args.length == 0) { + return args == null ? new Object[0] : args; + } + Object[] adapted = args.clone(); + if (!varArgs) { + for (int i = 0; i < Math.min(adapted.length, paramTypes.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + return adapted; + } + if (paramTypes.length == 0) { + return adapted; + } + int fixedCount = paramTypes.length - 1; + for (int i = 0; i < Math.min(fixedCount, adapted.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < adapted.length; i++) { + adapted[i] = adaptValue(adapted[i], componentType); + } + return adapted; + } + + private static boolean isSamInterface(Class type) { + if (type == com.codename1.util.OnComplete.class) { + return true; + } + if (type == com.codename1.util.SuccessCallback.class) { + return true; + } + if (type == com.codename1.util.FailureCallback.class) { + return true; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return true; + } + if (type == java.lang.Runnable.class) { + return true; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return true; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return true; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return true; + } + return false; + } + + private static Object adaptLambdaValue(final bsh.cn1.CN1LambdaSupport.LambdaValue lambda, Class type) { + if (type == com.codename1.util.OnComplete.class) { + return new com.codename1.util.OnComplete() { + public void completed(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.SuccessCallback.class) { + return new com.codename1.util.SuccessCallback() { + public void onSucess(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.FailureCallback.class) { + return new com.codename1.util.FailureCallback() { + public void onError(java.lang.Object arg0, java.lang.Throwable arg1, int arg2, java.lang.String arg3) { + try { + lambda.invoke(new Object[]{arg0, arg1, arg2, arg3}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == java.lang.Runnable.class) { + return new java.lang.Runnable() { + public void run() { + try { + lambda.invoke(new Object[0]); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return new com.codename1.ui.events.DataChangedListener() { + public void dataChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return new com.codename1.ui.events.SelectionListener() { + public void selectionChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return new com.codename1.printing.PrintResultListener() { + public void onResult(com.codename1.printing.PrintResult arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + return lambda; + } + + private static Object adaptValue(Object value, Class type) { + if (!(value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue)) { + return value; + } + // Direct fit when LambdaValue already implements the target SAM + // (Runnable, Function, Comparator, ...). + if (type.isInstance(value)) { + return value; + } + return adaptLambdaValue((bsh.cn1.CN1LambdaSupport.LambdaValue) value, type); + } + + private static int toIntValue(Object value) { + if (value instanceof Number) return ((Number) value).intValue(); + if (value instanceof Character) return (int) ((Character) value).charValue(); + throw new ClassCastException("Cannot coerce " + + (value == null ? "null" : value.getClass().getName()) + " to int"); + } + + private static boolean matches(Object[] args, Class[] paramTypes, boolean varArgs) { + if (!varArgs) { + if (args.length != paramTypes.length) { + return false; + } + for (int i = 0; i < paramTypes.length; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + return true; + } + if (paramTypes.length == 0) { + return true; + } + int fixedCount = paramTypes.length - 1; + if (args.length < fixedCount) { + return false; + } + for (int i = 0; i < fixedCount; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < args.length; i++) { + if (!matchesType(args[i], componentType)) { + return false; + } + } + return true; + } + + private static boolean matchesType(Object value, Class type) { + if (type == Object.class) { + return true; + } + if (value == null) { + return !type.isPrimitive(); + } + if (type.isArray()) { + return type.isInstance(value); + } + if ("boolean".equals(type.getName()) || type == Boolean.class) { + return value instanceof Boolean; + } + if ("char".equals(type.getName()) || type == Character.class) { + return value instanceof Character; + } + if ("byte".equals(type.getName()) || type == Byte.class || "short".equals(type.getName()) || type == Short.class + || "int".equals(type.getName()) || type == Integer.class || "long".equals(type.getName()) || type == Long.class + || "float".equals(type.getName()) || type == Float.class || "double".equals(type.getName()) || type == Double.class) { + // Java widens char to int implicitly, so accept Character + // for any int-or-larger numeric slot. + return value instanceof Number || value instanceof Character; + } + if (value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue) { + // LambdaValue implements common SAMs directly (Runnable, + // Function, Predicate, Comparator, ...). Also accept any + // CN1 SAM the listener-bridge knows how to wrap. + return type.isInstance(value) || isSamInterface(type); + } + return type.isInstance(value); + } + + private static CN1AccessException unsupportedConstruct(Class type, Object[] args) { + return new CN1AccessException("Generated constructor dispatch not implemented for " + type.getName() + describeArgs(args)); + } + + private static CN1AccessException unsupportedStatic(Class type, String name, Object[] args) { + return new CN1AccessException("Generated static dispatch not implemented for " + type.getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedInstance(Object target, String name, Object[] args) { + return new CN1AccessException("Generated instance dispatch not implemented for " + target.getClass().getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedStaticField(Class type, String name) { + return new CN1AccessException("Generated static field access not implemented for " + type.getName() + "." + name); + } + + private static CN1AccessException unsupportedField(Object target, String name) { + return new CN1AccessException("Generated field access not implemented for " + target.getClass().getName() + "." + name); + } + + private static CN1AccessException unsupportedStaticFieldWrite(Class type, String name, Object value) { + return new CN1AccessException("Generated static field write not implemented for " + type.getName() + "." + name + " value=" + describeValue(value)); + } + + private static CN1AccessException unsupportedFieldWrite(Object target, String name, Object value) { + return new CN1AccessException("Generated field write not implemented for " + target.getClass().getName() + "." + name + " value=" + describeValue(value)); + } + + private static String describeArgs(Object[] args) { + if (args == null || args.length == 0) { + return "()"; + } + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(describeValue(args[i])); + } + sb.append(')'); + return sb.toString(); + } + + private static String describeValue(Object value) { + return value == null ? "null" : value.getClass().getName(); + } +} diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_intents_spi.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_intents_spi.java new file mode 100644 index 00000000000..2e8a929336f --- /dev/null +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_intents_spi.java @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package bsh.cn1.gen; + +import bsh.cn1.CN1AccessException; + +public final class GeneratedAccess_com_codename1_intents_spi { + private GeneratedAccess_com_codename1_intents_spi() { + } + + public static Class findClass(String name) { + if (name == null) { + return null; + } + int dot = name.lastIndexOf('.'); + int dollar = name.lastIndexOf('$'); + int sep = dot > dollar ? dot : dollar; + if (sep < 0 || sep == name.length() - 1) { + return null; + } + return findClassBySimpleName(name.substring(sep + 1)); + } + + public static Class findClassBySimpleName(String simpleName) { + Class found0 = findClassChunk0(simpleName); + if (found0 != null) { + return found0; + } + return null; + } + + + private static Class findClassChunk0(String simpleName) { + if ("IntentBridge".equals(simpleName)) { + return com.codename1.intents.spi.IntentBridge.class; + } + return null; + } + public static Object construct(Class type, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + throw unsupportedConstruct(type, safeArgs); + } + + public static Object invokeStatic(Class type, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + throw unsupportedStatic(type, name, safeArgs); + } + + public static Object invoke(Object target, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + CN1AccessException unsupported = null; + if (target instanceof com.codename1.intents.spi.IntentBridge) { + try { + return invoke0((com.codename1.intents.spi.IntentBridge) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } + if (unsupported != null) { + throw unsupported; + } + throw unsupportedInstance(target, name, safeArgs); + } + + private static Object invoke0(com.codename1.intents.spi.IntentBridge typedTarget, String name, Object[] safeArgs) throws Exception { + if ("areIntentsSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.areIntentsSupported(); + } + } + if ("clearIndex".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.clearIndex((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("completeInvocation".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, java.util.Map.class}, false); + typedTarget.completeInvocation((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.util.Map) adaptedArgs[2]); return null; + } + } + if ("donate".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + typedTarget.donate((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); return null; + } + } + if ("index".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.util.Map.class}, false); + typedTarget.index((java.lang.String) adaptedArgs[0], (java.util.Map) adaptedArgs[1]); return null; + } + } + if ("isHeadlessExecutionSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHeadlessExecutionSupported(); + } + } + if ("isIndexingSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isIndexingSupported(); + } + } + if ("isVoiceInvocationSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isVoiceInvocationSupported(); + } + } + if ("registerIntents".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.registerIntents((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("removeFromIndex".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.removeFromIndex((java.lang.String) adaptedArgs[0]); return null; + } + } + if ("requestForeground".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.requestForeground(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + public static Object getStaticField(Class type, String name) throws Exception { + throw unsupportedStaticField(type, name); + } + + public static Object getField(Object target, String name) throws Exception { + throw unsupportedField(target, name); + } + + public static void setStaticField(Class type, String name, Object value) throws Exception { + throw unsupportedStaticFieldWrite(type, name, value); + } + + public static void setField(Object target, String name, Object value) throws Exception { + throw unsupportedFieldWrite(target, name, value); + } + + private static Object[] safeArgs(Object[] args) { + return args == null ? new Object[0] : args; + } + + private static Object[] adaptArgs(Object[] args, Class[] paramTypes, boolean varArgs) { + if (args == null || args.length == 0) { + return args == null ? new Object[0] : args; + } + Object[] adapted = args.clone(); + if (!varArgs) { + for (int i = 0; i < Math.min(adapted.length, paramTypes.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + return adapted; + } + if (paramTypes.length == 0) { + return adapted; + } + int fixedCount = paramTypes.length - 1; + for (int i = 0; i < Math.min(fixedCount, adapted.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < adapted.length; i++) { + adapted[i] = adaptValue(adapted[i], componentType); + } + return adapted; + } + + private static boolean isSamInterface(Class type) { + if (type == com.codename1.util.OnComplete.class) { + return true; + } + if (type == com.codename1.util.SuccessCallback.class) { + return true; + } + if (type == com.codename1.util.FailureCallback.class) { + return true; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return true; + } + if (type == java.lang.Runnable.class) { + return true; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return true; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return true; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return true; + } + return false; + } + + private static Object adaptLambdaValue(final bsh.cn1.CN1LambdaSupport.LambdaValue lambda, Class type) { + if (type == com.codename1.util.OnComplete.class) { + return new com.codename1.util.OnComplete() { + public void completed(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.SuccessCallback.class) { + return new com.codename1.util.SuccessCallback() { + public void onSucess(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.FailureCallback.class) { + return new com.codename1.util.FailureCallback() { + public void onError(java.lang.Object arg0, java.lang.Throwable arg1, int arg2, java.lang.String arg3) { + try { + lambda.invoke(new Object[]{arg0, arg1, arg2, arg3}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == java.lang.Runnable.class) { + return new java.lang.Runnable() { + public void run() { + try { + lambda.invoke(new Object[0]); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return new com.codename1.ui.events.DataChangedListener() { + public void dataChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return new com.codename1.ui.events.SelectionListener() { + public void selectionChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return new com.codename1.printing.PrintResultListener() { + public void onResult(com.codename1.printing.PrintResult arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + return lambda; + } + + private static Object adaptValue(Object value, Class type) { + if (!(value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue)) { + return value; + } + // Direct fit when LambdaValue already implements the target SAM + // (Runnable, Function, Comparator, ...). + if (type.isInstance(value)) { + return value; + } + return adaptLambdaValue((bsh.cn1.CN1LambdaSupport.LambdaValue) value, type); + } + + private static int toIntValue(Object value) { + if (value instanceof Number) return ((Number) value).intValue(); + if (value instanceof Character) return (int) ((Character) value).charValue(); + throw new ClassCastException("Cannot coerce " + + (value == null ? "null" : value.getClass().getName()) + " to int"); + } + + private static boolean matches(Object[] args, Class[] paramTypes, boolean varArgs) { + if (!varArgs) { + if (args.length != paramTypes.length) { + return false; + } + for (int i = 0; i < paramTypes.length; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + return true; + } + if (paramTypes.length == 0) { + return true; + } + int fixedCount = paramTypes.length - 1; + if (args.length < fixedCount) { + return false; + } + for (int i = 0; i < fixedCount; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < args.length; i++) { + if (!matchesType(args[i], componentType)) { + return false; + } + } + return true; + } + + private static boolean matchesType(Object value, Class type) { + if (type == Object.class) { + return true; + } + if (value == null) { + return !type.isPrimitive(); + } + if (type.isArray()) { + return type.isInstance(value); + } + if ("boolean".equals(type.getName()) || type == Boolean.class) { + return value instanceof Boolean; + } + if ("char".equals(type.getName()) || type == Character.class) { + return value instanceof Character; + } + if ("byte".equals(type.getName()) || type == Byte.class || "short".equals(type.getName()) || type == Short.class + || "int".equals(type.getName()) || type == Integer.class || "long".equals(type.getName()) || type == Long.class + || "float".equals(type.getName()) || type == Float.class || "double".equals(type.getName()) || type == Double.class) { + // Java widens char to int implicitly, so accept Character + // for any int-or-larger numeric slot. + return value instanceof Number || value instanceof Character; + } + if (value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue) { + // LambdaValue implements common SAMs directly (Runnable, + // Function, Predicate, Comparator, ...). Also accept any + // CN1 SAM the listener-bridge knows how to wrap. + return type.isInstance(value) || isSamInterface(type); + } + return type.isInstance(value); + } + + private static CN1AccessException unsupportedConstruct(Class type, Object[] args) { + return new CN1AccessException("Generated constructor dispatch not implemented for " + type.getName() + describeArgs(args)); + } + + private static CN1AccessException unsupportedStatic(Class type, String name, Object[] args) { + return new CN1AccessException("Generated static dispatch not implemented for " + type.getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedInstance(Object target, String name, Object[] args) { + return new CN1AccessException("Generated instance dispatch not implemented for " + target.getClass().getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedStaticField(Class type, String name) { + return new CN1AccessException("Generated static field access not implemented for " + type.getName() + "." + name); + } + + private static CN1AccessException unsupportedField(Object target, String name) { + return new CN1AccessException("Generated field access not implemented for " + target.getClass().getName() + "." + name); + } + + private static CN1AccessException unsupportedStaticFieldWrite(Class type, String name, Object value) { + return new CN1AccessException("Generated static field write not implemented for " + type.getName() + "." + name + " value=" + describeValue(value)); + } + + private static CN1AccessException unsupportedFieldWrite(Object target, String name, Object value) { + return new CN1AccessException("Generated field write not implemented for " + target.getClass().getName() + "." + name + " value=" + describeValue(value)); + } + + private static String describeArgs(Object[] args) { + if (args == null || args.length == 0) { + return "()"; + } + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(describeValue(args[i])); + } + sb.append(')'); + return sb.toString(); + } + + private static String describeValue(Object value) { + return value == null ? "null" : value.getClass().getName(); + } +} diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security.java index db3d7b1425c..97249086235 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security.java @@ -121,6 +121,9 @@ private static Class findClassChunk0(String simpleName) { if ("Signature".equals(simpleName)) { return com.codename1.security.Signature.class; } + if ("TapjackingPolicy".equals(simpleName)) { + return com.codename1.security.TapjackingPolicy.class; + } return null; } public static Object construct(Class type, Object[] args) throws Exception { @@ -244,6 +247,12 @@ private static Object invokeStatic2(String name, Object[] safeArgs) throws Excep } private static Object invokeStatic3(String name, Object[] safeArgs) throws Exception { + if ("addTapjackingListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + com.codename1.security.DeviceIntegrity.addTapjackingListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } if ("confirmAttestation".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); @@ -260,6 +269,11 @@ private static Object invokeStatic3(String name, Object[] safeArgs) throws Excep return com.codename1.security.DeviceIntegrity.getEnabledAccessibilityServices(); } } + if ("getTapjackingPolicy".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.security.DeviceIntegrity.getTapjackingPolicy(); + } + } if ("hasUntrustedAccessibilityService".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String[].class}, true)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String[].class}, true); @@ -280,6 +294,22 @@ private static Object invokeStatic3(String name, Object[] safeArgs) throws Excep return com.codename1.security.DeviceIntegrity.isDeviceCompromised(); } } + if ("isHideOverlayWindowsSupported".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.security.DeviceIntegrity.isHideOverlayWindowsSupported(); + } + } + if ("isScreenObscured".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.security.DeviceIntegrity.isScreenObscured(); + } + } + if ("removeTapjackingListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + com.codename1.security.DeviceIntegrity.removeTapjackingListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } if ("requestIntegrityToken".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); @@ -291,12 +321,24 @@ private static Object invokeStatic3(String name, Object[] safeArgs) throws Excep com.codename1.security.DeviceIntegrity.resetAttestation(); return null; } } + if ("setHideOverlayWindows".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + com.codename1.security.DeviceIntegrity.setHideOverlayWindows(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } if ("setSecureScreen".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); com.codename1.security.DeviceIntegrity.setSecureScreen(((Boolean) adaptedArgs[0]).booleanValue()); return null; } } + if ("setTapjackingProtection".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.security.TapjackingPolicy.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.security.TapjackingPolicy.class}, false); + com.codename1.security.DeviceIntegrity.setTapjackingProtection((com.codename1.security.TapjackingPolicy) adaptedArgs[0]); return null; + } + } throw unsupportedStatic(com.codename1.security.DeviceIntegrity.class, name, safeArgs); } @@ -722,6 +764,13 @@ public static Object invoke(Object target, String name, Object[] args) throws Ex unsupported = ex; } } + if (target instanceof com.codename1.security.TapjackingPolicy) { + try { + return invoke12((com.codename1.security.TapjackingPolicy) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } if (unsupported != null) { throw unsupported; } @@ -1100,6 +1149,12 @@ private static Object invoke10(com.codename1.security.KeyPair typedTarget, Strin } private static Object invoke11(com.codename1.security.SecureStorage typedTarget, String name, Object[] safeArgs) throws Exception { + if ("entryState".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.entryState((java.lang.String) adaptedArgs[0]); + } + } if ("get".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); @@ -1130,6 +1185,12 @@ private static Object invoke11(com.codename1.security.SecureStorage typedTarget, return typedTarget.set((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1], (java.lang.String) adaptedArgs[2]); } } + if ("setIfAbsent".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + return typedTarget.setIfAbsent((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); + } + } if ("setKeychainAccessGroup".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); @@ -1139,6 +1200,21 @@ private static Object invoke11(com.codename1.security.SecureStorage typedTarget, throw unsupportedInstance(typedTarget, name, safeArgs); } + private static Object invoke12(com.codename1.security.TapjackingPolicy typedTarget, String name, Object[] safeArgs) throws Exception { + if ("blocks".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class, java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class, java.lang.Boolean.class}, false); + return typedTarget.blocks(((Boolean) adaptedArgs[0]).booleanValue(), ((Boolean) adaptedArgs[1]).booleanValue()); + } + } + if ("isDetecting".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isDetecting(); + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + public static Object getStaticField(Class type, String name) throws Exception { if (type == com.codename1.security.BiometricError.class) return getStaticField0(name); if (type == com.codename1.security.BiometricType.class) return getStaticField1(name); @@ -1146,7 +1222,9 @@ public static Object getStaticField(Class type, String name) throws Exception if (type == com.codename1.security.Hash.class) return getStaticField3(name); if (type == com.codename1.security.Jwt.class) return getStaticField4(name); if (type == com.codename1.security.PublicKey.class) return getStaticField5(name); - if (type == com.codename1.security.Signature.class) return getStaticField6(name); + if (type == com.codename1.security.SecureStorage.class) return getStaticField6(name); + if (type == com.codename1.security.Signature.class) return getStaticField7(name); + if (type == com.codename1.security.TapjackingPolicy.class) return getStaticField8(name); throw unsupportedStaticField(type, name); } @@ -1214,6 +1292,13 @@ private static Object getStaticField5(String name) throws Exception { } private static Object getStaticField6(String name) throws Exception { + if ("ENTRY_ABSENT".equals(name)) return com.codename1.security.SecureStorage.ENTRY_ABSENT; + if ("ENTRY_PRESENT".equals(name)) return com.codename1.security.SecureStorage.ENTRY_PRESENT; + if ("ENTRY_UNKNOWN".equals(name)) return com.codename1.security.SecureStorage.ENTRY_UNKNOWN; + throw unsupportedStaticField(com.codename1.security.SecureStorage.class, name); + } + + private static Object getStaticField7(String name) throws Exception { if ("SHA256_WITH_ECDSA".equals(name)) return com.codename1.security.Signature.SHA256_WITH_ECDSA; if ("SHA256_WITH_RSA".equals(name)) return com.codename1.security.Signature.SHA256_WITH_RSA; if ("SHA384_WITH_ECDSA".equals(name)) return com.codename1.security.Signature.SHA384_WITH_ECDSA; @@ -1223,6 +1308,14 @@ private static Object getStaticField6(String name) throws Exception { throw unsupportedStaticField(com.codename1.security.Signature.class, name); } + private static Object getStaticField8(String name) throws Exception { + if ("BLOCK".equals(name)) return com.codename1.security.TapjackingPolicy.BLOCK; + if ("OFF".equals(name)) return com.codename1.security.TapjackingPolicy.OFF; + if ("REPORT".equals(name)) return com.codename1.security.TapjackingPolicy.REPORT; + if ("STRICT".equals(name)) return com.codename1.security.TapjackingPolicy.STRICT; + throw unsupportedStaticField(com.codename1.security.TapjackingPolicy.class, name); + } + public static Object getField(Object target, String name) throws Exception { throw unsupportedField(target, name); } diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security_hardening.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security_hardening.java new file mode 100644 index 00000000000..b5c220de968 --- /dev/null +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security_hardening.java @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package bsh.cn1.gen; + +import bsh.cn1.CN1AccessException; + +public final class GeneratedAccess_com_codename1_security_hardening { + private GeneratedAccess_com_codename1_security_hardening() { + } + + public static Class findClass(String name) { + if (name == null) { + return null; + } + int dot = name.lastIndexOf('.'); + int dollar = name.lastIndexOf('$'); + int sep = dot > dollar ? dot : dollar; + if (sep < 0 || sep == name.length() - 1) { + return null; + } + return findClassBySimpleName(name.substring(sep + 1)); + } + + public static Class findClassBySimpleName(String simpleName) { + Class found0 = findClassChunk0(simpleName); + if (found0 != null) { + return found0; + } + return null; + } + + + private static Class findClassChunk0(String simpleName) { + if ("Hardening".equals(simpleName)) { + return com.codename1.security.hardening.Hardening.class; + } + return null; + } + public static Object construct(Class type, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + throw unsupportedConstruct(type, safeArgs); + } + + public static Object invokeStatic(Class type, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + if (type == com.codename1.security.hardening.Hardening.class) return invokeStatic0(name, safeArgs); + throw unsupportedStatic(type, name, safeArgs); + } + + private static Object invokeStatic0(String name, Object[] safeArgs) throws Exception { + if ("getLevel".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.security.hardening.Hardening.getLevel(); + } + } + if ("getMappingId".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.security.hardening.Hardening.getMappingId(); + } + } + if ("isHardened".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.security.hardening.Hardening.isHardened(); + } + } + throw unsupportedStatic(com.codename1.security.hardening.Hardening.class, name, safeArgs); + } + + public static Object invoke(Object target, String name, Object[] args) throws Exception { + Object[] safeArgs = safeArgs(args); + CN1AccessException unsupported = null; + if (unsupported != null) { + throw unsupported; + } + throw unsupportedInstance(target, name, safeArgs); + } + + public static Object getStaticField(Class type, String name) throws Exception { + throw unsupportedStaticField(type, name); + } + + public static Object getField(Object target, String name) throws Exception { + throw unsupportedField(target, name); + } + + public static void setStaticField(Class type, String name, Object value) throws Exception { + throw unsupportedStaticFieldWrite(type, name, value); + } + + public static void setField(Object target, String name, Object value) throws Exception { + throw unsupportedFieldWrite(target, name, value); + } + + private static Object[] safeArgs(Object[] args) { + return args == null ? new Object[0] : args; + } + + private static Object[] adaptArgs(Object[] args, Class[] paramTypes, boolean varArgs) { + if (args == null || args.length == 0) { + return args == null ? new Object[0] : args; + } + Object[] adapted = args.clone(); + if (!varArgs) { + for (int i = 0; i < Math.min(adapted.length, paramTypes.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + return adapted; + } + if (paramTypes.length == 0) { + return adapted; + } + int fixedCount = paramTypes.length - 1; + for (int i = 0; i < Math.min(fixedCount, adapted.length); i++) { + adapted[i] = adaptValue(adapted[i], paramTypes[i]); + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < adapted.length; i++) { + adapted[i] = adaptValue(adapted[i], componentType); + } + return adapted; + } + + private static boolean isSamInterface(Class type) { + if (type == com.codename1.util.OnComplete.class) { + return true; + } + if (type == com.codename1.util.SuccessCallback.class) { + return true; + } + if (type == com.codename1.util.FailureCallback.class) { + return true; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return true; + } + if (type == java.lang.Runnable.class) { + return true; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return true; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return true; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return true; + } + return false; + } + + private static Object adaptLambdaValue(final bsh.cn1.CN1LambdaSupport.LambdaValue lambda, Class type) { + if (type == com.codename1.util.OnComplete.class) { + return new com.codename1.util.OnComplete() { + public void completed(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.SuccessCallback.class) { + return new com.codename1.util.SuccessCallback() { + public void onSucess(java.lang.Object arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.util.FailureCallback.class) { + return new com.codename1.util.FailureCallback() { + public void onError(java.lang.Object arg0, java.lang.Throwable arg1, int arg2, java.lang.String arg3) { + try { + lambda.invoke(new Object[]{arg0, arg1, arg2, arg3}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.ActionListener.class) { + return new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == java.lang.Runnable.class) { + return new java.lang.Runnable() { + public void run() { + try { + lambda.invoke(new Object[0]); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.DataChangedListener.class) { + return new com.codename1.ui.events.DataChangedListener() { + public void dataChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.ui.events.SelectionListener.class) { + return new com.codename1.ui.events.SelectionListener() { + public void selectionChanged(int arg0, int arg1) { + try { + lambda.invoke(new Object[]{arg0, arg1}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + if (type == com.codename1.printing.PrintResultListener.class) { + return new com.codename1.printing.PrintResultListener() { + public void onResult(com.codename1.printing.PrintResult arg0) { + try { + lambda.invoke(new Object[]{arg0}); + } catch (bsh.EvalError ex) { + throw new RuntimeException(ex); + } + } + }; + } + return lambda; + } + + private static Object adaptValue(Object value, Class type) { + if (!(value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue)) { + return value; + } + // Direct fit when LambdaValue already implements the target SAM + // (Runnable, Function, Comparator, ...). + if (type.isInstance(value)) { + return value; + } + return adaptLambdaValue((bsh.cn1.CN1LambdaSupport.LambdaValue) value, type); + } + + private static int toIntValue(Object value) { + if (value instanceof Number) return ((Number) value).intValue(); + if (value instanceof Character) return (int) ((Character) value).charValue(); + throw new ClassCastException("Cannot coerce " + + (value == null ? "null" : value.getClass().getName()) + " to int"); + } + + private static boolean matches(Object[] args, Class[] paramTypes, boolean varArgs) { + if (!varArgs) { + if (args.length != paramTypes.length) { + return false; + } + for (int i = 0; i < paramTypes.length; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + return true; + } + if (paramTypes.length == 0) { + return true; + } + int fixedCount = paramTypes.length - 1; + if (args.length < fixedCount) { + return false; + } + for (int i = 0; i < fixedCount; i++) { + if (!matchesType(args[i], paramTypes[i])) { + return false; + } + } + Class componentType = paramTypes[paramTypes.length - 1].getComponentType(); + for (int i = fixedCount; i < args.length; i++) { + if (!matchesType(args[i], componentType)) { + return false; + } + } + return true; + } + + private static boolean matchesType(Object value, Class type) { + if (type == Object.class) { + return true; + } + if (value == null) { + return !type.isPrimitive(); + } + if (type.isArray()) { + return type.isInstance(value); + } + if ("boolean".equals(type.getName()) || type == Boolean.class) { + return value instanceof Boolean; + } + if ("char".equals(type.getName()) || type == Character.class) { + return value instanceof Character; + } + if ("byte".equals(type.getName()) || type == Byte.class || "short".equals(type.getName()) || type == Short.class + || "int".equals(type.getName()) || type == Integer.class || "long".equals(type.getName()) || type == Long.class + || "float".equals(type.getName()) || type == Float.class || "double".equals(type.getName()) || type == Double.class) { + // Java widens char to int implicitly, so accept Character + // for any int-or-larger numeric slot. + return value instanceof Number || value instanceof Character; + } + if (value instanceof bsh.cn1.CN1LambdaSupport.LambdaValue) { + // LambdaValue implements common SAMs directly (Runnable, + // Function, Predicate, Comparator, ...). Also accept any + // CN1 SAM the listener-bridge knows how to wrap. + return type.isInstance(value) || isSamInterface(type); + } + return type.isInstance(value); + } + + private static CN1AccessException unsupportedConstruct(Class type, Object[] args) { + return new CN1AccessException("Generated constructor dispatch not implemented for " + type.getName() + describeArgs(args)); + } + + private static CN1AccessException unsupportedStatic(Class type, String name, Object[] args) { + return new CN1AccessException("Generated static dispatch not implemented for " + type.getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedInstance(Object target, String name, Object[] args) { + return new CN1AccessException("Generated instance dispatch not implemented for " + target.getClass().getName() + "." + name + describeArgs(args)); + } + + private static CN1AccessException unsupportedStaticField(Class type, String name) { + return new CN1AccessException("Generated static field access not implemented for " + type.getName() + "." + name); + } + + private static CN1AccessException unsupportedField(Object target, String name) { + return new CN1AccessException("Generated field access not implemented for " + target.getClass().getName() + "." + name); + } + + private static CN1AccessException unsupportedStaticFieldWrite(Class type, String name, Object value) { + return new CN1AccessException("Generated static field write not implemented for " + type.getName() + "." + name + " value=" + describeValue(value)); + } + + private static CN1AccessException unsupportedFieldWrite(Object target, String name, Object value) { + return new CN1AccessException("Generated field write not implemented for " + target.getClass().getName() + "." + name + " value=" + describeValue(value)); + } + + private static String describeArgs(Object[] args) { + if (args == null || args.length == 0) { + return "()"; + } + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(describeValue(args[i])); + } + sb.append(')'); + return sb.toString(); + } + + private static String describeValue(Object value) { + return value == null ? "null" : value.getClass().getName(); + } +} diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security_shield.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security_shield.java index b6dcf961c6d..744c2b3b7e0 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security_shield.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_security_shield.java @@ -756,6 +756,7 @@ private static Object getStaticField5(String name) throws Exception { if ("JAILBREAK".equals(name)) return com.codename1.security.shield.ShieldSignal.JAILBREAK; if ("REPACKAGED".equals(name)) return com.codename1.security.shield.ShieldSignal.REPACKAGED; if ("ROOT".equals(name)) return com.codename1.security.shield.ShieldSignal.ROOT; + if ("TAPJACK".equals(name)) return com.codename1.security.shield.ShieldSignal.TAPJACK; throw unsupportedStaticField(com.codename1.security.shield.ShieldSignal.class, name); } diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_surfaces.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_surfaces.java index 44fa1d8ba18..6f0261e1232 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_surfaces.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_surfaces.java @@ -323,6 +323,12 @@ private static Object invokeStatic3(String name, Object[] safeArgs) throws Excep return com.codename1.surfaces.SurfaceSerializer.serializeLiveActivity((com.codename1.surfaces.LiveActivityDescriptor) adaptedArgs[0], (java.util.Map) adaptedArgs[1], (java.util.Map) adaptedArgs[2]); } } + if ("serializeNodeToMap".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.surfaces.SurfaceNode.class, java.util.Map.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.surfaces.SurfaceNode.class, java.util.Map.class}, false); + return com.codename1.surfaces.SurfaceSerializer.serializeNodeToMap((com.codename1.surfaces.SurfaceNode) adaptedArgs[0], (java.util.Map) adaptedArgs[1]); + } + } if ("serializeState".equals(name)) { if (matches(safeArgs, new Class[]{java.util.Map.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.Map.class}, false); diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ui.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ui.java index fedfd2b97e8..4dab6087e1c 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ui.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ui.java @@ -13139,6 +13139,12 @@ private static Object invoke4(com.codename1.ui.CodeEditor typedTarget, String na typedTarget.addPointerReleasedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } + if ("addProtectedEditListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addProtectedEditListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } if ("addPullToRefresh".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Runnable.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Runnable.class}, false); @@ -14437,6 +14443,12 @@ private static Object invoke4(com.codename1.ui.CodeEditor typedTarget, String na typedTarget.removePointerReleasedListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } + if ("removeProtectedEditListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeProtectedEditListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } if ("removeReadyListener".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); @@ -14591,6 +14603,12 @@ private static Object invoke4(com.codename1.ui.CodeEditor typedTarget, String na typedTarget.setCursor(toIntValue(adaptedArgs[0])); return null; } } + if ("setCursorPosition".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + typedTarget.setCursorPosition(toIntValue(adaptedArgs[0])); return null; + } + } if ("setDiagnostics".equals(name)) { if (matches(safeArgs, new Class[]{java.util.List.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.util.List.class}, false); @@ -14871,6 +14889,12 @@ private static Object invoke4(com.codename1.ui.CodeEditor typedTarget, String na return typedTarget.setPropertyValue((java.lang.String) adaptedArgs[0], (java.lang.Object) adaptedArgs[1]); } } + if ("setProtectedRegionMarkers".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + typedTarget.setProtectedRegionMarkers((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); return null; + } + } if ("setPullToRefresh".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Runnable.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Runnable.class}, false); @@ -73790,6 +73814,12 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name typedTarget.addPostureListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } + if ("addTapjackingListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.addTapjackingListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } if ("addVirtualKeyboardListener".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); @@ -74030,6 +74060,24 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name return typedTarget.createThread((java.lang.Runnable) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); } } + if ("databaseIdentityForEngineFile".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.databaseIdentityForEngineFile((java.lang.String) adaptedArgs[0]); + } + } + if ("databaseManagedKeyIdentity".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.databaseManagedKeyIdentity((java.lang.String) adaptedArgs[0]); + } + } + if ("databaseRegistryIdentity".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.databaseRegistryIdentity((java.lang.String) adaptedArgs[0]); + } + } if ("delete".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); @@ -74336,6 +74384,11 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name return typedTarget.getHealth(); } } + if ("getHomeBridge".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getHomeBridge(); + } + } if ("getImageIO".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getImageIO(); @@ -74355,6 +74408,11 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name return typedTarget.getInitialWindowSizeHintPercent(); } } + if ("getIntentBridge".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getIntentBridge(); + } + } if ("getInvisibleAreaUnderVKB".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getInvisibleAreaUnderVKB(); @@ -74545,6 +74603,11 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name return typedTarget.getSurfaceBridge(); } } + if ("getTapjackingPolicy".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.getTapjackingPolicy(); + } + } if ("getUdid".equals(name)) { if (safeArgs.length == 0) { return typedTarget.getUdid(); @@ -74709,6 +74772,11 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name return typedTarget.isBidiAlgorithm(); } } + if ("isBlobQueryParameterSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isBlobQueryParameterSupported(); + } + } if ("isBoldTextEnabled".equals(name)) { if (safeArgs.length == 0) { return typedTarget.isBoldTextEnabled(); @@ -74760,6 +74828,22 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name return typedTarget.isDatabaseCustomPathSupported(); } } + if ("isDatabaseEncryptionSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isDatabaseEncryptionSupported(); + } + } + if ("isDatabaseFileEncrypted".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.isDatabaseFileEncrypted((java.lang.String) adaptedArgs[0]); + } + } + if ("isDatabaseManagedKeyHardwareBacked".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isDatabaseManagedKeyHardwareBacked(); + } + } if ("isDebuggableBuild".equals(name)) { if (safeArgs.length == 0) { return typedTarget.isDebuggableBuild(); @@ -74841,6 +74925,11 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name return typedTarget.isGrayscaleEnabled(); } } + if ("isHideOverlayWindowsSupported".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isHideOverlayWindowsSupported(); + } + } if ("isHighContrastEnabled".equals(name)) { if (safeArgs.length == 0) { return typedTarget.isHighContrastEnabled(); @@ -74988,11 +75077,21 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name return typedTarget.isReduceTransparencyEnabled(); } } + if ("isRelativeAttachmentNameResolvable".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isRelativeAttachmentNameResolvable(); + } + } if ("isRightMouseButtonDown".equals(name)) { if (safeArgs.length == 0) { return typedTarget.isRightMouseButtonDown(); } } + if ("isScreenObscured".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isScreenObscured(); + } + } if ("isScreenReaderEnabled".equals(name)) { if (safeArgs.length == 0) { return typedTarget.isScreenReaderEnabled(); @@ -75133,6 +75232,12 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name typedTarget.onEditingComplete((com.codename1.ui.Component) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); return null; } } + if ("openDatabaseConnections".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.openDatabaseConnections((java.lang.String) adaptedArgs[0]); + } + } if ("openFileChooser".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class, java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class, java.lang.String.class}, false); @@ -75166,6 +75271,16 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); return typedTarget.openOrCreate((java.lang.String) adaptedArgs[0]); } + if (matches(safeArgs, new Class[]{java.lang.String.class, com.codename1.db.DatabaseConfig.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, com.codename1.db.DatabaseConfig.class}, false); + return typedTarget.openOrCreate((java.lang.String) adaptedArgs[0], (com.codename1.db.DatabaseConfig) adaptedArgs[1]); + } + } + if ("openOrCreateForRekey".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.openOrCreateForRekey((java.lang.String) adaptedArgs[0]); + } } if ("platformUsesInputMode".equals(name)) { if (safeArgs.length == 0) { @@ -75289,6 +75404,12 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name typedTarget.removePostureListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; } } + if ("removeTapjackingListener".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); + typedTarget.removeTapjackingListener((com.codename1.ui.events.ActionListener) adaptedArgs[0]); return null; + } + } if ("removeVirtualKeyboardListener".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.events.ActionListener.class}, false); @@ -75461,6 +75582,12 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name typedTarget.setFramerate(toIntValue(adaptedArgs[0])); return null; } } + if ("setHideOverlayWindows".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); + typedTarget.setHideOverlayWindows(((Boolean) adaptedArgs[0]).booleanValue()); return null; + } + } if ("setInitialWindowSizeHintPercent".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.ui.geom.Dimension.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.ui.geom.Dimension.class}, false); @@ -75551,6 +75678,12 @@ private static Object invoke37(com.codename1.ui.Display typedTarget, String name typedTarget.setShowVirtualKeyboard(((Boolean) adaptedArgs[0]).booleanValue()); return null; } } + if ("setTapjackingProtection".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.security.TapjackingPolicy.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.security.TapjackingPolicy.class}, false); + typedTarget.setTapjackingProtection((com.codename1.security.TapjackingPolicy) adaptedArgs[0]); return null; + } + } if ("setThirdSoftButton".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Boolean.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Boolean.class}, false); @@ -90559,12 +90692,27 @@ private static Object invoke66(com.codename1.ui.EditField typedTarget, String na return typedTarget.containsOrOwns(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); } } + if ("copySelection".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.copySelection(); return null; + } + } if ("createStyleAnimation".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false); return typedTarget.createStyleAnimation((java.lang.String) adaptedArgs[0], toIntValue(adaptedArgs[1])); } } + if ("cutSelection".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.cutSelection(); return null; + } + } + if ("deleteBackward".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.deleteBackward(); return null; + } + } if ("deleteSurroundingText".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); @@ -91452,6 +91600,11 @@ private static Object invoke66(com.codename1.ui.EditField typedTarget, String na typedTarget.paintShadows((com.codename1.ui.Graphics) adaptedArgs[0], toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2])); return null; } } + if ("pasteClipboard".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.pasteClipboard(); return null; + } + } if ("performRedo".equals(name)) { if (safeArgs.length == 0) { typedTarget.performRedo(); return null; diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ui_editor.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ui_editor.java index d7e7785051d..888fec5b64d 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ui_editor.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_ui_editor.java @@ -675,12 +675,27 @@ private static Object invoke1(com.codename1.ui.editor.CodeView typedTarget, Stri return typedTarget.containsOrOwns(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); } } + if ("copySelection".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.copySelection(); return null; + } + } if ("createStyleAnimation".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false); return typedTarget.createStyleAnimation((java.lang.String) adaptedArgs[0], toIntValue(adaptedArgs[1])); } } + if ("cutSelection".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.cutSelection(); return null; + } + } + if ("deleteBackward".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.deleteBackward(); return null; + } + } if ("deleteSurroundingText".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); @@ -1532,6 +1547,11 @@ private static Object invoke1(com.codename1.ui.editor.CodeView typedTarget, Stri typedTarget.paintShadows((com.codename1.ui.Graphics) adaptedArgs[0], toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2])); return null; } } + if ("pasteClipboard".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.pasteClipboard(); return null; + } + } if ("performRedo".equals(name)) { if (safeArgs.length == 0) { typedTarget.performRedo(); return null; @@ -2082,6 +2102,12 @@ private static Object invoke1(com.codename1.ui.editor.CodeView typedTarget, Stri return typedTarget.setPropertyValue((java.lang.String) adaptedArgs[0], (java.lang.Object) adaptedArgs[1]); } } + if ("setProtectedRegionMarkers".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class}, false); + typedTarget.setProtectedRegionMarkers((java.lang.String) adaptedArgs[0], (java.lang.String) adaptedArgs[1]); return null; + } + } if ("setPullToRefresh".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Runnable.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Runnable.class}, false); @@ -2508,12 +2534,27 @@ private static Object invoke3(com.codename1.ui.editor.RichView typedTarget, Stri return typedTarget.containsOrOwns(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); } } + if ("copySelection".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.copySelection(); return null; + } + } if ("createStyleAnimation".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false); return typedTarget.createStyleAnimation((java.lang.String) adaptedArgs[0], toIntValue(adaptedArgs[1])); } } + if ("cutSelection".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.cutSelection(); return null; + } + } + if ("deleteBackward".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.deleteBackward(); return null; + } + } if ("deleteSurroundingText".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); @@ -3408,6 +3449,11 @@ private static Object invoke3(com.codename1.ui.editor.RichView typedTarget, Stri typedTarget.paintShadows((com.codename1.ui.Graphics) adaptedArgs[0], toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2])); return null; } } + if ("pasteClipboard".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.pasteClipboard(); return null; + } + } if ("performRedo".equals(name)) { if (safeArgs.length == 0) { typedTarget.performRedo(); return null; @@ -4478,12 +4524,27 @@ private static Object invoke5(com.codename1.ui.editor.EditorView typedTarget, St return typedTarget.containsOrOwns(toIntValue(adaptedArgs[0]), toIntValue(adaptedArgs[1])); } } + if ("copySelection".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.copySelection(); return null; + } + } if ("createStyleAnimation".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Integer.class}, false); return typedTarget.createStyleAnimation((java.lang.String) adaptedArgs[0], toIntValue(adaptedArgs[1])); } } + if ("cutSelection".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.cutSelection(); return null; + } + } + if ("deleteBackward".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.deleteBackward(); return null; + } + } if ("deleteSurroundingText".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class, java.lang.Integer.class}, false); @@ -5330,6 +5391,11 @@ private static Object invoke5(com.codename1.ui.editor.EditorView typedTarget, St typedTarget.paintShadows((com.codename1.ui.Graphics) adaptedArgs[0], toIntValue(adaptedArgs[1]), toIntValue(adaptedArgs[2])); return null; } } + if ("pasteClipboard".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.pasteClipboard(); return null; + } + } if ("performRedo".equals(name)) { if (safeArgs.length == 0) { typedTarget.performRedo(); return null; diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_util.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_util.java index acf718001d3..db52a07e0d8 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_util.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_util.java @@ -1528,6 +1528,16 @@ private static Object invoke9(com.codename1.util.EasyThread typedTarget, String typedTarget.addErrorListener((com.codename1.util.EasyThread.ErrorListener) adaptedArgs[0]); return null; } } + if ("awaitFinished".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.awaitFinished(); return null; + } + } + if ("isFinished".equals(name)) { + if (safeArgs.length == 0) { + return typedTarget.isFinished(); + } + } if ("isThisIt".equals(name)) { if (safeArgs.length == 0) { return typedTarget.isThisIt(); @@ -1538,6 +1548,11 @@ private static Object invoke9(com.codename1.util.EasyThread typedTarget, String typedTarget.kill(); return null; } } + if ("killWhenIdle".equals(name)) { + if (safeArgs.length == 0) { + typedTarget.killWhenIdle(); return null; + } + } if ("removeErrorListener".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.util.EasyThread.ErrorListener.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.util.EasyThread.ErrorListener.class}, false); diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_wearable.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_wearable.java index 4de75de0b11..15a09249c0c 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_wearable.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_com_codename1_wearable.java @@ -55,6 +55,9 @@ private static Class findClassChunk0(String simpleName) { if ("WearableConnection".equals(simpleName)) { return com.codename1.wearable.WearableConnection.class; } + if ("DroppedDeliveryHandler".equals(simpleName)) { + return com.codename1.wearable.WearableConnection.DroppedDeliveryHandler.class; + } if ("WearableDataListener".equals(simpleName)) { return com.codename1.wearable.WearableDataListener.class; } @@ -133,18 +136,38 @@ private static Object invokeStatic0(String name, Object[] safeArgs) throws Excep Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Runnable.class}, false); return com.codename1.wearable.WearableConnection.deliverDataChangedTracked((java.lang.String) adaptedArgs[0], (byte[]) adaptedArgs[1], (java.lang.Runnable) adaptedArgs[2]); } + if (matches(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Runnable.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Runnable.class, java.lang.Runnable.class}, false); + return com.codename1.wearable.WearableConnection.deliverDataChangedTracked((java.lang.String) adaptedArgs[0], (byte[]) adaptedArgs[1], (java.lang.Runnable) adaptedArgs[2], (java.lang.Runnable) adaptedArgs[3]); + } } if ("deliverDataRemoved".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); com.codename1.wearable.WearableConnection.deliverDataRemoved((java.lang.String) adaptedArgs[0]); return null; } + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Runnable.class}, false); + com.codename1.wearable.WearableConnection.deliverDataRemoved((java.lang.String) adaptedArgs[0], (java.lang.Runnable) adaptedArgs[1]); return null; + } + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Runnable.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Runnable.class, java.lang.Runnable.class}, false); + com.codename1.wearable.WearableConnection.deliverDataRemoved((java.lang.String) adaptedArgs[0], (java.lang.Runnable) adaptedArgs[1], (java.lang.Runnable) adaptedArgs[2]); return null; + } } if ("deliverMessage".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Integer.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Integer.class}, false); com.codename1.wearable.WearableConnection.deliverMessage((java.lang.String) adaptedArgs[0], (byte[]) adaptedArgs[1], toIntValue(adaptedArgs[2])); return null; } + if (matches(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Integer.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Integer.class, java.lang.Runnable.class}, false); + com.codename1.wearable.WearableConnection.deliverMessage((java.lang.String) adaptedArgs[0], (byte[]) adaptedArgs[1], toIntValue(adaptedArgs[2]), (java.lang.Runnable) adaptedArgs[3]); return null; + } + if (matches(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Integer.class, java.lang.Runnable.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, byte[].class, java.lang.Integer.class, java.lang.Runnable.class, java.lang.Runnable.class}, false); + com.codename1.wearable.WearableConnection.deliverMessage((java.lang.String) adaptedArgs[0], (byte[]) adaptedArgs[1], toIntValue(adaptedArgs[2]), (java.lang.Runnable) adaptedArgs[3], (java.lang.Runnable) adaptedArgs[4]); return null; + } } if ("deliverReply".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Integer.class, byte[].class, java.lang.String.class}, false)) { @@ -168,6 +191,22 @@ private static Object invokeStatic0(String name, Object[] safeArgs) throws Excep return com.codename1.wearable.WearableConnection.getDataPaths(); } } + if ("hasDataListener".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.wearable.WearableConnection.hasDataListener(); + } + } + if ("hasMessageListener".equals(name)) { + if (safeArgs.length == 0) { + return com.codename1.wearable.WearableConnection.hasMessageListener(); + } + } + if ("hasPendingReply".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.Integer.class}, false); + return com.codename1.wearable.WearableConnection.hasPendingReply(toIntValue(adaptedArgs[0])); + } + } if ("isCompanionAppInstalled".equals(name)) { if (safeArgs.length == 0) { return com.codename1.wearable.WearableConnection.isCompanionAppInstalled(); @@ -223,6 +262,23 @@ private static Object invokeStatic0(String name, Object[] safeArgs) throws Excep com.codename1.wearable.WearableConnection.removeStateListener((com.codename1.wearable.WearableStateListener) adaptedArgs[0]); return null; } } + if ("requestReplayAfterDrain".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Runnable.class}, false); + com.codename1.wearable.WearableConnection.requestReplayAfterDrain((java.lang.String) adaptedArgs[0], (java.lang.Runnable) adaptedArgs[1]); return null; + } + } + if ("resetForReload".equals(name)) { + if (safeArgs.length == 0) { + com.codename1.wearable.WearableConnection.resetForReload(); return null; + } + } + if ("runWhenListenerRegisters".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.Runnable.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.Runnable.class}, false); + com.codename1.wearable.WearableConnection.runWhenListenerRegisters((java.lang.String) adaptedArgs[0], (java.lang.Runnable) adaptedArgs[1]); return null; + } + } if ("sendMessage".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.wearable.WearableMessage.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.wearable.WearableMessage.class}, false); @@ -233,6 +289,12 @@ private static Object invokeStatic0(String name, Object[] safeArgs) throws Excep com.codename1.wearable.WearableConnection.sendMessage((com.codename1.wearable.WearableMessage) adaptedArgs[0], (com.codename1.wearable.WearableReplyHandler) adaptedArgs[1]); return null; } } + if ("setDroppedDeliveryHandler".equals(name)) { + if (matches(safeArgs, new Class[]{com.codename1.wearable.WearableConnection.DroppedDeliveryHandler.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.wearable.WearableConnection.DroppedDeliveryHandler.class}, false); + com.codename1.wearable.WearableConnection.setDroppedDeliveryHandler((com.codename1.wearable.WearableConnection.DroppedDeliveryHandler) adaptedArgs[0]); return null; + } + } if ("transferFile".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, byte[].class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class, java.lang.String.class, byte[].class}, false); @@ -269,30 +331,37 @@ public static Object invoke(Object target, String name, Object[] args) throws Ex unsupported = ex; } } + if (target instanceof com.codename1.wearable.WearableConnection.DroppedDeliveryHandler) { + try { + return invoke2((com.codename1.wearable.WearableConnection.DroppedDeliveryHandler) target, name, safeArgs); + } catch (CN1AccessException ex) { + unsupported = ex; + } + } if (target instanceof com.codename1.wearable.WearableDataListener) { try { - return invoke2((com.codename1.wearable.WearableDataListener) target, name, safeArgs); + return invoke3((com.codename1.wearable.WearableDataListener) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.wearable.WearableMessageListener) { try { - return invoke3((com.codename1.wearable.WearableMessageListener) target, name, safeArgs); + return invoke4((com.codename1.wearable.WearableMessageListener) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.wearable.WearableReplyHandler) { try { - return invoke4((com.codename1.wearable.WearableReplyHandler) target, name, safeArgs); + return invoke5((com.codename1.wearable.WearableReplyHandler) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } } if (target instanceof com.codename1.wearable.WearableStateListener) { try { - return invoke5((com.codename1.wearable.WearableStateListener) target, name, safeArgs); + return invoke6((com.codename1.wearable.WearableStateListener) target, name, safeArgs); } catch (CN1AccessException ex) { unsupported = ex; } @@ -419,7 +488,17 @@ private static Object invoke1(com.codename1.wearable.WearableNode typedTarget, S throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke2(com.codename1.wearable.WearableDataListener typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke2(com.codename1.wearable.WearableConnection.DroppedDeliveryHandler typedTarget, String name, Object[] safeArgs) throws Exception { + if ("deliveryDropped".equals(name)) { + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + typedTarget.deliveryDropped((java.lang.String) adaptedArgs[0]); return null; + } + } + throw unsupportedInstance(typedTarget, name, safeArgs); + } + + private static Object invoke3(com.codename1.wearable.WearableDataListener typedTarget, String name, Object[] safeArgs) throws Exception { if ("dataChanged".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.wearable.WearableMessage.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.wearable.WearableMessage.class}, false); @@ -435,7 +514,7 @@ private static Object invoke2(com.codename1.wearable.WearableDataListener typedT throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke3(com.codename1.wearable.WearableMessageListener typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke4(com.codename1.wearable.WearableMessageListener typedTarget, String name, Object[] safeArgs) throws Exception { if ("messageReceived".equals(name)) { if (matches(safeArgs, new Class[]{com.codename1.wearable.WearableMessage.class, java.lang.Boolean.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{com.codename1.wearable.WearableMessage.class, java.lang.Boolean.class}, false); @@ -445,7 +524,7 @@ private static Object invoke3(com.codename1.wearable.WearableMessageListener typ throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke4(com.codename1.wearable.WearableReplyHandler typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke5(com.codename1.wearable.WearableReplyHandler typedTarget, String name, Object[] safeArgs) throws Exception { if ("replyFailed".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); @@ -461,7 +540,7 @@ private static Object invoke4(com.codename1.wearable.WearableReplyHandler typedT throw unsupportedInstance(typedTarget, name, safeArgs); } - private static Object invoke5(com.codename1.wearable.WearableStateListener typedTarget, String name, Object[] safeArgs) throws Exception { + private static Object invoke6(com.codename1.wearable.WearableStateListener typedTarget, String name, Object[] safeArgs) throws Exception { if ("connectionStateChanged".equals(name)) { if (safeArgs.length == 0) { typedTarget.connectionStateChanged(); return null; diff --git a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_java_io.java b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_java_io.java index 57a729dedf9..6c3406ee5bc 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_java_io.java +++ b/scripts/cn1playground/common/src/main/java/bsh/cn1/gen/GeneratedAccess_java_io.java @@ -479,6 +479,10 @@ private static Object invoke1(java.io.ByteArrayOutputStream typedTarget, String if (safeArgs.length == 0) { return typedTarget.toString(); } + if (matches(safeArgs, new Class[]{java.lang.String.class}, false)) { + Object[] adaptedArgs = adaptArgs(safeArgs, new Class[]{java.lang.String.class}, false); + return typedTarget.toString((java.lang.String) adaptedArgs[0]); + } } if ("write".equals(name)) { if (matches(safeArgs, new Class[]{java.lang.Integer.class}, false)) { diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 9b2c3666391..eafe3a95b44 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -96,6 +96,9 @@ public enum Section { BASIC, BUILD_HINTS, EXTENSIONS, ADVANCED } private ProjectBinding binding; private SettingsProperties settings; private BuildHintCatalog buildHints = BuildHintCatalog.load(); + /// hint name -> the annotation attribute that declares it, e.g. "@Ios(pods)". + /// Empty when the project has not been built, or declares none. + private java.util.Map annotationOwnedHints = new java.util.HashMap<>(); private Section section = Section.BASIC; private Form form; private Container page; @@ -227,6 +230,7 @@ private void loadProject() { Log.e(ex); } buildHints = BuildHintCatalog.load(); + annotationOwnedHints = loadAnnotationOwnedHints(); } } @@ -655,6 +659,7 @@ private void animatePage() { private Component hintRow(BuildHintMetadata meta) { Container row = new Container(BoxLayout.y()); row.setUIID(uiid("SettingsRow")); + String ownedBy = annotationOwnedHints.get(meta.name()); boolean active = hasBuildHint(meta.name()); String value = active ? settings.getBuildHint(meta.name()) : ""; BuildHintType effectiveType = effectiveHintType(meta, value); @@ -667,8 +672,22 @@ private Component hintRow(BuildHintMetadata meta) { if (active) { metaLine.add(new Label("Active", uiid("SettingsActiveBadge"))); } + if (ownedBy != null) { + metaLine.add(new Label(ownedBy, uiid("SettingsRowMeta"))); + } text.add(name).add(metaLine); - if (active) { + if (ownedBy != null) { + // Set by an annotation on the main class. Editing it here would write a + // second declaration and the next build would refuse the project, so the + // value is shown and the controls are withheld. + TextArea owned = new TextArea("Set by " + ownedBy + " on the main class. " + + "Change it there -- declaring it here as well fails the build."); + owned.setUIID(uiid("SettingsRowText")); + owned.setEditable(false); + owned.setFocusable(false); + text.add(owned); + row.add(text); + } else if (active) { text.add(activeHintEditor(meta, value, effectiveType)); } else { Container controls = new Container(new FlowLayout(Component.LEFT, Component.CENTER)); @@ -686,7 +705,7 @@ private Component hintRow(BuildHintMetadata meta) { header.add(BorderLayout.EAST, controls); row.add(header); } - if (active) { + if (active && ownedBy == null) { row.add(text); } TextArea details = new TextArea(meta.description()); @@ -2049,4 +2068,53 @@ public boolean isScrollableY() { return false; } } + + /// Reads the hints the main class's annotations declare. + /// + /// The annotation processor writes this file into `target/classes` on every + /// build and deletes it when the last annotation goes away, so it is the + /// authoritative statement of what the annotations currently declare -- and + /// it carries `cn1.buildHints.origin.`, which names the attribute. + /// + /// This matters because a hint declared by an annotation must not also be + /// written into `codenameone_settings.properties`: the next build fails with + /// the duplicate-declaration error. Without this the Add button would create + /// exactly that, silently, for any hint the generated project ships as an + /// annotation. + /// + /// An unbuilt project has no file and no annotation-owned hints, which is the + /// same conservative answer this tool gave before. + private java.util.Map loadAnnotationOwnedHints() { + java.util.Map out = new java.util.HashMap<>(); + if (binding == null || binding.projectDir() == null) { + return out; + } + String path = binding.projectDir() + "/target/classes/META-INF/codenameone/build-hints.properties"; + InputStream in = null; + try { + String url = ProjectIO.fsUrl(path); + FileSystemStorage fs = FileSystemStorage.getInstance(); + if (!fs.exists(url)) { + return out; + } + in = fs.openInputStream(url); + String text = Util.readToString(in, "ISO-8859-1"); + String originPrefix = "cn1.buildHints.origin."; + for (String line : com.codename1.util.StringUtil.tokenize(text, "\n")) { + String t = line.trim(); + if (!t.startsWith(originPrefix)) { + continue; + } + int eq = t.indexOf('='); + if (eq > originPrefix.length()) { + out.put(t.substring(originPrefix.length(), eq).trim(), t.substring(eq + 1).trim()); + } + } + } catch (Exception ex) { + Log.e(ex); + } finally { + Util.cleanup(in); + } + return out; + } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 38673e3f57e..55741ec3ba8 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -104,6 +104,27 @@ public void dynamicFamiliesAreNotOffered() { } } + /** + * The generated project ships hints like ios.themeMode as annotations, not + * properties lines. The catalog has to say which hints have an annotation + * form so the Build Hints UI can refuse to write a second declaration -- + * doing so would fail the very next build with a duplicate-hint error. + */ + @Test + public void everyAnnotatedHintNamesItsAttribute() { + BuildHintCatalog catalog = BuildHintCatalog.load(); + int annotated = 0; + for (BuildHintMetadata h : catalog.all()) { + if (h.annotation() == null) { + continue; + } + annotated++; + assertTrue(h.annotation().startsWith("@"), h.name() + " -> " + h.annotation()); + assertTrue(h.annotation().endsWith(")"), h.name() + " -> " + h.annotation()); + } + assertTrue(annotated > 50, "expected the curated set, got " + annotated); + } + @Test public void searchStillMatchesOnNameAndDescription() { BuildHintCatalog catalog = BuildHintCatalog.load(); From 53da7c3402d4eefd94e76df56990e03677666dc7 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 04:43:13 +0300 Subject: [PATCH 008/115] Defer the generated-project templates to a follow-up Every project the archetype and the initializr produce is pinned to a released Codename One version -- the initializr hardcodes 7.0.267 in GeneratorModel.CN1_PLUGIN_VERSION -- and no released core carries com.codename1.annotations.buildhints. So a generated project would import annotations that do not resolve and fail to compile before the user has written a line, and the settings those templates stopped declaring would simply be gone. The templates are reverted to exactly their previous state: the archetype's __mainName__.java and codenameone_settings.properties, and the initializr's common.zip and four source archives. They can move to annotations in a follow-up once a release containing the package is out. The generated build hint table is dropped from the agent skill reference for the same reason -- it documented a form those projects cannot use yet -- so the generator no longer rewrites markdown at all. What stays from that area is unrelated to annotations: the skill reference described build hints that no builder reads, so a reader copying them got a green build and no effect. android.xPermissions is spelled android.xpermissions, android.minSdkVersion is android.min_sdk_version, and android.sdkVersion, android.googlePlayVersion, build.compile, build.timeout, javascript.html5, javascript.bundleResources and ios.orientation do not exist at all. Those corrections are right for the published version too, and the catalog gate now holds our own documentation to them. Co-Authored-By: Claude Opus 5 (1M context) --- .../build/shared/BuildHintCodeGenerator.java | 83 +------- .../common/codenameone_settings.properties | 27 ++- .../common/src/main/java/__mainName__.java | 13 -- scripts/copyright-header-exclusions.txt | 1 - scripts/gen-build-hint-annotations.sh | 4 +- .../src/main/resources/barebones-src.zip | Bin 1435 -> 1327 bytes .../common/src/main/resources/common.zip | Bin 251603 -> 251573 bytes .../common/src/main/resources/grub-src.zip | Bin 275075 -> 279805 bytes .../common/src/main/resources/kotlin-src.zip | Bin 1563 -> 1507 bytes .../common/src/main/resources/skill/SKILL.md | 2 +- .../skill/references/android-to-cn1.md | 2 +- .../skill/references/build-and-run.md | 24 +-- .../resources/skill/references/build-hints.md | 185 ++++-------------- .../skill/references/native-interfaces.md | 31 ++- .../common/src/main/resources/tweet-src.zip | Bin 356080 -> 357703 bytes 15 files changed, 73 insertions(+), 299 deletions(-) diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java index 60864ab4ef8..a6f3017d02a 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java @@ -96,13 +96,14 @@ private BuildHintCodeGenerator() { /** * @param args annotation source root, the catalog source root for the - * generated binding table, and optionally one or more markdown - * files carrying a generated build hint table + * generated binding table, then any number of further outputs: + * a directory receives the simulator schema, an {@code .adoc} + * file receives the developer guide's table */ public static void main(String[] args) throws IOException { if (args.length < 2) { System.err.println("usage: BuildHintCodeGenerator " - + " [markdown-file...]"); + + " [output...]"); System.exit(2); } File annRoot = new File(args[0], PKG_PATH); @@ -167,9 +168,7 @@ public int compare(BuildHints.Hint a, BuildHints.Hint b) { write(new File(catalogRoot, "BuildHintAnnotationBinding.java"), bindingSource(byGroup, enums)); for (int i = 2; i < args.length; i++) { File target = new File(args[i]); - if (target.getName().endsWith(".md")) { - rewriteMarkdown(target, byGroup); - } else if (target.getName().endsWith(".adoc") || target.getName().endsWith(".asciidoc")) { + if (target.getName().endsWith(".adoc") || target.getName().endsWith(".asciidoc")) { write(target, asciidocTable()); } else { write(new File(target, "com/codename1/impl/javase/BuildHintCatalogDefaults.java"), @@ -647,78 +646,6 @@ private static String quote(String s) { return "\"" + esc(s) + "\""; } - private static final String MD_BEGIN = ""; - private static final String MD_END = ""; - - /** - * Rewrites the generated table inside a markdown file, between the marker - * comments, leaving the hand-written prose around it alone. - * - *

This exists because the file it targets is shipped to coding agents and - * was hand-maintained: it told them to set {@code android.xPermissions}, - * {@code android.minSdkVersion} and {@code android.sdkVersion}, none of which - * any builder reads. Generating the table from the catalog is the only way it - * stays true.

- */ - private static void rewriteMarkdown(File file, Map> byGroup) - throws IOException { - if (!file.isFile()) { - throw new IOException("No such markdown file: " + file); - } - StringBuilder existing = new StringBuilder(); - java.io.BufferedReader r = new java.io.BufferedReader( - new java.io.InputStreamReader(new java.io.FileInputStream(file), "UTF-8")); - try { - String line; - while ((line = r.readLine()) != null) { - existing.append(line).append('\n'); - } - } finally { - r.close(); - } - String text = existing.toString(); - int begin = text.indexOf(MD_BEGIN); - int end = text.indexOf(MD_END); - if (begin < 0 || end < 0 || end < begin) { - throw new IOException(file + " has no generated-table markers"); - } - StringBuilder table = new StringBuilder(); - table.append(MD_BEGIN).append('\n'); - table.append("\n\n"); - for (Map.Entry> e : byGroup.entrySet()) { - table.append("### `@").append(e.getKey().annotationSimpleName()).append("`\n\n"); - table.append("| Attribute | Type | Build hint |\n"); - table.append("| --- | --- | --- |\n"); - for (BuildHints.Hint h : e.getValue()) { - table.append("| `").append(h.attr()).append("` | `") - .append(markdownType(h)).append("` | `codename1.arg.") - .append(h.name()).append("` |\n"); - } - table.append('\n'); - } - table.append(MD_END); - String out = text.substring(0, begin) + table + text.substring(end + MD_END.length()); - java.io.Writer w = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); - try { - w.write(out); - } finally { - w.close(); - } - } - - private static String markdownType(BuildHints.Hint h) { - if (h.type() == HintType.ENUM) { - StringBuilder sb = new StringBuilder(h.enumName()).append('.'); - List v = h.values(); - for (int i = 0; i < v.size(); i++) { - sb.append(i == 0 ? "" : "\\|").append(enumConstant(v.get(i))); - } - return sb.toString(); - } - return javaType(h); - } /** * Folds text to ASCII. diff --git a/maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties b/maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties index 33d9b8ef65d..8f0bf0d6d5e 100644 --- a/maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties +++ b/maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties @@ -4,14 +4,17 @@ codename1.android.keystore= codename1.android.keystoreAlias= codename1.android.keystorePassword= -# Build hints are now declared as annotations on the main class, where the -# compiler checks them -- see the @Ios / @Android / @Desktop / @Build -# annotations on ${mainName}. Setting the same hint here as well is a build -# error, so move a hint rather than copying it. -# -# java.version stays here on purpose: it selects the toolchain that compiles -# the very class the annotations live on, and the project generator resolves it -# before any code is compiled. +codename1.arg.ios.newStorageLocation=true +# Modern native themes (iOS liquid-glass + Material 3) - opt-in. +codename1.arg.nativeTheme=modern +codename1.arg.ios.themeMode=modern +codename1.arg.and.themeMode=modern +# Desktop integration (only takes effect when the app runs on the desktop). titleBar mode is +# one of: native (OS title bar + native menu bar), custom (undecorated, CN1-drawn title bar) or +# toolbar (legacy in-app CN1 Toolbar). interactiveScrollbars enables grab-able, click-to-page +# desktop scrollbars. These are honored by the generated desktop Stub. +codename1.arg.desktop.titleBar=native +codename1.arg.desktop.interactiveScrollbars=true codename1.arg.java.version=${javaVersion} codename1.displayName=${mainName} codename1.icon=icon.png @@ -32,11 +35,6 @@ codename1.ios.release.provision= # See the "On-Device Debugging (iOS)" chapter of the developer guide # for the full setup (the IntelliJ Run/Debug configs that come with # this project's .idea/ directory are wired against these hints). -# -# These have a checked form too. On the main class: -# @OnDeviceDebug(ios = true, iosProxyHost = "127.0.0.1", iosProxyPort = 55333) -# and iosWaitForAttach = true to block at boot until the debugger attaches. -# Use one form or the other -- declaring a hint in both places fails the build. #codename1.arg.ios.onDeviceDebug=true #codename1.arg.ios.onDeviceDebug.proxyHost=127.0.0.1 #codename1.arg.ios.onDeviceDebug.proxyPort=55333 @@ -49,8 +47,7 @@ codename1.ios.release.provision= # bundled with this project, or with the cn1:android-on-device-debugging # Maven goal. See the "On-Device Debugging (Android)" chapter of the # developer guide for the wireless-debugging instructions and the full -# adb flow. The checked form is @OnDeviceDebug(android = true) on the -# main class; use one form or the other, not both. +# adb flow. #codename1.arg.android.onDeviceDebug=true codename1.j2me.nativeTheme=nbproject/nativej2me.res codename1.kotlin=false diff --git a/maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java b/maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java index 44d6a1a1b39..a415a02bd87 100644 --- a/maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java +++ b/maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java @@ -4,7 +4,6 @@ package ${package}; import static com.codename1.ui.CN.*; -import com.codename1.annotations.buildhints.*; import com.codename1.system.Lifecycle; import com.codename1.ui.*; import com.codename1.ui.layouts.*; @@ -15,19 +14,7 @@ /** * This file was generated by Codename One for the purpose * of building native mobile applications using Java. - * - *

The annotations below are build hints: settings the native build reads, - * written so the compiler checks them. A misspelled name is an unknown symbol - * and an unsupported value is an unknown enum constant, rather than a line in - * codenameone_settings.properties that is silently ignored. Hints that have no - * annotation yet, and open-ended ones such as android.permission.<NAME>, - * still go in that file; setting the same hint in both places is a build - * error.

*/ -@Ios(newStorageLocation = true, themeMode = IosThemeMode.MODERN) -@Android(themeMode = AndroidThemeMode.MODERN) -@Desktop(titleBar = DesktopTitleBar.NATIVE, interactiveScrollbars = true) -@Build(nativeTheme = NativeThemeMode.MODERN) public class ${mainName} extends Lifecycle { @Override public void runApp() { diff --git a/scripts/copyright-header-exclusions.txt b/scripts/copyright-header-exclusions.txt index 0ef1e55292f..77a600506a3 100644 --- a/scripts/copyright-header-exclusions.txt +++ b/scripts/copyright-header-exclusions.txt @@ -27,4 +27,3 @@ vm/ByteCodeTranslator/src/cn1_sqlite3.h | SQLite3 Multiple Ciphers public header vm/ByteCodeTranslator/src/cn1_sqlite3_amalgamation.h | SQLite3 Multiple Ciphers amalgamation, upstream MIT notice over public-domain SQLite Ports/JavaScriptPort/src/main/webapp/js/sqlite3mc.js | SQLite3 Multiple Ciphers WebAssembly loader, Emscripten generated, MIT over public-domain SQLite Ports/JavaScriptPort/src/main/webapp/js/sqlite3-opfs-async-proxy.js | SQLite3 Multiple Ciphers OPFS proxy worker, MIT over public-domain SQLite -maven/cn1app-archetype/src/main/resources/archetype-resources/common/src/main/java/__mainName__.java | Archetype template for the application class of a user's own project, not Codename One source; a GPL header here would be applied to the user's code diff --git a/scripts/gen-build-hint-annotations.sh b/scripts/gen-build-hint-annotations.sh index 0ba5d0e386f..fd63b45b109 100755 --- a/scripts/gen-build-hint-annotations.sh +++ b/scripts/gen-build-hint-annotations.sh @@ -32,17 +32,15 @@ check=0 echo "gen-build-hint-annotations: building the catalog" >&2 (cd "$REPO_ROOT/maven" && mvn -q -B -pl build-hint-catalog package -DskipTests) -SKILL_REF="$REPO_ROOT/scripts/initializr/common/src/main/resources/skill/references/build-hints.md" JAVASE_SRC="$REPO_ROOT/Ports/JavaSE/src" GUIDE_TABLE="$REPO_ROOT/docs/developer-guide/_generated-build-hints.adoc" java -cp "$CLASSES" com.codename1.build.shared.BuildHintCodeGenerator \ - "$ANN_ROOT" "$CATALOG_SRC" "$SKILL_REF" "$JAVASE_SRC" "$GUIDE_TABLE" + "$ANN_ROOT" "$CATALOG_SRC" "$JAVASE_SRC" "$GUIDE_TABLE" if [ "$check" -eq 1 ]; then targets=("CodenameOne/src/com/codename1/annotations/buildhints" "maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java" - "scripts/initializr/common/src/main/resources/skill/references/build-hints.md" "Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java" "docs/developer-guide/_generated-build-hints.adoc") if ! git -C "$REPO_ROOT" diff --quiet -- "${targets[@]}" \ diff --git a/scripts/initializr/common/src/main/resources/barebones-src.zip b/scripts/initializr/common/src/main/resources/barebones-src.zip index da205fa34873e76436d9de37f5f7b71a6bdf1184..6f000dc9a5194aabf4c97420dfbee7ffb6ec6adb 100644 GIT binary patch literal 1327 zcmWIWW@h1H00H5A{}?a>O0Y7>Fk~f`CF+NUa56BLZJC%^0mP*h+zgB?Ul|z~SVVvd z18{2RglSLC&n43cL1ZIRD-v@Ha#G1ON*tR}xs{0p1q2NQd6NSiI&8~Rj2;7hewLYm zK^3=wzLkyz1%8RSsd}K`gNI)7XN=IR4ff5yZNRhlcesF^rK4K%0Y7hPowHV3vM({j zHpsCbU6!0UN#lj`I>Rsf{rm2!9G&ng!Q)AF|Chxn6(8H*3vdPT#P2W?nwhoJ@Un{F zsR>^n*nD4Pp}?gTSll{4QXuE!iDQ?|FZ^DUJ=gu{v_*#&>8ivvxK8xuGE}YfKDfJE zz)^lepMo z-P^Ig@}9}rz$qrj_MWZd2??okhH; z?@{M9_3}KtFZ>06;NsIfM(KN^4C1EUlzeYf*;Tfp^5V^nA|Hc}7N&5_PqTdzs~%X` zUgxZ`{Z88Az8zhQH#ePT)jV3db7B0&9?@4Hu3ekpp`LD*QNRAt5fpM85-x9`v1 z9lK*~d>));(F-p#HN7cN{)Ea!tx|O!*S(ENDu%sD8rIQIUofNO;8ZP+zc@sJ%8e| Vij@s$5(6s`?gSc^0L=6Z3;^vO0WXyti-ZJ{Q#UwIAKbX^K)T9KGrkdul>xi~iE zxs{0p1z=4gKxqyJfh4imYYU|^f|(f@PO>pDsN&Y-Tj^L(;Fp-2st58;ZHRCFZ3}_D zzr%TIG@cw-y79ogS64b7@9@~RjalW`w}=auZYVhwa@0i#iv4|WJ>|)fo4Jdc6^ic7 zHm{NCo%(aKjlz=<&YEdcuWGDvi?WvG2}wQ4`oWQH>h_K~Tqc_i*}H@!Z4?y0Ec_xj zM9R6-^;mao$S#ekod05XDnyY)`JNWWv| z$A)=}PG8(?Ik~%=G3LOw5V>z&%2zHOX5_V!+|UrqJYC4E{rCFAMl7%Q`>2)1?pIN5 z{uArlw%K(*^LI9V^&+A9-?o~_?5+*)ylk>_`n%ZiNYAwQRGIc2?C@fXVv zZ@HxjVM=kgZcYoHdzbx9Krrv6#OXq}R{vy@b57WnzqGoS??HfA-JDHV{aPE?Y_@8w zyI;i1_etpU@@bKrGn|+1*}lT`_64~f*-31RDvd&ncdjg35^R;(rT1X5?>ggu8l`{q z8GYA8oGy@`Wx)1r`;jLC&MFzH68S;9jI-7oL@Vb-Q+Ku3xf;_xr@VXV%_T+5Tas^`;LWjBFmvXZ%oHTAwmIr19-4b@`tT z4;CdBnZ0U!`O|G((*4M}xjErCKZG6RFWM#1_i)y{@V6y`zGhOs@fp_*S-$Oj61V){ z?$Z}^E!?}lWS*;7Dzr&slkvUE52fY{pH=QUJ@pai$<3$EV& zHs)$Ykr|A2w9NgA`fl;b&6ImfSM|NP_x`@^yub6F-|so+Jm-J@&-47x5Lb!MRch2F z66CCp%I$mgjCw{%9zo7(BM1Rmfka0|S|mqC`0QMYfGzK154&MKA(@2W@W0{^1dm|t zBwZ{&^iFkl!mw&M_?lDJOTY0RN@0hb zwOBiwoiyPk-RU;AKki3WF*mn2@mPee`^)chCv9g>tzcfHZ=rt+wZMo}lRJ2z5W_W|OO$#AW>|<*${!`P;L* zE?p~sZ!}YD6@BTN=D5O&^MT3*&TN~tu0^emXDE($FV(g1*q=|niHPZwR920ideqjl zmzg#tzbPyKlM?=<bDVVf|@Fj;g#d2VB-nJHt*^?v=^8WR`lT5u*VMe;?= z$x-nn%i+=E;ydgRF22|GpMKjq4WC4gt8fgAb8K0&OIB?a*Q`9)Y>>I?)ii!^WmRr^ z{WwcZ%)frf;HsDCj>xDX#LKK>MzUPt<1HHgm+%zzz_j7J=UT$6jdhhr25F9#gO+=D zSWSP4CaX5s7+RaBXiL*;1Kd6K|HI+%LuUMYD?jfmHx2UN#Tsk>eV1C|FSV= zf7CfE{P^;UjPObOYiNH-eQIn>Olqo!1yL;eoSDG~@?aMwi>nFBTGtB+NTYi5wHl1hO8#+qd z&9wdTYrM11Ykr+pc45jv2X1hT^&RH;r*ws|w95>`I#(0#M)>ONk$^N zGun52RG%1n%6{eMTJh_lTV-*Z#u($vo%NUQ3qFk>ikz6-8$$JeB`(C zWk=_V$!6td8yq^KthPg8HfO2EQx9oG5sT^cZBp6MXUcxhnmuN1M;+=@;*=F+@}!Uv z+RVVcV#RA;p2#K+WNlQUl*AVo^Q+t1%B81Ga(umaoLDU=$nTGzoB8dQ{taM5>S$IrhMpU=n-6wn4crx`IW#fBT)noDnKxS9jd4f*<7H*t|pNMDRn?o zvmP}j3M{I@4pboKf-5K`y<*0HUiJQLm{uYYVT^=i8WeT`Q_{RWHi2RBeNa41>jo>J zT{8|3b%lV4p2R^@Ay|V};h?_|Y#?mK*vXzoc4jQL6t-rWeqV2@9L%e`I9Jeme=ai@-W! z34(^IG{E z&NVnBEGlju%34%~2YY&f6~TtLXzUjPEA#*lZWaSa^db+Q5Mu|5d9X{2Eq1Jjb7HU_ z_2R>IePA7$$cKmefR4d`JV@AGjsIX_k-XGA081`|Rj{`Yn4-H1;cOq+fHDi=+J3MJ z2}Xuu5tQ`FL8|cuy!d@9mo|?Ps)*u0qi%#`IRqZ=1PtSk3a;D|QXX3Luaz}cQlaj{$ z<*!DzHq767*1h@F4ISgN)7_cL+PBwjHQ^>{+;9nCW*=6p$XZPMy_8as^TEMnfAUxP z_uL;XwoA1K8jF&*HD!fgNgsC95X~6bB3vQoe@=)?aNs$*(e`-=2M8(fC4y=*2a{Zq zG^fj4Jxn#F@43Z$d-V><#mSK-hcmuJ+0V6VY+5$Cd`2#`%feqd<#_n$A2SVA?ELif z*`q>SXFYnGP3JW~bYGMTLoGQeh_~lTx4V&QSWv)=57x`}zU*C+iOQi5$5S+q>UjIL zopVbI)xOa-^_F&SaA|{rPxPDM_v2em9mu5bP7!pfc9jv`Bd~7Q`NX}tOl98ool+Lx z89|JR$Gf!Xy>4dT6RMr9RW27rzb&ed*Cut7ALQm8CO%hE_UK?ROLE-VLsi_R=(k}f zTb&j?(zM^?&SlOFxg?7J@NvCf#-^TDd*_j}Xdy%M89BX?MvMP*NApzf527oBJ`^na zsp;QVjrkL0l4HFM3Rf4JzYX5bt}Tw&Y?Z(}>_s1hNa0L$6%CE+++!D=@fHy#^xc2F zG@IQre67~0%4M^Ln)|%&t&SqXcbZ7oIq9vKlCs(wKrN85afg= z4XDam?&>k;v>>DV*BfVTGX6Cs!ZhKD5~X^uyKw7vTS^E`%Q-{SJx!L(?36D>M?->5 z$L6XemtJ1|BrC1DEz)Ii6KxB{+3l&HWeq)t9mkb)&jWVn!$iGnJ*mzE91(O>jyjd4 zwb);zKGJs~W$Bmbb+h}ZEl(~N{I&K%d@6}NN3|<64~#uKUyye;Y*|k!%4H-He#xdD z2pGTxzpMWI`UZ_uPTy}>r}V0{)Nv^);_1Y%HM`yQ-Gpx)v}~KOnkq_)NLe>mL)CK# zWq;0#JjL5uZbtsNX(PKKwScSQcuHoE(k(~-7#dE}l%_H6X<+Jk%V1`F>XTH!^)oUr zmoC1uJmIpuU)1#QiQ&p5TS&|(QG8{5Rxt6^94uS-{AI?ZtcoyZGhz1KKFPa$eDT}+ zdRH6Y$C+y$dFPQY=#kxwUpb4eAJ;0p<48Q7w3Pm2q3Jp7C02q3ydQz1BJTU%*tcnN zU;7JBe;b--&@k?#2tMw0Oq+CI)X<}l?$Go6Av{T7NvuzV9Bh|xU7KwpzW7V~<#;8+bl|1oH+4`7IqD7y_}8nRL?N=;gu zH{T53d~f#rk!gOSMZq9{e$0^XVO@QBD_=N}x1BO-Ub$zjxn2B8`u1Z1AKMf8_oUa< zx^f)E{Lb{&hHu%cxS63&AC#JLc&L}>K{_#iFYQpy*rAs>J_`DFfv3~p)mEp586&B> z&bxYi`fMfWSg^lZ+1L1^%&Woh<7GY4!#zXg{NS|Tw1sAA5Z!W;2_EM0_#mFM1U?CTn zqID{8go}|4tH6kIoD|gVz!c$CR{^yHqqqJCh`{C!OvoVyTOH<2^ZptO61xEz?P0;FZmLH=P>VmS3*j;XGbgCgkDgJHiSIMM^8VQmiAgT*>|HTH{Gx+Gj}jw~!Ld%T5x(xlEc43XbT80D7t5ex zACQ7?o&!}>wHylbfHtH(!))91VcX&=p+_IsB(Rc-@FWiqM2aiH-$@8{c^Esl5__C-SFm(Vk;lP5e1HckZWI>_lzy^KDg3ix@t-=xu{LFAi UTfvpbftX@l6OSNS?Epjn0rK{?1poj5 diff --git a/scripts/initializr/common/src/main/resources/grub-src.zip b/scripts/initializr/common/src/main/resources/grub-src.zip index 524d386a3f907ee1802568f0906eba108f788e80..4df7d0b9c3b591ccb1c12b0198b8fda1d9e95a5f 100644 GIT binary patch delta 13701 zcmb7~c|26@`^U#QV^4P3ce0MH>}4q>N-0!C3rVYNAu^UMNtTp@h#sOMTV-jpW+_ob z3n|o7c29~T`OTRbW1R0C(@!t2{<+@Q^|`NmIp=(CVV}8X&v8Rznt-qzBSsW+WdEuC zYPL4481PL=!r^Yhua6%)h5`G70fWKvVK5kCs3PlpsI&WV`r#liMidGO0Qf}dWfo;1 z1`017*sB2ugW{x|5_-g(U9{D1vjmB%SunaMw(i+$g{#QLVlY1J7|cpC4qep(SV`sJ z9F7zJGvcybEua|73n#b~Wh`mE41?j;CM8JV#wy?`^$+|n``p37t4muTXsXb7w!~Yx zJt@)q&|s;9*+sGA2RF$yJ$E^E%21tkQ8w^&^{#BpOHCDU?4L?}02={N697M;`=Z)2F#gmWC* z*nk7SR#|V>@@WlR@t^)WO+l+oc}63#O(!?kzt8J+cWe`nT^kbsjpe*~chq;~8|^c% zuf(5ssdI_bMAbR zvq$P!6U^0;x1M+{&$4^pTib! z@0kSu2*JbEoLvkZ`q{1?=5Hj#*|~)VOy$NZS_98Am_$UM#$&3&KM#hPF(;l_c0_imBsG|z*0w#@$afK39fe(+_*Aj#{$}YYljpM++&hx zoayxI7T#=>1{Uu&m)tj{64Cce-B_r~E}HGm2Aw@V4I{qBA5M1-P31^L*|2!Oyt4d0 zZxl02jDXO+dM)Foan1Y6^-fZ@N)748GB3Uj~c(^*7UQ=H7IYfRrdL3nZ5Fm7j4`2sSNS4LPYU$CQ@IrLFMEl){O{gRF8Ay!oZ4`VVPl_J zEjXO{6R_*Tey-j4s|wf?yfewp{$$`WU8T-XQ&ETWUn$fyRhyVUGSJ`1oh0%zXXwuL zSEGB(%Dyl&b2)5JN$11iEec(@f)n1mw@zL3VmNnT!}~N~r8Kz0`*65=j^WXo$(;N0?)5LirS4m<`($A=#8&%!nn9mI?yg|8R`A%P z?8*?iNv;juk48t_B1;|Z&gQ6RYJGh44-k3?vJS>+8lG&}{LDP0_Rc>1wE39g=`5Y$ zoVQaCU$_c6#5Z@IF;C+^?%`u!$nmqOYqWBWv8rTq|JjfGJ6A$n3h_TUcpuAOc9PFn z&hq;}?wju}wR}USPsa9_YzlZ7Y`x*3x#G-!yxi7YHatBRtYMB^8MhuNr|||!YYDsA z$@V0(O|_Zl6)A zKb!J6=JqdalyQrPU4WtUkym?cS+kj7yK0Uv(0DK4W6PXd;x#2 z1LHCIYLs1}aPmfF%%frv)!w;-EbVtg@9h*Haq>*=%zwF2{b-e-^Wz;B1x0L{8=In@ zNNkQ?Ze#LA^8C19KBqjB-Pt7Ftx-mjb#=G1duw$;L84C6{0_TyvE9a zcUSTx_Tqz)-i{j|(ogBg4|522)#68Q6m#-)UV7SkXV>@hJ()WGuSdG8w&S}Qh6nuK zyb(^Ac^UFERQXY_!nvMwE9>uexmpo&E?H@Qll^g*>cTU^M>y5oCE0|VZhUA>%NJ0Y zkjIGdJ6A?r?E(WGt^nqG+UkvcjXi85&n0Jv)Jh^{Ma(y5EH6D<@Ny$Vro})v-rL#c zQnR*$80(1~-4EFV+H*4j4n;1e-JjMw)HsDI1&eOc_7vL_ptC)@-Ze_B!pqfhShS+( z>)zuW7G0HN4L$W{7~HRP-3Z}R>s(^-@*VYlY2B?!v#R;Gl~wJx=H4DIvM#hY=t|z| z`hf4L^V;V!mxViQ6`=&43yoHB9i8}_X%%a;oK|-{)ol3kZAV1;^$WRzDn|N6V@*+g z9k&t{PJfq9J+ZQ@_1ZDqrZb5_MshnY5B`a`z#pJ+{$Bwp{(*@BT-BB;@tK`$_Ul@h zdH?jd%w_&^viT5tpZQ6lOT?xrFjvEe|h?#wyx_a-UKaMj#$-79`bOGvHPQ9x?$7F_uHBi@(#IG-3)GhJ!&NK z>3J4DBv!8(tMM+c-PB^gw9*^h;$!vphqYIIjVm4r>M@-sA4CQzE}7QbV^8e7biA)A0iTw6$S>!qfXZP6=@6>+1U#--&P ziRDZ$2leH0yOtOJ^qb1co4u%6w@QRVf7@nRiFSMd4vf`E4ZO(pAYWuK>1VRqKw5>w zx|koYe|chxti@kc*VZ@^;a9DBG*mr9;lFhnSA_@HPm!i zRaC71Pr4TWKo!i>`E$X2w7g+Z*tBL?<4}Wy%*VIoUuN2KXAVi9QRetF`>k!6s^l-< zODqX$c>jU%Q{f45H&0gdLC}bfk{^b_=Si5sqfB4Bi?^TdvKn^&^+fi#S~l}F&a6Vo zqh>#zSlI6kAD>!RIR~g*eyT0+u^^4sFLuTI^gI{uK z^F%}+>*~(UT+1qP=X_~hMu&TMmRCxX_mes?#nbBrdD9Wsvi}_J0R!|`dv3ar-{rj7u{+7=mVtY9a={~P+|BbMV;g-| zGD81M*6^KUPhZ{N^3;Ua*Iv*4&6G@qLtOXj{<^ACAMwgN_p9*E#{&I+?KlVRw)7M>U@4mM^mXqvs?ENnOf(xfAMNBIVYJa}hS=-`* z=es(0m)S2Rr2ec}lkirdB&qe8>MzFDxU7=^+!TgmI-Nd>vx2W5zkRmXeZ5p0M{>1q zT{~m8T;&Dkxt85)x0ivRd&kY!-F9{PS~t=f8)M2MGkj3e{czFEXngfm35zSOiM(z6 z-vtCXb|{89++ulL8`;DK8u#-CW;}P}OXk>U3b8^qx_+Ni%JhuG)(qn{CAE@OrJpSS zrup8pw~W7oJyTO9NO4zvuoP5Yns>?O?S;ziU2y`LwcDO>3XlKr`@MRH=efB&$!xD9 zlD&Mo7}wYDbu;#_{HooB*UBys%aS|P2h6ruzP@Cl)m!@^$%(zNJ0P+hvuS)%fGL8j z_)OH3(uRxdQ9DZhN!^yb|8}_g70qK?KTHP*Wh8ux58TMZs#T?NhqJBU+v7Iyg(a>i zC|o@MRzgmfP+ZS)}vY zO{TWyd*71^&JBJ^EE+lK%={^s&3m5CxR*o}Io#LO#BDxSgRR;0kv(Pg?dHnB7_r^4 zLJgY+cL=U9OE|5}HsoflC2}+{Y_~f1hS$rRy;F~w0|`4)HP({2w32URD-@sten zT}ip$Z>-|~8giKZP~5k~NE-~(?phn zhxYPd$scR*N0Gz-e%O=^ZvYN4k-paCq0?LDzwOY%W48b=!1-`hUaY{vCl{^kSFC~e zl<*8&fO{c)4-GLzwm=C*%rq~SDx6mK)IESHc3zAVA9lq;c3R=W`++)&m_|N&oe??+ zU@6(v__52E&c6LH@QxB5&yVF=I^6jLAWaF+_XGr&4zKeBA}Qfc0$ARq!^3@n8S+r| zmX5q10GwbX^w1*&Q2u|cHs&Mgoh2X!!^ncc7!g(*0XJSKMB@v(tT+T{fhEC(puBME zg+?p9G8~YBmjW?-pCDFzA^UzBD@P>;$b4~r2tg2XodHr08Nza?dgEfLUvlC7$!u_PXgOa#4^t}ilq>sT1d3QMG66b%6PwtQ12NU1Aqu(M9RkR0Fe|$PNG=; zB?k>Ns%4+c06z-zOqAZBGadlWi>G^y7##+#v$D*+3OG-xlP881UC>6uGJ**6CsO8E z2NY8n;;azX7S(>G)i=9FU?+Jd)u8v!sSiF{;iJ!iHn>FfOJMzyipm() zVx*L@iqI$)qm+ z!AqLhO|caq)loq!Tzdt0j?$TO87$dqg`eBB!so=mTuOMeEJ){J_D&YWvCoUase@Ec zRvO#B%uF3z4<9N-?S(oZ)sCn2Ag3YTC|z^Xxo zCg8HAyZOKboT7~PjXB7(5Ke0VgXW+;CH%ZRJu&`UL37F~9+t;aJv3;I7uZGB{8lHB z&QN=tz&3L8jTU6nN<4l9bcbaVnxeZ9N$Y?~_5j5x6WSdJQf(w!i3b9~36A+08dRX( zrnJI&&VzlFgJZKIy?v*i01Cje2^&cXs!*iY?O+HDr>rjJ40`R;&j7bkb~Ry}(mBQI zazQ)FTpAaFR1ZX2D`iI!xQCpb&YRUhIk=l5J71aJ;Tuo|&QN-oR!_Z$XzhpMdQfpb zdr}KXHAq^jxmv&q${-0xHPvH}R&w^&U=>A+xhnMp(a2u<9^`_PbAJYv7FHpRM-pW4 z8H8_!;6R8GNBw$FYt1Hp2D#uzT`wb68EXAXeP+=Jhu(h$8|IUbPk~tZt)4CiJ#p|& z809%hi~BPLij_?dfIP%!yc(gFo*MPBPJ=01Hv-y`!?hQNOe^xmICuh%B#c=Znp2}U z=78^@1vy*^B8-CSoTinXpk1C4zIBFrYtRaJngPWq;W>Zkh2Q%FHdDgkmO@1UmT}4P zLIg!>WuE}-Z-6v&YnBWz6KBGzQ%d-=((C(KR;&dG^=klvN^FFyOV5K|?mnK*2favl zm+-@n#X&aC zLP(+Tg(o3YVxcjpp)#aU*}n)?B8Nhm0RZz~#H*3w)#--9y(7q#gYqWXmm^wZe94IwR}~~&M#Dc`m=P2BfAY;WBkATEF-H&q5!GVFP(w!Z5NW2C06BRV zfuWFz79b#KYqJz0vWf|03=Bajb#UMa%07plLkb%oX2E` zoF);(8}0Q(bu_STmJEP62;bzB?!*^gX;Y^QWg-fIiny_?=qiZHU)7S35I~N`_?+?AiVGG|tQ!j%+0SF;+Vp24lQv8fT)Z za>AY-`iOc(5#4wgN5vAS0yg;mTGZUmd&)(GwIxM3CpRn-^&*ifF>C;$i)s)ijRn1I7Jen7ko%!VLuTan?Yj9 zH6#r7711$!DMYmo&G0Y{>5VWS3od@WXz3u8E6OE&NWpF+dQBB+M9tK03d>G(9qCnP z;6YS(=qF_`;sN>53(p`6+kS`T zqz8|yfr6aWZ@c#y2yf4@e+|2q^k@$q1ih1$Ov5H7J=#eRLGNQH(-cn=y=BMcu;Pn^ zhx0Nln44xxzq>BH_DBCHfYuUjeMPu280|#|-78_T7})(p*P$nm&=D#{=3pBXol8Y> zB1&Wq_Cx8lF2M=WItdLj4cnvWRozx1)S9j#az=mMut%%?w*!Q~8nR|r2X7^-h8i5yD_LTcaY!yEj9d?PWB+<$jh1PgTtj%3R20HsbQewR zni!%EJ#^{z$_VMhiDU_|Z;Q@VB00lUG6x&D=$yO?qTa)6Bo20R>CJ0#Ct_Y3Zjp<^ z#x8o%No3K_MPv?kc+oldm4aJw(JRG<3I?Kyiw+g2B1U+Vj1YZabWQ+=;J6<#EEvG( zTp*IeJtj$nO;oHjwG~WoduaG6P%kn=Ni_rHF>eRco0O1o#H2VpA=29Dl5^D%Lltcz zN+#LF=p2_if~#y^7%JTd=CT=tQ2iHV8n%zot9Bu)-fkyzu#b$+nP?!?w!U6aOEQ$v z;cH0PqKmj1)}SNonuvO@ddT%)cNv|7SGhjhqE#N<$FRs@roC`TMqGl*{A&ookt29` zEqx3`$9d_3y2}?8RO7``6{Ogiv{ox|QwlDkeR;7&3wr62+>71GCK4n;2a;J-Ns5nH zNtO<@*i;E3LqvmmNpk+Zh~x?PkvR1w5##}7$xl*9G_C27xJ6?tm?vTT8i)QG#z=VS U!e9;%{=LLuFaiREFJR370p4L*g#Z8m delta 8865 zcmZu$2|QG97oKazPWF8#+t`<~%a&w`6tZPkB-xUP$x>($jsBMw(LzO*29qUZjS{H{ z87dVmB3ldk?!7ZIrf>RHbKmDV&pG$pcgAyG%jsOn?>Rv=9*pn?78D8<5fFLkaD-n# zq#F42Eg%Y@z(b}Op;XCt0Q8rRDESVpq|QlA^E-4<4V2==2%`m2C{W4_BSgLuhpsd% zFcR<;ECapYNJg+^WFtO|hmpLZMp*&J%rR`_B}MWQmVBkUO6WDhuw?-vj3`vFPqdF( zrZ77*R5bD)E+kW!jt2!6NMiV)cS6WJ5k!Gb9J!Fh#{!}-F3Ic^6%?Wh2NH&X3JkZT zFTW?9s_278b`)w-7lo3i@EaJIaoz6jk;9w$6Bf%%mV0mR%5B&<`Lxwe=hQ}np|11W z&f`13e2J0dKF_%e#d_TNW#ymv9>6O7hJsBs-fqsDXY65WWQ*SYnTbL1qn5&&dligx zh2PRMj?euVuVgqZ#S_(TWv_0wHD)8<8%rL2@!7OpEtU44c3OlO*zrEM9jWNWZL1GF z7Zqpj_>IjyzV9) z1CP>`oW_+1%D828U&cmT@@q-~e7$~;s&*dB7L?QyI;SP{eTELpDbFRftJPLW!?s&H z%g_D(#9lnBT+TJF8f!}~4y#l~TikxjshmC!mI&(`Y5beaHoa}Nx>2OO@%o9ay4&&? z6z#`7%(lHSF}TXhC!pCaPgh0XeZS>e*R*8%E$RI2y?5pGh2nh^r7Yd1mn|)-GH&Ni z@hog>^0lz6-P36xevi}Vcvjvu02Sure%R#ti0R|Ri;DDTF^QMkc884(`wMSf#+BZV zx!I|ne5p(K1TgYcZ%=Hi)wMz0^k9raXX?VV=eO9jJ@ZrBz88?b4b-)q+h-C|$j$Uh zEYjf^|F%cy3G1Cc{QFNnc5a;6F!A}&@RfyWnJkvXr^79w2T&`hjGgi>^Tzi(4;PW- zT?UqmN%{1j#MGirSqWY-+VF4(^Vu6Yd)R9mJ~)o#aT&{*T@=n$+R%^}+bJhd zBIW3gNjO_?2w<_HGMR@^UweKE9`BJ+bUIsqVFDHF};^M~K9dpm}8b_4* z^Cq5O-nY-av*x&oMj>E9)PH5f6&NRiOVd>zVqVT5{0ub1Ha&XLxy3q$cSqu-?s1P_ zVtRwqj?c;^V~tKq3GSJ0zZct4UCf{&`0NsI(p^Wp%9J;O;T%_vb7KVuC!1%T`xf)> ztE4b5mJN%LTI%fFQe46EQMBCai7>xBq%i+!owR2`#n=m@g|xz4G52=v4z*wmV->Qx=R$yG)m%>rW?RH0S*$ zirZG>X7d5rqZ8)#3c7{U5;BqfcfG0;F6R)0_n;$3EowypL+=RP$GjPCgs@wRmGtLv zXE&D9?WN!AJD=s2q$qpgbjYD0$GD_!MoVm1ZAO$izFy8!wj}W2LsbpEMmZDxC#O~d zI0~&sk{{?C?jV9iwx6-LF4>L>3&p9c$}I2)ikEG7+hluSDK!37!ss0?5kQKR%*akB zx*&5Ws!LGPwQTls>R`s0(-9|Zlt`V00B$Vgq=yMAWsvQM_pR*#y3$t{nU*}}%7X?@ zdX=2!YfRq0d$aaaskltZon6NDa?*=#K#XBxb+&4(?g^JuTRl(gPM z+~81)NT7AM(*=`glNk~3#Pr>)nm_KwAJ+OZ;?X}ma*5+>C}9z>d;G!a#p1Ph>LJyn zy*Bzi3@>%dYumAWLleq?SkR0q^BCajBFns zpwUWVHkajFB{<)*nagWcYx^%eb~LJaEPk2Ce|~SG{%@6$9dT$Ywu6b|3D=JteHp~= zZo^kyS2gc*#9^3$`G73nhf90rW}}HlF$2M*ChqD{%om?V7k81Y;b)0IE-O6<+3EXW z6HZk6^L!wI!{)l1-gHPuqa%0Ze^TG4{ES z*QBciS2V6;(1YbQl8!vvAwkKf`vzzUt}PE=xk%- zfRGK(TTQtk?gefqV0^!`%s|Vg02T8)DygqCW#<50`#5C|tahvOA@x;}S{$aG&$)oY-)TBr;88_#O9h(doTPTAj-= zYc1Pq25T43#W@!bu*PwRnA79UN=7ke1?+c*zVCH>KXp|4=9~|EUbp>{Mtgy3N%H~! zEyK58j~0lP*bxGaDt57MzHWFYwZK=Ht+FilBCfpc5_A9ChL5V88p>%O9Z$S6G}H@d z?CFs2@meZv@f7YEX9|oQ@!#yrg39n}hzXDQwba)6_?X}4*H??nO&aMVFJD-q!wDXp zhQ8}CI$x?(ilRHKE52JkG>YyCO?WlWxAgnO<*`3k6R@r|m`~>AvzL43!9yjBdUX=J z!;5lo+p`tUiVk?^9VQ^~i( z2p*04`Vp%5QMIdKT5Tiyj#f6$`)NFMK4>npgWj-V`j4xOngmbHY954v0*_aFAYz}cgH zxXSY$Lq?#YDbFdzkMXe`0jSOd2iDcr${LSr>HDk{3C1sPB^gMyE_lD()7u;v z_%v5zJ0~tI?M?6Uow?or?KRH3xj_JrIlm=3&uGhqE6+O!&q0PG^LJ}rzuT32bR{P6 zqUiOD&+QhL{J+gy^)8VT_q7nRDel{q`)hD?;>G)luQ|hd;~(hST!cvMLasG4jRu}? zfMF*t?t2|6VhPsy-F=2zB({)@(L!#a(|?E`3-j=u*(A3e{aCT9uR6Y53LnbpLU~A7dL<5=`c&5QmB)OmPsDepq!Y6h zN_mmG3d$p2r%k)kZ!?+Pt>qnQJfk6UAUZ_Z?uSuW>;*Gv*1_4u7EhUU@O41XD8~_3 z(IE312mI@IxlQJd(1?2n&mxdP}$|FkdXapA~Og2O8!)mqF?Ms#rxM)RqG{QT;cUNbw$3=%46b7>tW|;;ent4 zQu*Sxh$8VCzxKqPlXq)`3_1>m1Ru&&>2X-ux^S*MY`@|{r;OMQwRpek7>Sg4OXTM**8~xz%5mHlExP^dhXu%7VupG#RI@USCvd(6v03RWp>4 zSFnq)Wl)Pn^-Q@ymguwnuJ4=jrmP=#T$kzNj>6zi7G%df#E&h?X0km4v=iL8?`hac z6GF23?&jDi8RFtp=1NfPzsew7s9~z8_V#w(`7Xll*R|CxwJ? zXit)pflR0I&-5FjrKsOe6867oZmT*~kAB_!i#@jd+6Rw9-SqTlpTC?i?itztW-9h7 zcy_i=!~IRDZ!{nHIZ*h^EFm>S&hdU~`KJE;_0KQs*p2y0oSAl#w79x3#pjPwFU=;7 zdIO<4vjM)xtU{XIEShcJux4#4elmJ2>ZJ1TGaqlx3!OPvV)fv|i8`waKkfb)Mnb;y zTq%2w>&5bT7x6xqN!7Y>*KjxY?mUqnKiZDJFMnb`=@``BYZSyv*!Hu_ydAYNf;L_u zkDqk1lvxj&M@1cM&=_85q+~+rhT40mN~U$#FrZM&JSddj8Vldg!%JY`-N%58lJFdh z&lct-vkLsXIf+5So`%v?OTA(kLBfzp>e>=MZ7UCfxb^p%I(~3F z4}r-#lpd}?8Q)-!jK<2Kg8~MFXK~;n=r}w@*vjB`NvIB=-7-k&)`U-P#fY#v=p}{W zCoFn3Qh4vBFd_sY|1bnq2E(K=5`^jfofPzsG)9OZd{78Us1BZy!SLhtGvx{O5g?g? z$3+VfDx)(P$rXhr@DQ#f9Hoef%VPK-x(Fd7Ih7)KoxCJZ$W2Y4EGfuA1sy)^N3IXg zl*v!f&%8&G7t9tVIAlLTu8`Up!5(=G7vXr03%LpKz4H;Y^ZF=iFBPDQ_g$+;E+s)u z5rBbai{MoI4F>3;0|RK+=#aMvLcr0oCqd+HKKc2Bl3Ex>uoeq7 zmb;Rj3E2g3BnxP$gkd9u*PBy#@EV@f%uGPH{9yh|V*nMEp*!8x4N^k$gQ3v7%8;Bz ze*`5IUc-;Q<|b?y30hNQz+ZXCM|k?~I?^L@Z6H<^y3;2U>1)L?fn}=DCaU;WL>-#2 zuz_%EL6w!NkR<>RAga*;7RX~;)lYQ8Vo=02Vffh=Ayz zfFsC>4Dlxqu(*~}kZ2(SxUF3?fC`!z4&pZ{z-p~)44@|#5Fut`fz@iE_VN+WXaIfG zrC!y-2or0KfJ4-BPysx|1Pef%nwhVSVJCjJ2WHu+>KE9A;Uuc>1>RCURZ!O-;3oz{ z|4=lcrVA7MgMky&H+gO|RB}lK;6U}XpeMr!s>A`zM8`P5nF1Wwfm*bj40x;&SwSXU zD7x$n;7Tp2nhllhnGO72D~5&3KEgjBVqG)KE&__wVJigyClPxMsG%f4wjoO~)I8H- zplMBu0gTs&vN~1(oD?Q}_y*po0YoSr83owD0|rn>#A*S~)lbMMzzTjcz;J-c4}gxp zL{@OWA!Y-p-VKNm>l*-WYWB~E02@)N8E~dv^oDW~yV`(I$|96ojF{C4SW}Zcj4>OC zkft2<;-E2<99rZhHuVC`)QHO#=zb(#0OZd9r^qf z!Tt)MV5|qikkeQMlUWrL&cNSIvG`aa2N!BG<669J4k2U!h({=1RrO9nRoJvOF( zuKyk$Q-|)*MKtlyQ8GEDETv2oQjMb}49kIBLNqBakOA0ifSI(QjvW#O`woypm?qL} zCmDn+2$)F=dN|SHpDD^CZUkf#p$WD0fKeR=^6lYws0yxYLz6dI8FDHhizrQOvp@Mx zVABGo)1p8S8HN3e3U&8jqeb^Lt&9PKLUl2rP@5?2Avc~glpF?o957ytCS_0r8HMf6 zf8sQt+7SeVjSrYW3;M^ALC6FBzm3n92*VQO-$;Z=&?xVwMpx|;I73VGJ3%D^Td38x zhPbV7Yy0UnBEH;Be3cjPwVqd(xsHcH>=$@eil)4#>@^w)Te4NX@crxcj^t7iVY9YM zEC^XoJbi_V2-`SNU4|y<6fKs~WA_ z3QeU{p^!IREp{boeX%iwH694NM$kre$TP!2MjLcdr-9w{C@^ff!691M&y)iHW4=K* z4H~KctPrja*iVZn*i%$sLk`+&(gfagp}?>;|L1dV|8Z8aniYlmpov0ht^1sx-HUWj z8?@7+QOpjeD8fD-d`1hKhf`qK(Stj*X`(M4qrkAY|EHc)nO^VOA^%|v{lL>+S5MO^ zN*rwe!7ZC;WM5@cV0aJ!8);!IG}}UZQ4ciQOcOYnM+t<71F)JF4lVk-OUYxx>Q>22 zrr9bhWoXbx2crvC=+LCV-$yjC$OHkZ>C(V*O_U1YSpqDfh27c_Sn=Pf0#wqYk!t9s zNWt^PKh1T?HhOpiYHkYDT$6Rpz1xpGY&j4fK|m#a8ribf6j^u}A%AzRH<)wdXt2Tv zqePGRZ18RkO>Yd|Ljoi~37J&>sY}uv?Nz$$&A38_#JHMp`$hw$`|@m0D0smY>*2;{SOIv1+D-9 diff --git a/scripts/initializr/common/src/main/resources/kotlin-src.zip b/scripts/initializr/common/src/main/resources/kotlin-src.zip index 62c4b0d91ebf286b04879864fe10fdc228030322..32c52cdb23f951135e2fe1da62acbf084a784591 100644 GIT binary patch literal 1507 zcmWIWW@h1H00FH${}?a>O0YA?Fl6VKK{oO5@f+C#qLdW0l{rO?G#Kiw1)-7{F+x(p*J7;FK z8hSdN+VGWKQmmn57Brd z&$D`6(~Sk+^kzSu7oq>?c<3q--Rq6jhy7O{HuF?l9LS`6Bjm{fmCg?uGH(JFi^_4| z*!Cf}j)(EPRHMny3w(b!-M@UQ`th>Kou3c=vJ=kcsWz2#Iw@Aunvj)!Bk{~()tu}} ztS@CV9BsNPGY0habwKjfMye5Zh zujfmFdBPms(p#o+wQRbUkS)8zRxDguC4?nk@m`+Q4%LbT?e2pTzh0lbRbufcwjrn} zy*!|7_JZE!?muV72dZ@`?VS*l@cgjHt25DoGqx9e{=j3=%I$pr@apopDkU5C`#hNW zH0*{%vMkT$gR?BPMfGwI$2)({zVdbDK4k-m?~Hz{=O4(iy{)tF&e<^gXWP4f7V+et zdRQOwSgX!wx7zoU4^wqa7p`k&S3h~P>D}=cDYX-yCvu1J$OW_|I(?{j`hNPum90~j z_xu*eojJr&(@k=IN@^Z3q2%WgO52P~a?H3gtOPXc3NXBN1To>+mlcwIF|sah!?+oc z4Fj15H4KuKNjFd!#lSXH0}P48DxrU|1H%XFRS94haZgerH(Hs0O5v0s`i4 ti1Db|9iLU$BTyX0Cvm{i1<5MZ6oSieRyLqB8Q6eOj){R`JO0Waz?EI3P%sl-7oQk*+ijwnl@hB8VRhU|lm|KvOibsPa zb`80ei3J5x)!DsdqijNQ3BT(`3GXeYCo(K%NZwqn_vya3*Qeu2lbNm_ zoYvO=l(WK)^8o-t;;7^c{{3F#Xk1;vUc}Wch*}~nw%3U zIepQt8d=%fGd6@3Z$7w4S7%>eR?n`yAf@lieqPJ)zHNSX|77p)rqe&lcLeOuJRkM!rquo?&fjgrLWY%2AACQE1b(`}W_7pscGF|GHv(Bv z-f?-eOkM9wXDnj)cfp$X*3=IUC#9BKIGmq)ak<+eDc!e@>~EZ%uV2>gm?7leDch=Ug-E`K|t>JXUt@lNG6u4MtUC2XTkH*1KPZesGXNc@vqS!MrBil=~ zVl}s)QW1~rK>-bqdi9&ftGjj{{u{jLKzWkiU!Mc{6P%hKuT$TV;QOOx;p)T7qipV- zv(t6Ic|7yHg}pmp`uYX=hIZfiZ-r^ zeJ#MAB=a)L>*R0G*FqaJD}VjcTg$=Mde!jIqPbQ3Mdq%1up{e}V9n_U`CA*!+Olt$ z@Hst}sNQn&@Z_Vviqvij{yaLj@w`Y*rD5A+)$m+9soM7kHv9a&lyv@IYwgpL0{e&B zi%TM(rR$|{yS%XBm#B&P9@EPD1Lg;`Euy{UAKd+YuT{Ik(tkpSi+AV72U~?J4&Dif z{rqC>i_Y%&53!aXqwh%{*3Yhfeej?t%W?m`>g~!WFQ0l67E|MPdN-@$-;Ao72l3Y0COKx@1rJas5C|~5bp+AyVhFb;P(cI%AWdkc06yD51rtJ}ACL*P z4OE^XiYeSSg9<7L0NGp*WMVM@R&0Tc!Ci2Hj9~=gC5_wg8G~Gm;qw|O{2>73GA9-k Ym!ZZ6E3oKfU|<8nKA;22fl3(|08`W^mH+?% diff --git a/scripts/initializr/common/src/main/resources/skill/SKILL.md b/scripts/initializr/common/src/main/resources/skill/SKILL.md index e8941d36b9a..e3d88dbd20b 100644 --- a/scripts/initializr/common/src/main/resources/skill/SKILL.md +++ b/scripts/initializr/common/src/main/resources/skill/SKILL.md @@ -21,7 +21,7 @@ This skill teaches you how to write code for a Codename One (CN1) cross-platform `SKILL.md` (this file) is the top-level cheat sheet. Deeper reference material lives under `references/` — pull the relevant file in **only when you need it**: - `references/build-and-run.md` — Local vs cloud builds, JDK matrix, Maven goals, `codenameone_settings.properties`, running the simulator, building for iOS/Android/Web, automated (Enterprise) cloud builds in CI. -- `references/build-hints.md` — Build hints: the typed `@Ios` / `@Android` / `@Desktop` annotations that the compiler checks, and the `codename1.arg.*` properties form for everything they do not cover yet. +- `references/build-hints.md` — Curated index of `codename1.arg.*` build hints (iOS, Android, push, web). - `references/java-api-subset.md` — How to inspect the supported Java API subset, IO (`Storage`, `FileSystemStorage`), networking (`ConnectionRequest`, `Rest`), OAuth/OpenID Connect (`OidcClient`), WebSockets (cn1lib), concurrency, dates, SQLite. **Read this whenever the compliance check fails or when you reach for a `java.*` API.** - `references/api-clients.md` — The three "spec to typed client" code generators that share one architecture: REST/OpenAPI (`cn1:generate-openapi` + `@RestClient`), gRPC (`cn1:generate-grpc` + `@GrpcClient`), and GraphQL (`cn1:generate-graphql` + `@GraphQLClient`). Read this when the backend has an OpenAPI spec, a `.proto`, or a GraphQL schema and you want a generated, annotated client instead of hand-rolling calls. - `references/ui-components.md` — Form, Toolbar, Container layouts (Border/Box/Flow/Grid/Layered), common components, navigation, dialogs. diff --git a/scripts/initializr/common/src/main/resources/skill/references/android-to-cn1.md b/scripts/initializr/common/src/main/resources/skill/references/android-to-cn1.md index 2c23a546e26..73f16aef281 100644 --- a/scripts/initializr/common/src/main/resources/skill/references/android-to-cn1.md +++ b/scripts/initializr/common/src/main/resources/skill/references/android-to-cn1.md @@ -116,7 +116,7 @@ The EDT/UI-thread rule is identical in spirit to Android: never touch a componen | `Retrofit` / `OkHttp` | Not in the JDK subset. | `Rest.get/post(...).fetchAsJsonMap(...)` — see `references/java-api-subset.md`. | | `Coroutines` / `RxJava` | No coroutines runtime; no RxJava in the subset. | `Display.startThread(...)` + `Display.callSerially(...)`; chain callbacks. | | `R.string.xxx`, `R.drawable.xxx` | No resources system. | `UIManager.getInstance().localize("name", "default")` for strings (reads `messages.properties` bundles); load images by file name `Image.createImage("/foo.png")`. | -| `Permission` manifest entries | Different mechanism. | `Display.requestPermission(...)` at runtime + `@Android(xpermissions = ...)` build hint annotation (see `references/build-hints.md`). | +| `Permission` manifest entries | Different mechanism. | `Display.requestPermission(...)` at runtime + `codename1.arg.android.xpermissions` build hint (see `references/build-hints.md`). | | `Activity onCreate/onResume/onPause` | No Activity lifecycle. | Override `Lifecycle.init/start/stop` (app-level) and react to `Form.show()` (per-screen). | | `AsyncTask` | Deprecated upstream too. | `Display.startThread(...)` + `callSerially(...)`. | | `BroadcastReceiver` | No analog at the CN1 level. | For lifecycle-related events (`Lifecycle.start()` etc.) use the CN1 lifecycle. For external system events use a native interface. | diff --git a/scripts/initializr/common/src/main/resources/skill/references/build-and-run.md b/scripts/initializr/common/src/main/resources/skill/references/build-and-run.md index 6dba2b6b60e..7965ce7a0a9 100644 --- a/scripts/initializr/common/src/main/resources/skill/references/build-and-run.md +++ b/scripts/initializr/common/src/main/resources/skill/references/build-and-run.md @@ -198,27 +198,15 @@ This file lives at `common/codenameone_settings.properties`. The most useful key codename1.packageName=com.example.myapp codename1.mainName=MyAppName codename1.displayName=My App Name -codename1.kotlin=false codename1.arg.java.version=17 # Required: routes the build to the Java 17 build server +codename1.arg.ios.includePush=false +codename1.kotlin=false +codename1.arg.android.xpermissions=... +codename1.arg.ios.deployment_target=14.0 +codename1.arg.ios.teamId=ABCDEF1234 ``` -Anything prefixed `codename1.arg.` is forwarded to the build server. Note that -`java.version` stays here on purpose -- it picks the toolchain that compiles the app, -so it is resolved before any of the app's own classes exist. - -Most other build hints are better written as annotations on the main class, where the -compiler checks them: - -```java -@Ios(includePush = false, deploymentTarget = "14.0", teamId = "ABCDEF1234") -@Android(xpermissions = "...") -public class MyAppName extends Lifecycle { -} -``` - -Declaring the same hint in both places fails the build. See -[`build-hints.md`](build-hints.md) for which hints have an annotation and which are -still set in the properties file. +Anything prefixed `codename1.arg..` is forwarded to the build server. See [`build-hints.md`](build-hints.md) for the curated index of build hints. The complete reference is in the Codename One Developer Guide at . ## Layout invariants diff --git a/scripts/initializr/common/src/main/resources/skill/references/build-hints.md b/scripts/initializr/common/src/main/resources/skill/references/build-hints.md index 136d14fa16c..d5f9d31023c 100644 --- a/scripts/initializr/common/src/main/resources/skill/references/build-hints.md +++ b/scripts/initializr/common/src/main/resources/skill/references/build-hints.md @@ -1,159 +1,14 @@ # Build Hints Reference -Build hints control native platform behaviour that cannot be expressed in Java or CSS: permissions, frameworks, plist entries, signing, SDK versions. There are two ways to set one, and you should prefer the first. +Build hints are key/value pairs in `common/codenameone_settings.properties` that are forwarded to the Codename One build server. Every key starts with `codename1.arg.` (the build server strips that prefix). They control native platform behaviour that cannot be expressed in Java/CSS: permissions, frameworks, splash screens, signing, platform SDK versions, etc. -## 1. Annotations on the main class (preferred) +This file is a curated index of the most commonly needed hints. The complete authoritative reference is in the Codename One Developer Guide: -Most commonly used hints have a typed annotation in `com.codename1.annotations.buildhints`. Put them on the class named by `codename1.mainName`: +- — full guide +- — editing build hints from the simulator's *Build Hints* menu +- — variable substitution syntax for hints -```java -import com.codename1.annotations.buildhints.*; - -@Ios(newStorageLocation = true, deploymentTarget = "14.0", pods = {"Firebase/Core"}) -@Android(minSdkVersion = 24, useAndroidX = true) -@Desktop(titleBar = DesktopTitleBar.NATIVE) -public class MyApplication extends Lifecycle { -} -``` - -**Use this form whenever the hint appears in the generated table below.** The compiler checks it: a misspelled name is an unknown symbol, a wrong value type is a type error, and a value outside a hint's supported set is an unknown enum constant. An attribute you do not set is not written at all, so the build's own default still applies. - -## 2. `common/codenameone_settings.properties` (everything else) - -Hints with no annotation, and open-ended families such as `android.permission.`, are set as `codename1.arg.=` lines. This form still works exactly as it always has and nothing validates it — a misspelled key is accepted, never read, and silently does nothing. - -**Setting the same hint in both places fails the build.** Move a hint rather than copying it. - -## Annotated hints - -Every attribute below is generated from the build hint catalog, so it is always in step with what the builders actually read. - - - - -### `@IosPrivacy` - -| Attribute | Type | Build hint | -| --- | --- | --- | -| `calendarsFullAccessUsageDescription` | `String` | `codename1.arg.ios.NSCalendarsFullAccessUsageDescription` | -| `calendarsUsageDescription` | `String` | `codename1.arg.ios.NSCalendarsUsageDescription` | -| `calendarsWriteOnlyAccessUsageDescription` | `String` | `codename1.arg.ios.NSCalendarsWriteOnlyAccessUsageDescription` | -| `cameraUsageDescription` | `String` | `codename1.arg.ios.NSCameraUsageDescription` | -| `healthShareUsageDescription` | `String` | `codename1.arg.ios.NSHealthShareUsageDescription` | -| `healthUpdateUsageDescription` | `String` | `codename1.arg.ios.NSHealthUpdateUsageDescription` | -| `localNetworkUsageDescription` | `String` | `codename1.arg.ios.NSLocalNetworkUsageDescription` | -| `locationAlwaysAndWhenInUseUsageDescription` | `String` | `codename1.arg.ios.NSLocationAlwaysAndWhenInUseUsageDescription` | -| `locationAlwaysUsageDescription` | `String` | `codename1.arg.ios.NSLocationAlwaysUsageDescription` | -| `locationWhenInUseUsageDescription` | `String` | `codename1.arg.ios.NSLocationWhenInUseUsageDescription` | -| `microphoneUsageDescription` | `String` | `codename1.arg.ios.NSMicrophoneUsageDescription` | -| `remindersFullAccessUsageDescription` | `String` | `codename1.arg.ios.NSRemindersFullAccessUsageDescription` | -| `remindersUsageDescription` | `String` | `codename1.arg.ios.NSRemindersUsageDescription` | - -### `@Ios` - -| Attribute | Type | Build hint | -| --- | --- | --- | -| `addLibs` | `String[]` | `codename1.arg.ios.add_libs` | -| `applicationQueriesSchemes` | `String[]` | `codename1.arg.ios.applicationQueriesSchemes` | -| `beforeFinishLaunching` | `String` | `codename1.arg.ios.beforeFinishLaunching` | -| `bundleVersion` | `String` | `codename1.arg.ios.bundleVersion` | -| `dependencyManager` | `IosDependencyManager.AUTO\|COCOAPODS\|SPM\|BOTH\|NONE` | `codename1.arg.ios.dependencyManager` | -| `deploymentTarget` | `String` | `codename1.arg.ios.deployment_target` | -| `glAppDelegateHeader` | `String` | `codename1.arg.ios.glAppDelegateHeader` | -| `includePush` | `boolean` | `codename1.arg.ios.includePush` | -| `interfaceOrientation` | `String` | `codename1.arg.ios.interface_orientation` | -| `minDeploymentTarget` | `String` | `codename1.arg.ios.minDeploymentTarget` | -| `newStorageLocation` | `boolean` | `codename1.arg.ios.newStorageLocation` | -| `objC` | `boolean` | `codename1.arg.ios.objC` | -| `plistInject` | `String` | `codename1.arg.ios.plistInject` | -| `pods` | `String[]` | `codename1.arg.ios.pods` | -| `podsPlatform` | `String` | `codename1.arg.ios.pods.platform` | -| `podsSources` | `String[]` | `codename1.arg.ios.pods.sources` | -| `prerenderedIcon` | `boolean` | `codename1.arg.ios.prerendered_icon` | -| `projectType` | `IosProjectType.IOS\|IPAD\|IPHONE` | `codename1.arg.ios.project_type` | -| `spmPackages` | `String[]` | `codename1.arg.ios.spm.packages` | -| `teamId` | `String` | `codename1.arg.ios.teamId` | -| `themeMode` | `IosThemeMode.AUTO\|MODERN\|IOS7\|LEGACY` | `codename1.arg.ios.themeMode` | -| `uiscene` | `boolean` | `codename1.arg.ios.uiscene` | -| `urlScheme` | `String` | `codename1.arg.ios.urlScheme` | - -### `@Android` - -| Attribute | Type | Build hint | -| --- | --- | --- | -| `activityLaunchMode` | `String` | `codename1.arg.android.activity.launchMode` | -| `appBundle` | `boolean` | `codename1.arg.android.appBundle` | -| `buildToolsVersion` | `String` | `codename1.arg.android.buildToolsVersion` | -| `captureRecord` | `String` | `codename1.arg.android.captureRecord` | -| `debug` | `boolean` | `codename1.arg.android.debug` | -| `disableR8` | `boolean` | `codename1.arg.android.disableR8` | -| `enableProguard` | `boolean` | `codename1.arg.android.enableProguard` | -| `gradleDep` | `String[]` | `codename1.arg.android.gradleDep` | -| `hideStatusBar` | `boolean` | `codename1.arg.android.hideStatusBar` | -| `installLocation` | `InstallLocation.AUTO\|INTERNAL_ONLY\|PREFER_EXTERNAL` | `codename1.arg.android.installLocation` | -| `licenseKey` | `String` | `codename1.arg.android.licenseKey` | -| `minSdkVersion` | `int` | `codename1.arg.android.min_sdk_version` | -| `multidex` | `boolean` | `codename1.arg.android.multidex` | -| `newFirebaseMessaging` | `boolean` | `codename1.arg.android.newFirebaseMessaging` | -| `proguardKeep` | `String[]` | `codename1.arg.android.proguardKeep` | -| `release` | `boolean` | `codename1.arg.android.release` | -| `repositories` | `String[]` | `codename1.arg.android.repositories` | -| `targetSDKVersion` | `int` | `codename1.arg.android.targetSDKVersion` | -| `themeMode` | `AndroidThemeMode.AUTO\|MODERN\|HOLOLIGHT\|LEGACY` | `codename1.arg.and.themeMode` | -| `topDependency` | `String[]` | `codename1.arg.android.topDependency` | -| `useAndroidX` | `boolean` | `codename1.arg.android.useAndroidX` | -| `xapplication` | `String` | `codename1.arg.android.xapplication` | -| `xgradle` | `String[]` | `codename1.arg.android.xgradle` | -| `xpermissions` | `String` | `codename1.arg.android.xpermissions` | - -### `@Desktop` - -| Attribute | Type | Build hint | -| --- | --- | --- | -| `adaptToRetina` | `boolean` | `codename1.arg.desktop.adaptToRetina` | -| `fullscreen` | `boolean` | `codename1.arg.desktop.fullscreen` | -| `height` | `int` | `codename1.arg.desktop.height` | -| `interactiveScrollbars` | `boolean` | `codename1.arg.desktop.interactiveScrollbars` | -| `resizable` | `boolean` | `codename1.arg.desktop.resizable` | -| `titleBar` | `DesktopTitleBar.NATIVE\|CUSTOM\|TOOLBAR` | `codename1.arg.desktop.titleBar` | -| `width` | `int` | `codename1.arg.desktop.width` | - -### `@OnDeviceDebug` - -| Attribute | Type | Build hint | -| --- | --- | --- | -| `android` | `boolean` | `codename1.arg.android.onDeviceDebug` | -| `ios` | `boolean` | `codename1.arg.ios.onDeviceDebug` | -| `iosProxyHost` | `String` | `codename1.arg.ios.onDeviceDebug.proxyHost` | -| `iosProxyPort` | `int` | `codename1.arg.ios.onDeviceDebug.proxyPort` | -| `iosWaitForAttach` | `boolean` | `codename1.arg.ios.onDeviceDebug.waitForAttach` | - -### `@Build` - -| Attribute | Type | Build hint | -| --- | --- | --- | -| `facebookAppId` | `String` | `codename1.arg.facebook.appId` | -| `gcmSenderId` | `String` | `codename1.arg.gcm.sender_id` | -| `nativeTheme` | `NativeThemeMode.MODERN\|LEGACY\|CUSTOM` | `codename1.arg.nativeTheme` | -| `noExtraResources` | `boolean` | `codename1.arg.noExtraResources` | - -### `@Hardening` - -| Attribute | Type | Build hint | -| --- | --- | --- | -| `allowUnhardenedLocalBuild` | `boolean` | `codename1.arg.harden.allowUnhardenedLocalBuild` | -| `controlFlow` | `HardenControlFlow.OFF\|ON` | `codename1.arg.harden.controlFlow` | -| `keep` | `String` | `codename1.arg.harden.keep` | -| `level` | `HardenLevel.OFF\|STANDARD\|AGGRESSIVE\|PARANOID` | `codename1.arg.harden.level` | -| `rename` | `boolean` | `codename1.arg.harden.rename` | -| `strings` | `HardenStrings.OFF\|CONSTANTS\|ALL` | `codename1.arg.harden.strings` | - - - -## Hints with no annotation yet - -These are set in `common/codenameone_settings.properties`. +When in doubt, search the developer guide for the exact key name — there are hundreds of hints and only the ones you actually need are listed here. ## Universal @@ -166,20 +21,48 @@ These are set in `common/codenameone_settings.properties`. | Hint | Effect | | --- | --- | +| `codename1.arg.ios.deployment_target=14.0` | Minimum iOS version. Set to the lowest iOS you actually support. | +| `codename1.arg.ios.teamId=ABCDEF1234` | Apple Developer Team ID; used by `ios-source` Xcode projects for code signing. | +| `codename1.arg.ios.includePush=true` | Include APNs entitlements + frameworks for push. | +| `codename1.arg.ios.add_libs=libsqlite3.0.dylib;libxml2.dylib` | Link extra system libraries. | +| `codename1.arg.ios.pods=Firebase/Core,Firebase/Analytics` | CocoaPods to include. | +| `codename1.arg.ios.pods.platform=14.0` | Pod platform target (must be >= deployment_target). | +| `codename1.arg.ios.pods.sources=https://github.com/CocoaPods/Specs.git` | Custom Pod source repos. | +| `codename1.arg.ios.objC=true` | Allow the iOS port to use Objective-C runtime features the strict mode would block. | +| `codename1.arg.ios.NSCameraUsageDescription=...` | Camera privacy description in `Info.plist`. See *iOS privacy strings* below for the pattern. | +| `codename1.arg.ios.NSLocationWhenInUseUsageDescription=...` | Location (in-use) privacy description. | | `codename1.arg.ios.NSPhotoLibraryUsageDescription=...` | Photo library privacy description. | +| `codename1.arg.ios.NSMicrophoneUsageDescription=...` | Microphone privacy description. | +| `codename1.arg.ios.plistInject=...raw XML...` | Inject raw `……` snippets into `Info.plist` for keys that don't have a dedicated `ios.NS*` hint above. | +| `codename1.arg.ios.glAppDelegateHeader=#import "MyHeader.h"` | Prepend custom imports to the generated AppDelegate. | | `codename1.arg.ios.statusbar_hidden=true` | Hide the iOS status bar. | +| `codename1.arg.ios.beforeFinishLaunching=...` | Native code inserted before iOS's `application:didFinishLaunchingWithOptions:` returns. | +| `codename1.arg.ios.newStorageLocation=true` | Use modern iOS storage paths (recommended for new apps). | | `codename1.arg.ios.wallet.extension=true` | Generate an Apple Wallet issuer-provisioning extension (iOS 14+). See *Apple Wallet issuer provisioning* below. | ## Android | Hint | Effect | | --- | --- | +| `codename1.arg.android.targetSDKVersion=34` | Target SDK in the manifest (drives Play Store acceptance). | +| `codename1.arg.android.min_sdk_version=24` | Minimum Android API level. | +| `codename1.arg.android.buildToolsVersion=34.0.0` | Android build-tools version. Also selects the compile SDK — there is no separate compile-SDK hint. | +| `codename1.arg.android.xpermissions=` | Inject extra `` lines into the manifest. | +| `codename1.arg.android.xapplication=` | Inject XML inside the manifest's `` element. | +| `codename1.arg.android.activity.launchMode=singleTask` | Launch mode for the main activity. | | `codename1.arg.android.statusbar_hidden=true` | Hide the Android status bar. | +| `codename1.arg.android.debug=false` | Whether to build a debug APK in addition to release. | +| `codename1.arg.android.licenseKey=...` | Google Play licensing key. | +| `codename1.arg.android.release=true` | Treat the build as a release (R8/ProGuard on, etc.). | +| `codename1.arg.android.proguardKeep=...` | Extra ProGuard `-keep` rules. | +| `codename1.arg.android.gradleDep=implementation 'com.example:lib:1.0'` | Inject Gradle dependencies. | ## Push notifications | Hint | Effect | | --- | --- | +| `gcm.sender_id=1234567890` | Firebase/GCM sender ID for Android push. | +| `codename1.arg.ios.includePush=true` | Pair with the FCM/APNs setup on the iOS side. | ## iOS privacy strings (`Info.plist`) diff --git a/scripts/initializr/common/src/main/resources/skill/references/native-interfaces.md b/scripts/initializr/common/src/main/resources/skill/references/native-interfaces.md index d06bbc2d55a..75d16576b02 100644 --- a/scripts/initializr/common/src/main/resources/skill/references/native-interfaces.md +++ b/scripts/initializr/common/src/main/resources/skill/references/native-interfaces.md @@ -109,21 +109,18 @@ This step matters because every platform has a different stub layout, naming con The CN1 iOS port runs **without ARC** for these `.m` files (`CLANG_ENABLE_OBJC_ARC=NO`). Don't rely on autorelease-pool magic; retain manually or use static singletons for objects whose lifetime needs to outlive a method call. (This is also true for native code authored in `Ports/iOSPort/nativeSources/`.) -iOS Info.plist privacy strings have **dedicated, compiler-checked names**. Set them with `@IosPrivacy` on the main class rather than hand-writing plist XML: +iOS Info.plist privacy strings have **dedicated build hint names** — set them directly, don't fall back to `ios.plistInject`. The pattern is `ios.=`: -```java -@IosPrivacy( - cameraUsageDescription = "Scan QR codes to pair the device.", - locationWhenInUseUsageDescription = "Find nearby branches near your location.", - microphoneUsageDescription = "Record voice notes." -) -public class MyAppName extends Lifecycle { -} +```properties +codename1.arg.ios.NSCameraUsageDescription=Scan QR codes to pair the device. +codename1.arg.ios.NSLocationWhenInUseUsageDescription=Find nearby branches near your location. +codename1.arg.ios.NSPhotoLibraryUsageDescription=Attach photos to support tickets. +codename1.arg.ios.NSMicrophoneUsageDescription=Record voice notes. ``` -App Store builds reject location, camera, microphone, photo, contacts, etc. without the appropriate descriptions. Use `@Ios(plistInject = "...")` only for raw XML keys that have no dedicated attribute. +App Store builds reject location, camera, microphone, photo, contacts, etc. without the appropriate descriptions. Use `ios.plistInject` only for raw XML keys that don't have a dedicated hint. -If you need a CocoaPod dependency, add it with `@Ios(pods = {"PodName"})`. +If you need a CocoaPod dependency, add `codename1.arg.ios.pods=PodName,...` to `codenameone_settings.properties`. ### Android (Java) @@ -150,15 +147,13 @@ public class GpsBridgeImpl { } ``` -Permissions in the Android manifest are injected with `@Android(xpermissions = ...)`: +Permissions in the Android manifest are injected via `codename1.arg.android.xpermissions`. For example: -```java -@Android(xpermissions = "") -public class MyAppName extends Lifecycle { -} +```properties +codename1.arg.android.xpermissions= ``` -Extra Gradle dependencies go in `@Android(gradleDep = {"implementation 'com.example:lib:1.0'"})`. See `references/build-hints.md`. +Extra Gradle dependencies go in `codename1.arg.android.gradleDep`. See `references/build-hints.md`. ### JavaScript (TeaVM-friendly JS) @@ -304,6 +299,6 @@ navigator.geolocation.watchPosition(function(pos) { - **Method signature mismatch between the interface and the stub** — happens after you edit the Java interface but forget to regenerate. Re-run `mvn cn1:generate-native-interfaces -Dcn1.generateNativeInterfaces.overwrite=true` and re-apply your platform code. - **Returning Java objects** — not supported by the bridge marshaler. Return primitives, `String`, `byte[]`, or `PeerComponent` only. - **`PeerComponent` on iOS without ARC** — peer-component implementations can dangle if you treat the bridge like an ARC-managed Swift method. Retain natively, or wrap returned views in a static holder. -- **Permissions / Info.plist** — the build server happily accepts a native interface that calls a privacy-protected API, but the App Store / Play Store reject it. Set `@IosPrivacy(...)` for the plist strings and `@Android(xpermissions = ...)` for the manifest (see `references/build-hints.md`). +- **Permissions / Info.plist** — the build server happily accepts a native interface that calls a privacy-protected API, but the App Store / Play Store reject it. Set `codename1.arg.ios.plistInject` and `codename1.arg.android.xpermissions` (see `references/build-hints.md`). - **Forgetting `isSupported()` return** — defaults to `false`, so the Java side thinks the bridge isn't available. Always override. - **`NativeLookup.create()` returns null in the simulator only** — usually means the `javase/` impl class is missing or in the wrong package. diff --git a/scripts/initializr/common/src/main/resources/tweet-src.zip b/scripts/initializr/common/src/main/resources/tweet-src.zip index 431c10e5a9a8da29900812fcc29903d5d2366bcc..add354048e9050391925ac291ca7400f911061c2 100644 GIT binary patch delta 5747 zcma)A2{@E%6#i%ViLobJV`7k^ER8#KE2-P!B3Y)SlqNGW)~p#zQ;H&W)2QJVT}unf za*0+Vp>A1IRF))_BBZqH{{Q?lGVaWDd3bo9^PcxT=R4my=bNF98MXa0*s?ecJrKYH z-qbLv@j8kWfEoHbi2?tFhT{Mf_B{#!u*v`cumf=#bEWu}9sz!cHBqtJLzuNJ4o~8F zg>C8KRKFd*bg>K$wiqm@$OBYE;W^rLP!0bRIe4Dk>>WV?VGJ5QC|JCtLL48#evX^< z!@I}3{{&l%l_5(a?sXAEQE|FY=Q&*}-r?2hN6>Vn2@y3ZNmRP>=TMbR|#xgU@ zFw%0D#>y0YDExa>&{33Cgi15-B9qR@aW;9NWdq;9uI`m5S-FNyP9j%wS*eZOvaLjJMZ;YC4d6l2ro!Tz(~8V3!u-xFS` z9ne2RUfjf}t4ufYQrnVjfB8J`*2$C5{-RV~`&)OFbGS(pp9<3Mm^{b+BG zmTmQ$RSzv6OV=AcD{#Onc+s!M7;HW}l9|%{W+2|mwsN60-JINYPkm%oxAYL_Z=ZC} z0~!_bM~IL1+@Yn%G>Q_emSGa9$3BHvqkj9O!GbtIx3!!7 zG$!gV8j%{}n(n2U+m+Y)DXQF&EKw-c>-{OW=ud5XU{>yELgGH%2()e@ZH}@+@gcP& zYW1at7*nZ6Es~bAsoCS^3x|~BleIsx>(3o>YjskSy?l9##kzzLm2*$>+ojUYu;s+q z4|)x!l~|QUJ)1imbW|MdOI6XALw~)uXHy>gpZn)E_hK3A-fXCeFc~#?tx@K+#_^A^ zvwh^q3mYT1?(4PAd89?9Cv%cqpPM;%!?A;D&+V!odlUOUQU{Kl z&Q5!~S!Sz!SjRmB%BdxX)53RFp3HJ;Z`YBGPIX>=-hmZd?%~_q{~)cxo@_HuPk!%b zkHR}kIp6gk7#wIwD~Mrh?*N zhAk>*ef(|8Zw4-&E;LJO=f1n@FKE*)wQ^`4Dqf22M(w?&GuHKzQ!(4pHPB^u^6+l; z7Y2iKH)cI$J=xi@EUVS0c$f3eM+LV6G?WT0;*G8QnMCQ3PZU~yu=x&_rFrB-zZcX-M%df6w>G?s zZjP$DVRp=J;9`cI&OmXXRUz$r(o1PJzIIx|a>Gr=j{}ojimtcSNX%URx^k)FoI|PZ zdecAZp?xvL{Q0)%ZB<_>?G?w=eOJuOCI%S&zFXG!Igq37XIffmqMZF{bi-JFU}=EO zT$*HZ=9#;f&ZMwhqhfLr4hEiodNWh^rM1Kt#&JoX*s6T3(hYhw>uc*P&Uwb`sorIs z>Ka=%ljE{a!g9T|@2cy!Zl|{fT=CC~Y+>c#dR4-$HD3B@xyzS+lls#4;6hi~Ys`yI zzp^X(E}EgWp=tGhbzBQ$slD8l^;3yE{Gl)CKj8&zT6Yq3Ke;?o9-9hXO z$-Hxi%r^IvyythNXWYNx(ym(;T_5Ua{&~^z`0CN1zW3}qloRV4YiBfOM_Rl~FClEN zPF|Kwb6%X0qPf2P6l+dPt>c=M&Jbou9Fr}#uscDyDLf{JG{0pu+DztlqT|Tdq~h+R zuO+#98iB)wx^qYR`xfo5vK@D;xlOuy+@9@qB#gM2YCS(SftXs7S`|T+*=1O0aO{0c z%PReKJ;c7&IJ?xKt@xcqhtWU1~1)>O4QADnj}ap=wC5Si`Yt~<5XKMO6-wH|JhE@9@~ ztg^zOuby6JrT=O9oZ7|_$F;uIjTi6CYSs1p^uDaXgpU>-<;Df{4{*yvtf@;AoS@&|9m+`%cCLJRfhM z<|ifwq_Qeg#?pe7R@3ZCYI4Y=tLut}ADeGNhjfnXmgf`%c2%=}_s>4(_>d7aUuVua z!aaf{?rT(aFPKvFqB3Yxi%M&;E!wRT74L4CjEnVj|HmnAt)jl{m`7(qjkl^H+t~Iy znq4`YKZo^#=|N!(5Bg6A_xA|^+f0Ab=-eh`a69N}h z=sS55#4JCyasuW*+koJ02Hu;d0Xt)j*US8&d$SGt532?Ms?cm!@Z#?`q85u2Mx)RQ z5GaFHT{tnmg)CYyjWpyK0RBvIbmRWP`-O*SAUJP(C^?nPsE2;0Zgo$M;lqIKjx#`6 z@w?6(lj0w?I6^RuD-8DB_g~M&5~fZWh`A2p_M&hJ9266!fiZ>DK4pLl5K|F8%Q9rJ z%2}MQP?Qql*@)ohD=s@_#4ywsc<+J&)ZyIl2Ic;be;gB~fH6bTr^6xmfJ*-dRZ)TF zsnX%>_Te+ZK?}(aFA`M`%mk*>2b2-d{mj$eEo5 zSvL9dk3d9SmVGY3*7AJna3IanKNB%rgdLgyK&p8~CQ6A?3HHfEkWe0Fe&%1x4Qz(D zW&ROQ07OK<(WQd?!h{c3flnaH{tFd6#16)(A^n0L91t4xvzC=K2X6-TmI+%L-7mnC zPNx}#^XgRuSCJb-niIGk)G!7Cs8evi6o7$c@E^Pv8i>5mrrcxBkOzn-NeO_K@CF}gX(JQC?YVBT7ul<00}5Af;jmp zCqnR2+5{GQn*!N;m9Mkx;mZxf=^b$BYS!xe+@!5f8WzW%8=6dH@*;MGE0ZXl(2MZuK1 zK_A^gDK#N*6Ds6Xkz(K!6kz$)fShc@8BU`wNJ36kNbya+VsH~4|ByIvAA%mm0DzG| znxCKJ$%mH`aq`{$rX*TB??>RgYak>7&b6sH8)pzn7`W7P1U!sxw$hlEzr38awd zIpI^0uC6>Jyt(i>g(2a-Z6?jj5Yir;HiDCO67ER}LzZs?36#mDe_Q}3xXTD{#}2k2 U^g$#5%!a;u0SSs*VXzG=c#ck9KF&@~j!HpZo&}$+P;Tqu5aMfK zFn7DNcFOc-32{C&$Y;Gd_;@MDiVF)ae|HM9^YZm{0_Tw;Fc@71EehEnH?p6B56-A{ z^k6sv&Vtylg>d1#QMkq$)|?#O7R^TG08LkvL|zv<7QsQTV+naRSUn!j>lQ)DJUhVx zq>zmLhEvILrzUE^G*3vuU_?%^VmB0y)^0iO)1!%JCYMJfA~*Pdc=e?p5z^&(t^P4R zs57R5^1k>mTv1hH=lU$!t;I%h%q8b7S_z-_mztfcA2W4ym(p*-j#Q?ppS!o+XjAzVveu5a5#DYe zR7K8&Mh=AB3S)ROH|P6$znY}he>RY(eKBz|C^<|x9;Kch(J#%IGW2bXeicyt!!pby zOGPGL{kQtireta`?8vT?2z6my|HZ@uInxx}-zo0TVMyeNz{G`y6X(ef3ATuz%9@^) z{6Ms&dADGRcjvbKQ*S)tb0st@_HTS`si!Kqbmnn#Quf<3$@GryZnT!{V3I_{6Pjs4 zTdWxUAo;bu+NrpKEw??+GN%H(z1%~+o+rE>rau`h87lI*TlRImyrW#Vk0K+vSKe$Q zDNemLOD+1I_?g2=%)%r=G|p1x#)GZd=@QanH^k)T?C?~j)?4k zR1gsksCjA0e>ifjg|8xk@^Ed6ak}LM-9sH-8EKZbDY11o1-pKeMXcUt}P+^Hb83hbc7MY@UzyTNNmab^<^2&L)OQKb60(5`CfdOfxQ?;;FdHan#qtR z&^c23vFK;l%d%i*x?v>0f&*oT<;#-Vg^m#u;>;Us#lf1MP4dPbQ%h_84D)C17qp)L z-RRUa$zK>_*zrBNu8^&uN93e z&yU+b3rj-AYyV89B%&zQAM)uP z4J?=(+aq@=+c6X)X^_X;nlh^w&{HXzcB!=4x24)Z#;2dI9OFbKl&-;AQ733PMyc_< ziEq@l;pXNFooW9a;>?lwFdzB3So0-c_1`~SXX|7{nX17=2~h)LY+apMNE=PjuQi;~ zrUuBkt)R?|+PPExk?{|{@dLj-2b!orEaHJn+D?RpD8fl0B1}e5N zb~R5b_#+OPC!A}qYzj0xT@^!1iV>7BkjP^?H=cN~NU{mrNwh0@6FH~*1}4;&SsQw6 zemdkMTzFwnGVZp(b!6K7Yl`BvJ^r! zt?a_DMWiV2Am&z4#p{4l*=*W}rG=5N5rG%y-S9h3pSe{s7E{ruDSSBJrFKryh`L=z zzxmi9^st;AmA=hekMPwz8EI>(!g2C@2vv|o)UELe_;PKJY%bQ*?X(iN}JjNqy zQsu@a>!2ocMx^AvvM*HR(#z9Lp?9w2t)i_Ro6YkZlEy@p zmHL|0ssirtr#m*O@9`i$RPwn~gfVoz5t&zG;^dg6R#KOK&HA0yfqvr}N0aNt`H{D+ zj_p1gd7L@&@x#Lb4B2amoY7A^b~EUFl|p~}ZUYI`}Qdh}a9%?^^nC_<96^*(D&gI2KqN{KR z7OhfC$gXqw4?oT0{gS1}Pw)Nbu80y%cL6wJg_1zZaO8z80Zo@Da1;gImO#k6MJVCr z1+|ZzBPc^~EExnoo^^8yq_Px@G7eHaf2JTAvr-*lW8|v3Kn{;63Wlkzz87!n3Sb<9)RZ1Eq~j4bdCj}S+yg08~rWp6a_ z7uX0P)~5pco^U*ekc8yHAV9^H>h{_sJlqxVNOT+?$ zZYY8Tq-og$0zY#>=xDI4Wg(IKFM$uUOagNyFGQ5$Ury7-uw^b3LJ*X|oB@UK<3Nxg z<~28kilkqFi*n4_73D7D&_R{VN>xyn>jnn-krIMJ$N?iRe`2$ZZ8!iVq5%)rRjBd? zmKhzC)WGy=@Ub`>1O>JtJ2m$D{|}wb$^{BI8D1pCE*4VVP^K%FqLRcy!TX8kma9xy zSuXPrg}|zG1J8;|rxn?kmbD**m1hR8wERM|tYx4}&K}S@X|G_g_VuyHvK*lkZ|~Q(7&R8&8f=JVHV-0P9t(thp~1clLbox4W55ovKM8L4bZ{0bo&lK)D}EinGb=jC1~Bq3q5DX!x&! z)1R=7Ic*HcUQMXUg9ysdE=W^X8MmASDyb+U;7dhucM|YEff8MjK!+_vQUfpntE{u^ eAV3a)8s7$UQxy2?1cQ;l&rvK4cG(_kP5%Wz$mbvc From 8b505657e8c9fdac4c2e71496aaa3b95e1793928 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 05:32:17 +0300 Subject: [PATCH 009/115] Annotate inside the integration test, not in the archetype The test asserted the generated project already imports the build hint annotations, which was true only while the archetype template carried them. Now that generated projects stay property-backed until a release ships the package, the test adds the annotations to the generated main class itself. It annotates only hints the template does not declare -- ios.pods, ios.teamId, desktop.width, android.installLocation -- because setting one in both places is a build error, which the last section of the test covers deliberately. It also now asserts the reverse: a hint the properties file declares must not appear in the emitted resource, so the two sources stay separate. Co-Authored-By: Claude Opus 5 (1M context) --- .../build-hint-annotations-test.sh | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/maven/integration-tests/build-hint-annotations-test.sh b/maven/integration-tests/build-hint-annotations-test.sh index 49d14ede752..b5b977b4523 100755 --- a/maven/integration-tests/build-hint-annotations-test.sh +++ b/maven/integration-tests/build-hint-annotations-test.sh @@ -31,15 +31,18 @@ chmod 755 mvnw MAIN=common/src/main/java/com/example/MyApp.java SETTINGS=common/codenameone_settings.properties -echo "--- the generated project must already use annotations ---" +# The archetype deliberately still ships its hints as properties: generated +# projects are pinned to a released Codename One whose core has no +# com.codename1.annotations.buildhints, so a template carrying them would not +# compile. The annotations move there in a follow-up. Add them here instead, and +# only for hints the template does NOT declare -- setting one in both places is +# a build error, which is covered separately at the end of this test. +echo "--- annotate the generated main class ---" +perl -0pi -e 's/^import com\.codename1\.system\.Lifecycle;/import com.codename1.annotations.buildhints.*;\nimport com.codename1.system.Lifecycle;/m' $MAIN +perl -0pi -e 's/^public class MyApp extends Lifecycle \{/\@Ios(pods = {"Alamofire", "SwiftyJSON"}, teamId = "ABCDE12345")\n\@Android(installLocation = InstallLocation.INTERNAL_ONLY)\n\@Desktop(width = 1280)\npublic class MyApp extends Lifecycle {/m' $MAIN grep -q "com.codename1.annotations.buildhints" $MAIN \ - || { echo "FAIL: the archetype's main class does not import the build hint annotations"; exit 1; } -grep -q "^codename1.arg.ios.newStorageLocation" $SETTINGS \ - && { echo "FAIL: ios.newStorageLocation should have moved to @Ios, not stayed in $SETTINGS"; exit 1; } - -echo "--- add a hint of each shape ---" -perl -0pi -e 's/\@Ios\(/\@Ios(pods = {"Alamofire", "SwiftyJSON"}, teamId = "ABCDE12345", /' $MAIN -grep -q 'pods = {"Alamofire"' $MAIN || { echo "FAIL: could not patch $MAIN"; exit 1; } + || { echo "FAIL: could not add the import to $MAIN"; exit 1; } +grep -q 'pods = {"Alamofire"' $MAIN || { echo "FAIL: could not annotate $MAIN"; head -40 $MAIN; exit 1; } echo "--- process-classes must emit the hints ---" ./mvnw -B -q -pl common process-classes @@ -53,10 +56,16 @@ check() { # rather than the constant name, and an unset attribute writes nothing at all check "codename1.arg.ios.pods=Alamofire,SwiftyJSON" check "codename1.arg.ios.teamId=ABCDE12345" -check "codename1.arg.ios.themeMode=modern" -check "codename1.arg.desktop.titleBar=native" +check "codename1.arg.desktop.width=1280" +# The enum is written as the value the builder compares against, not the Java +# constant name -- INTERNAL_ONLY would be silently unrecognized. +check "codename1.arg.android.installLocation=internalOnly" grep -q "codename1.arg.ios.objC" $EMITTED \ && { echo "FAIL: an attribute nobody set must not be written"; exit 1; } +# A hint the properties file declares must not appear here: the annotations do +# not set it, and the two sources stay separate. +grep -q "codename1.arg.desktop.titleBar" $EMITTED \ + && { echo "FAIL: $EMITTED should only carry what the annotations declare"; exit 1; } echo "--- the hints must reach the build request ---" # "Build target not supported" is thrown after the merged settings file is From b2e0e86f14bc363a424b5a7aeda0a29e2224d72e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 07:33:44 +0300 Subject: [PATCH 010/115] Four migration and Settings defects from review Kotlin string interpolation. `quote()` escaped nothing for `$`, so migrating a Kotlin main class turned any hint value containing one into an interpolated string -- an `android.gradleDep` of `implementation 'x:y:$version'` either fails to compile as an unresolved reference or silently resolves to something else. The target language is threaded into the quoting and `\$` is emitted for Kotlin only, since Java has no such construct. Imports in a default-package source. With no package declaration and no existing import, `head.indexOf("package ")` returned -1 and the arithmetic put the import at the first newline in the file -- inside the copyright comment. The project was then left with unresolved annotations and its properties entries already deleted. The class declaration is the anchor in that case. Aliases in the Settings tool. Ownership was looked up by exact name, so with `@Android(themeMode = ...)` owning `and.themeMode`, the row for its deprecated alias `cn1.androidTheme` still offered Add -- creating the second declaration of one effective setting that the next build refuses through the alias conflict check. Both sides of the lookup are canonicalised now. Credential masking. The scraper this catalog replaced inferred SECRET from names containing password, secret or token, and the Settings field masks on that type. Classifying them as STRING rendered a stored certificate password as visible text. All five -- codename1.mac.certificatePassword, macNative.notarize.password, windows.msix.password, windows.signing.password and facebook.clientToken -- are SECRET again, with a test that holds every future credential-shaped name to it. Co-Authored-By: Claude Opus 5 (1M context) --- .../_generated-build-hints.adoc | 10 +++--- .../build/shared/BuildHintsApple.java | 2 +- .../build/shared/BuildHintsDesktop.java | 4 +-- .../build/shared/BuildHintsExternal.java | 2 +- .../build/shared/BuildHintsGeneral.java | 2 +- .../maven/MigrateBuildHintsMojo.java | 34 +++++++++++++++---- .../MigrateBuildHintsPropertyParsingTest.java | 21 ++++++++++++ .../settings/CodenameOneSettings.java | 12 +++++-- .../settings/BuildHintCatalogTest.java | 32 +++++++++++++++++ 9 files changed, 100 insertions(+), 19 deletions(-) diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc index d23082316d4..1173af133f9 100644 --- a/docs/developer-guide/_generated-build-hints.adoc +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -1387,7 +1387,7 @@ |Mac Native cloud builds only. Path to the `.p12` file containing the Mac signing certificate(s) — _Mac App Distribution_ (3rd Party Mac Developer Application) for App Store builds, _Developer ID Application_ for Developer ID builds, or both bundled into the same P12 when `macNative.distribution=both`. Not interchangeable with the iOS distribution certificate. Required for cloud Mac builds. |codename1.mac.certificatePassword -|string +|secret |_(none)_ |_(none)_ |Mac Native cloud builds only. Password to unlock the P12 referenced by `codename1.mac.certificate`. Required for cloud Mac builds. @@ -1417,7 +1417,7 @@ |The application ID for an app that requires native Facebook login integration, this defaults to null which means native Facebook support shouldn't be in the app |facebook.clientToken -|string +|secret |_(none)_ |_(none)_ |The client token for an app that requires native Facebook login integration, this is required if the facebook.appId is set. @@ -2941,7 +2941,7 @@ | |macNative.notarize.password -|string +|secret |_(none)_ |_(none)_ | @@ -3115,7 +3115,7 @@ | |windows.msix.password -|string +|secret |_(none)_ |_(none)_ | @@ -3163,7 +3163,7 @@ | |windows.signing.password -|string +|secret |_(none)_ |_(none)_ | diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java index d606f485d0a..b8abef16dd3 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java @@ -168,7 +168,7 @@ static void register(List h) { h.add(new Hint("macNative.notarize.password") .group(HintGroup.MAC_NATIVE) - .type(HintType.STRING) + .type(HintType.SECRET) .platform("mac") .consumedBy("MacNativeBuilder")); diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java index aee3bf9532d..b4c52de1b8e 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java @@ -256,7 +256,7 @@ static void register(List h) { h.add(new Hint("windows.msix.password") .group(HintGroup.WINDOWS) - .type(HintType.STRING) + .type(HintType.SECRET) .platform("windows") .consumedBy("WindowsNativeBuilder")); @@ -316,7 +316,7 @@ static void register(List h) { h.add(new Hint("windows.signing.password") .group(HintGroup.WINDOWS) - .type(HintType.STRING) + .type(HintType.SECRET) .platform("windows") .consumedBy("WindowsNativeBuilder")); diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsExternal.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsExternal.java index 30c02b89a6e..3cdc2f5a517 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsExternal.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsExternal.java @@ -149,7 +149,7 @@ static void register(List h) { h.add(new Hint("codename1.mac.certificatePassword") .group(HintGroup.GENERAL) - .type(HintType.STRING) + .type(HintType.SECRET) .platform("general") .external() .doc("Mac Native cloud builds only. Password to unlock the P12 referenced by " diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java index c85cf6117a0..ef61db1fecb 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java @@ -181,7 +181,7 @@ static void register(List h) { h.add(new Hint("facebook.clientToken") .group(HintGroup.GENERAL) - .type(HintType.STRING) + .type(HintType.SECRET) .platform("general") .consumedBy("AndroidGradleBuilder") .doc("The client token for an app that requires native Facebook login integration, this is " diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index e1ea5a01430..2dc4d16923a 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -336,7 +336,7 @@ String toSourceLiteral(BuildHints.Hint hint, String value, boolean kotlin) { case STRING_LIST: { String sep = hint.separator(); if (sep == null || sep.length() == 0) { - return quote(v); + return quoteFor(v, kotlin); } String[] parts = v.split(java.util.regex.Pattern.quote(sep), -1); StringBuilder sb = new StringBuilder(kotlin ? "[" : "{"); @@ -349,12 +349,12 @@ String toSourceLiteral(BuildHints.Hint hint, String value, boolean kotlin) { if (written++ > 0) { sb.append(", "); } - sb.append(quote(t)); + sb.append(quoteFor(t, kotlin)); } return sb.append(kotlin ? ']' : '}').toString(); } default: - return quote(v); + return quoteFor(v, kotlin); } } @@ -373,7 +373,16 @@ static String enumConstant(String wire) { return out.length() > 0 && Character.isDigit(out.charAt(0)) ? "V" + out : out; } - private static String quote(String s) { + /** + * Renders a value as a string literal for the target language. + * + *

Kotlin interpolates {@code $} inside a string, and hint values contain + * it: an {@code android.gradleDep} of + * {@code implementation "com.x:y:${'$'}{version}"} would either fail to + * compile as an unresolved reference or silently resolve to something else. + * Java has no such construct, so the escape is emitted only for Kotlin.

+ */ + static String quoteFor(String s, boolean kotlin) { StringBuilder sb = new StringBuilder("\""); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); @@ -383,6 +392,9 @@ private static String quote(String s) { case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; + case '$': + sb.append(kotlin ? "\\$" : "$"); + break; default: sb.append(c); } } @@ -456,9 +468,17 @@ private void insertAnnotations(File source, String annotations, String simpleNam int eol = head.indexOf('\n', lastImport + 1); head = head.substring(0, eol + 1) + importLine + "\n" + head.substring(eol + 1); } else { - int pkgEnd = head.indexOf('\n', head.indexOf("package ")); - head = head.substring(0, pkgEnd + 1) + "\n" + importLine + "\n" - + head.substring(pkgEnd + 1); + // No existing import. Anchor on the package declaration, and when the + // class is in the default package anchor on the class declaration + // instead: indexOf("package ") returns -1 there, and the old + // arithmetic then put the import at the first newline in the file, + // which is inside the copyright comment. The result compiled to + // nothing useful while the properties entries had already been + // deleted. + int pkg = head.indexOf("package "); + int anchor = pkg >= 0 ? head.indexOf('\n', pkg) + 1 : head.length(); + head = head.substring(0, anchor) + (pkg >= 0 ? "\n" : "") + + importLine + "\n" + (pkg >= 0 ? "" : "\n") + head.substring(anchor); } write(source, head + annotations + tail); } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 136d6faae4f..159ebd3a080 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -85,6 +85,27 @@ public void commentsAndBlanksDeclareNothing() { assertNull(MigrateBuildHintsMojo.propertyKeyOf(" ")); } + /// Kotlin interpolates `$` inside a string; Java does not. A hint value + /// carrying one -- a Gradle snippet such as `${'$'}{version}` -- would either + /// fail to compile as an unresolved reference or resolve to something else. + @Test + public void dollarSignsAreEscapedOnlyForKotlin() { + assertEquals("\"implementation 'x:y:\\$version'\"", + MigrateBuildHintsMojo.quoteFor("implementation 'x:y:$version'", true)); + assertEquals("\"implementation 'x:y:$version'\"", + MigrateBuildHintsMojo.quoteFor("implementation 'x:y:$version'", false)); + } + + /// The class declaration is the only safe anchor in a default-package source: + /// there is no `package` line, and the old arithmetic put the import at the + /// first newline in the file, which is inside the copyright comment. + @Test + public void aDefaultPackageSourceStillGetsAUsableAnchor() { + String src = "/*\n * Copyright\n */\npublic class MyApp {\n}\n"; + assertEquals(src.indexOf("public class MyApp"), + MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); + } + @Test public void aValueOnlyLineHasNoSeparator() { assertEquals("bare", MigrateBuildHintsMojo.propertyKeyOf("bare")); diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index eafe3a95b44..880a7f61bb1 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -659,7 +659,13 @@ private void animatePage() { private Component hintRow(BuildHintMetadata meta) { Container row = new Container(BoxLayout.y()); row.setUIID(uiid("SettingsRow")); - String ownedBy = annotationOwnedHints.get(meta.name()); + // Look the hint up by its canonical name: a deprecated alias configures the + // same effective setting, so cn1.androidTheme is owned whenever an + // annotation owns and.themeMode. Matching on the exact name left the alias + // row offering Add, which would create the second declaration the next + // build refuses through the alias conflict check. + String ownedBy = annotationOwnedHints.get( + com.codename1.build.shared.BuildHints.canonicalName(meta.name())); boolean active = hasBuildHint(meta.name()); String value = active ? settings.getBuildHint(meta.name()) : ""; BuildHintType effectiveType = effectiveHintType(meta, value); @@ -2107,7 +2113,9 @@ private java.util.Map loadAnnotationOwnedHints() { } int eq = t.indexOf('='); if (eq > originPrefix.length()) { - out.put(t.substring(originPrefix.length(), eq).trim(), t.substring(eq + 1).trim()); + String hint = t.substring(originPrefix.length(), eq).trim(); + out.put(com.codename1.build.shared.BuildHints.canonicalName(hint), + t.substring(eq + 1).trim()); } } } catch (Exception ex) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 55741ec3ba8..825cfd4d7dd 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -125,6 +125,38 @@ public void everyAnnotatedHintNamesItsAttribute() { assertTrue(annotated > 50, "expected the curated set, got " + annotated); } + /** + * The Settings field for a credential is masked from its type. The catalog + * that replaced the old name-matching scraper has to keep classifying these + * as SECRET, or a stored certificate password renders as visible text. + */ + @Test + public void credentialHintsStayMasked() { + BuildHintCatalog catalog = BuildHintCatalog.load(); + for (BuildHintMetadata h : catalog.all()) { + String n = h.name().toLowerCase(); + if (n.contains("password") || n.contains("secret") || n.contains("token")) { + assertEquals(BuildHintType.SECRET, h.type(), + h.name() + " holds a credential and must render masked"); + } + } + } + + /** + * A deprecated alias configures the same effective setting as its target, so + * the Build Hints UI has to treat it as annotation-owned too -- otherwise its + * row still offers Add and creates the duplicate the next build refuses. + */ + @Test + public void aliasesResolveToTheirCanonicalName() { + assertEquals("and.themeMode", + com.codename1.build.shared.BuildHints.canonicalName("cn1.androidTheme")); + assertEquals("nativeTheme", + com.codename1.build.shared.BuildHints.canonicalName("cn1.nativeTheme")); + assertEquals("ios.pods", + com.codename1.build.shared.BuildHints.canonicalName("ios.pods")); + } + @Test public void searchStillMatchesOnNameAndDescription() { BuildHintCatalog catalog = BuildHintCatalog.load(); From fd05dfa07ea8037982ec7797a404c59d9ea3177f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:19:23 +0300 Subject: [PATCH 011/115] Require the process-annotations binding on the module that owns the main class The guard accepted an execution anywhere in the reactor, but ProcessAnnotationsMojo scans only the output directory of the module it is bound to. A binding on a platform or utility module therefore never sees the main class that common compiles, and the migration would still delete the working properties and leave annotations nothing ever reads -- the exact failure the guard was added to prevent, one level in. It now resolves the module whose base directory is the Codename One project directory, which is where the main class lives and where findMainClassSource looks, and requires the binding there. An execution bound to phase `none` is declared but never runs, so it no longer counts either. The refusal names that module and says explicitly that binding the goal elsewhere in the reactor does not help, since that is the mistake being made. Verified against a real project: gamebuilder passes with the binding on common, and is refused when it is moved to the javase module. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 77 ++++++++++++++++--- .../MigrateBuildHintsPropertyParsingTest.java | 33 ++++++++ 2 files changed, 98 insertions(+), 12 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 2dc4d16923a..3d704ee6c13 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -105,10 +105,14 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException // binding deletes working properties and replaces them with annotations no // goal ever reads, so the hints disappear from the build silently. if (!processAnnotationsIsBound()) { - throw new MojoFailureException("This project does not run the cn1 process-annotations " - + "goal, so build hint annotations would never be turned back into the " - + "codename1.arg.* pairs the builders read, and migrating would silently drop " - + "them.\n\nAdd it to the common module's POM first:\n" + File owner = getCN1ProjectDir(); + throw new MojoFailureException("The module that holds the main class" + + (owner == null ? "" : " (" + owner + ")") + + " does not run the cn1 process-annotations goal, so build hint annotations " + + "would never be turned back into the codename1.arg.* pairs the builders " + + "read, and migrating would silently drop them. The goal only scans the " + + "module it is bound to, so binding it elsewhere in the reactor does not " + + "help.\n\nAdd it to that module's POM first:\n" + " \n" + " cn1-process-classes\n" + " process-classes\n" @@ -239,24 +243,73 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException * lives in the common module.

*/ private boolean processAnnotationsIsBound() { + return moduleOwningTheMainClass() != null; + } + + /** + * The reactor module that both holds the main class and runs + * {@code process-annotations} over it, or null. + * + *

Checking the reactor as a whole is not enough. {@code + * ProcessAnnotationsMojo} scans only the output directory of the module it is + * bound to, so a binding on a platform or utility module never sees the main + * class that {@code common} compiles. The migration would then delete the + * properties and leave annotations nothing ever reads.

+ * + *

The owning module is the one whose base directory is the Codename One + * project directory -- the directory holding + * {@code codenameone_settings.properties}, which is also where + * {@link #findMainClassSource} looks.

+ */ + private org.apache.maven.project.MavenProject moduleOwningTheMainClass() { + File projectDir = getCN1ProjectDir(); + if (projectDir == null) { + return null; + } java.util.List projects = reactorProjects; if (projects == null || projects.isEmpty()) { projects = java.util.Collections.singletonList(project); } for (org.apache.maven.project.MavenProject p : projects) { - java.util.List plugins = p.getBuildPlugins(); - if (plugins == null) { + if (p.getBasedir() == null || !sameDirectory(p.getBasedir(), projectDir)) { continue; } - for (org.apache.maven.model.Plugin plugin : plugins) { - if (!"codenameone-maven-plugin".equals(plugin.getArtifactId())) { + return bindsProcessAnnotations(p) ? p : null; + } + return null; + } + + private static boolean sameDirectory(File a, File b) { + try { + return a.getCanonicalFile().equals(b.getCanonicalFile()); + } catch (IOException ex) { + return a.getAbsoluteFile().equals(b.getAbsoluteFile()); + } + } + + /** + * Whether this module runs {@code process-annotations} in a real phase. + * + *

An execution bound to {@code none} is declared but never runs, which for + * this purpose is the same as not being declared at all.

+ */ + static boolean bindsProcessAnnotations(org.apache.maven.project.MavenProject p) { + java.util.List plugins = p.getBuildPlugins(); + if (plugins == null) { + return false; + } + for (org.apache.maven.model.Plugin plugin : plugins) { + if (!"codenameone-maven-plugin".equals(plugin.getArtifactId())) { + continue; + } + for (org.apache.maven.model.PluginExecution e : plugin.getExecutions()) { + if (e.getGoals() == null || !e.getGoals().contains("process-annotations")) { continue; } - for (org.apache.maven.model.PluginExecution e : plugin.getExecutions()) { - if (e.getGoals() != null && e.getGoals().contains("process-annotations")) { - return true; - } + if ("none".equalsIgnoreCase(String.valueOf(e.getPhase()))) { + continue; } + return true; } } return false; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 159ebd3a080..ce1a4964cfd 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -22,9 +22,14 @@ */ package com.codename1.maven; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.project.MavenProject; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNull; /// Covers the properties parsing in `MigrateBuildHintsMojo`. @@ -106,6 +111,34 @@ public void aDefaultPackageSourceStillGetsAUsableAnchor() { MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); } + /// process-annotations scans only the output of the module it is bound to, so + /// a binding on a platform or utility module never sees the main class that + /// the common module compiles. Accepting one would let the migration delete + /// the properties and leave annotations nothing reads. + @Test + public void onlyAnEnabledExecutionOnTheOwningModuleCounts() { + assertTrue(MigrateBuildHintsMojo.bindsProcessAnnotations( + moduleBinding("process-annotations", "process-classes"))); + assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( + moduleBinding("css", "process-classes"))); + // Declared but never run is the same as absent for this purpose. + assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( + moduleBinding("process-annotations", "none"))); + } + + private static MavenProject moduleBinding(String goal, String phase) { + PluginExecution e = new PluginExecution(); + e.setPhase(phase); + e.addGoal(goal); + Plugin plugin = new Plugin(); + plugin.setGroupId("com.codenameone"); + plugin.setArtifactId("codenameone-maven-plugin"); + plugin.addExecution(e); + MavenProject p = new MavenProject(); + p.getBuild().addPlugin(plugin); + return p; + } + @Test public void aValueOnlyLineHasNoSeparator() { assertEquals("bare", MigrateBuildHintsMojo.propertyKeyOf("bare")); From 3037e76565002641bee2a0d8e40c02c89c4e2cef Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:52:13 +0300 Subject: [PATCH 012/115] Require an execution that can actually see compiled classes Being declared in a real phase was still not enough. ProcessAnnotationsMojo returns immediately when `skip` is set, and again when its output directory does not exist -- which is every phase before `compile`. An execution configured `true`, or bound to `generate-sources`, therefore emits no annotation resource at all, and the migration would delete the working properties and leave nothing behind. The guard now requires the execution to be unskipped and bound at or after `compile`, taking an absent phase as the goal's own default of `process-classes`. Skip is read from both the execution and the plugin configuration. Verified end to end: gamebuilder proceeds normally, and is refused once its execution carries true. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 50 +++++++++++++++++-- .../MigrateBuildHintsPropertyParsingTest.java | 40 +++++++++++++-- 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 3d704ee6c13..5cccbd6c5f6 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -288,10 +288,15 @@ private static boolean sameDirectory(File a, File b) { } /** - * Whether this module runs {@code process-annotations} in a real phase. + * Whether this module runs {@code process-annotations} somewhere it can + * actually see compiled classes. * - *

An execution bound to {@code none} is declared but never runs, which for - * this purpose is the same as not being declared at all.

+ *

Being declared is not enough. {@code ProcessAnnotationsMojo} returns + * immediately when {@code skip} is set, and again when its output directory + * does not exist -- which is the case for every phase before {@code compile}. + * An execution that is skipped, or bound to {@code generate-sources}, emits + * no annotation resource at all, so migrating against it would delete the + * working properties and leave nothing behind.

*/ static boolean bindsProcessAnnotations(org.apache.maven.project.MavenProject p) { java.util.List plugins = p.getBuildPlugins(); @@ -306,7 +311,10 @@ static boolean bindsProcessAnnotations(org.apache.maven.project.MavenProject p) if (e.getGoals() == null || !e.getGoals().contains("process-annotations")) { continue; } - if ("none".equalsIgnoreCase(String.valueOf(e.getPhase()))) { + if (!phaseSeesCompiledClasses(e.getPhase())) { + continue; + } + if (isSkipped(e.getConfiguration()) || isSkipped(plugin.getConfiguration())) { continue; } return true; @@ -315,6 +323,40 @@ static boolean bindsProcessAnnotations(org.apache.maven.project.MavenProject p) return false; } + /** + * The default lifecycle from {@code compile} onward -- the phases by which + * {@code target/classes} exists. + */ + private static final java.util.List PHASES_WITH_CLASSES = + java.util.Arrays.asList("compile", "process-classes", + "generate-test-sources", "process-test-sources", + "generate-test-resources", "process-test-resources", + "test-compile", "process-test-classes", "test", + "prepare-package", "package", + "pre-integration-test", "integration-test", "post-integration-test", + "verify", "install", "deploy"); + + /** + * @param phase the execution's phase, or null to accept the goal's own + * default of {@code process-classes} + */ + private static boolean phaseSeesCompiledClasses(String phase) { + if (phase == null || phase.trim().length() == 0) { + return true; + } + return PHASES_WITH_CLASSES.contains(phase.trim().toLowerCase()); + } + + /** Reads {@code true} out of a plugin or execution configuration. */ + private static boolean isSkipped(Object configuration) { + if (!(configuration instanceof org.codehaus.plexus.util.xml.Xpp3Dom)) { + return false; + } + org.codehaus.plexus.util.xml.Xpp3Dom skip = + ((org.codehaus.plexus.util.xml.Xpp3Dom) configuration).getChild("skip"); + return skip != null && "true".equalsIgnoreCase(String.valueOf(skip.getValue()).trim()); + } + /** * Whether the codenameone-core on this project's compile classpath actually * carries the annotations. diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index ce1a4964cfd..61db8909385 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -25,6 +25,7 @@ import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.xml.Xpp3Dom; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -118,18 +119,47 @@ public void aDefaultPackageSourceStillGetsAUsableAnchor() { @Test public void onlyAnEnabledExecutionOnTheOwningModuleCounts() { assertTrue(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", "process-classes"))); + moduleBinding("process-annotations", "process-classes", false))); + // No phase means the goal's own default, process-classes. + assertTrue(MigrateBuildHintsMojo.bindsProcessAnnotations( + moduleBinding("process-annotations", null, false))); assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("css", "process-classes"))); + moduleBinding("css", "process-classes", false))); // Declared but never run is the same as absent for this purpose. assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", "none"))); + moduleBinding("process-annotations", "none", false))); + } + + /// ProcessAnnotationsMojo returns immediately when skip is set, and again + /// when its output directory does not exist -- which is every phase before + /// compile. Such an execution emits no annotation resource, so migrating + /// against it would delete the properties and leave nothing behind. + @Test + public void anExecutionThatCannotSeeCompiledClassesDoesNotCount() { + assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( + moduleBinding("process-annotations", "generate-sources", false))); + assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( + moduleBinding("process-annotations", "process-resources", false))); + assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( + moduleBinding("process-annotations", "process-classes", true))); + // compile is the earliest phase where target/classes exists. + assertTrue(MigrateBuildHintsMojo.bindsProcessAnnotations( + moduleBinding("process-annotations", "compile", false))); } - private static MavenProject moduleBinding(String goal, String phase) { + private static MavenProject moduleBinding(String goal, String phase, boolean skip) { PluginExecution e = new PluginExecution(); - e.setPhase(phase); + if (phase != null) { + e.setPhase(phase); + } e.addGoal(goal); + if (skip) { + Xpp3Dom config = new Xpp3Dom("configuration"); + Xpp3Dom flag = new Xpp3Dom("skip"); + flag.setValue("true"); + config.addChild(flag); + e.setConfiguration(config); + } Plugin plugin = new Plugin(); plugin.setGroupId("com.codenameone"); plugin.setArtifactId("codenameone-maven-plugin"); From af8ff86d778f436546ec8d0f550af1a89d90f9a2 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 11:11:54 +0300 Subject: [PATCH 013/115] Stop the generated simulator schema duplicating the hand-written one The group name is part of the property key, so registering harden.level under both `hardening` and `Hardening` overwrites nothing -- it creates a second group, and BuildHintEditor renders every group it finds. The user saw duplicate controls for one setting, for all of harden.*, nativeTheme, ios.themeMode and and.themeMode. The comment claiming the hand-written entries take precedence because the setter never overwrites was simply wrong: the two never collided on a key. BuildHintSchemaDefaults now records the hints it describes as it registers them, and the generated companion skips those. Precedence is explicit rather than assumed, and it cannot drift, since the record is built from the same set() calls that do the describing. Verified by walking the registered properties: 89 hints in the editor, none appearing under more than one group, and harden.level, nativeTheme, ios.themeMode and and.themeMode all resolving to their hand-written group. Also makes the integration test fail when the merged settings file is absent. It was the only assertion that annotation hints reach the build request, and skipping it on a missing file meant a regression in goal ordering, target validation or the merge itself would have left the test green. Co-Authored-By: Claude Opus 5 (1M context) --- .../impl/javase/BuildHintCatalogDefaults.java | 173 +++++++++++++++++- .../impl/javase/BuildHintSchemaDefaults.java | 24 +++ .../build/shared/BuildHintCodeGenerator.java | 12 +- .../build-hint-annotations-test.sh | 20 +- 4 files changed, 219 insertions(+), 10 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java index 8a8014201c3..c1f57e69aef 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java @@ -29,8 +29,12 @@ * BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and re-run * scripts/gen-build-hint-annotations.sh.

* - *

Registered after {@link BuildHintSchemaDefaults}, whose hand-written - * entries take precedence because the shared setter never overwrites.

+ *

Registered after {@link BuildHintSchemaDefaults} and skipping every hint + * that class already describes. Precedence cannot be left to the setter: + * the group name is part of the property key, so registering harden.level + * under both `hardening` and `Hardening` does not overwrite anything -- it + * makes a second group, and the editor renders both, giving the user + * duplicate controls for one setting.

*/ final class BuildHintCatalogDefaults { @@ -38,263 +42,428 @@ private BuildHintCatalogDefaults() { } static void register() { + java.util.Set handWritten = BuildHintSchemaDefaults.declaredHints(); set("{{@IosPrivacy}}.label", "iOS Privacy Strings"); + if (!handWritten.contains("ios.NSCalendarsFullAccessUsageDescription")) { set("{{#IosPrivacy#ios.NSCalendarsFullAccessUsageDescription}}.label", "Calendars full access usage description"); set("{{#IosPrivacy#ios.NSCalendarsFullAccessUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSCalendarsUsageDescription")) { set("{{#IosPrivacy#ios.NSCalendarsUsageDescription}}.label", "Calendars usage description"); set("{{#IosPrivacy#ios.NSCalendarsUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSCalendarsWriteOnlyAccessUsageDescription")) { set("{{#IosPrivacy#ios.NSCalendarsWriteOnlyAccessUsageDescription}}.label", "Calendars write only access usage description"); set("{{#IosPrivacy#ios.NSCalendarsWriteOnlyAccessUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSCameraUsageDescription")) { set("{{#IosPrivacy#ios.NSCameraUsageDescription}}.label", "Camera usage description"); set("{{#IosPrivacy#ios.NSCameraUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSHealthShareUsageDescription")) { set("{{#IosPrivacy#ios.NSHealthShareUsageDescription}}.label", "Health share usage description"); set("{{#IosPrivacy#ios.NSHealthShareUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSHealthUpdateUsageDescription")) { set("{{#IosPrivacy#ios.NSHealthUpdateUsageDescription}}.label", "Health update usage description"); set("{{#IosPrivacy#ios.NSHealthUpdateUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSLocalNetworkUsageDescription")) { set("{{#IosPrivacy#ios.NSLocalNetworkUsageDescription}}.label", "Local network usage description"); set("{{#IosPrivacy#ios.NSLocalNetworkUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSLocationAlwaysAndWhenInUseUsageDescription")) { set("{{#IosPrivacy#ios.NSLocationAlwaysAndWhenInUseUsageDescription}}.label", "Location always and when in use usage description"); set("{{#IosPrivacy#ios.NSLocationAlwaysAndWhenInUseUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSLocationAlwaysUsageDescription")) { set("{{#IosPrivacy#ios.NSLocationAlwaysUsageDescription}}.label", "Location always usage description"); set("{{#IosPrivacy#ios.NSLocationAlwaysUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSLocationWhenInUseUsageDescription")) { set("{{#IosPrivacy#ios.NSLocationWhenInUseUsageDescription}}.label", "Location when in use usage description"); set("{{#IosPrivacy#ios.NSLocationWhenInUseUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSMicrophoneUsageDescription")) { set("{{#IosPrivacy#ios.NSMicrophoneUsageDescription}}.label", "Microphone usage description"); set("{{#IosPrivacy#ios.NSMicrophoneUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSRemindersFullAccessUsageDescription")) { set("{{#IosPrivacy#ios.NSRemindersFullAccessUsageDescription}}.label", "Reminders full access usage description"); set("{{#IosPrivacy#ios.NSRemindersFullAccessUsageDescription}}.type", "TextField"); + } + if (!handWritten.contains("ios.NSRemindersUsageDescription")) { set("{{#IosPrivacy#ios.NSRemindersUsageDescription}}.label", "Reminders usage description"); set("{{#IosPrivacy#ios.NSRemindersUsageDescription}}.type", "TextField"); + } set("{{@Ios}}.label", "iOS"); + if (!handWritten.contains("ios.add_libs")) { set("{{#Ios#ios.add_libs}}.label", "Add libs"); set("{{#Ios#ios.add_libs}}.type", "TextArea"); set("{{#Ios#ios.add_libs}}.description", "A semicolon separated list of libraries that should be linked to the app to build it"); + } + if (!handWritten.contains("ios.applicationQueriesSchemes")) { set("{{#Ios#ios.applicationQueriesSchemes}}.label", "Application queries schemes"); set("{{#Ios#ios.applicationQueriesSchemes}}.type", "TextArea"); set("{{#Ios#ios.applicationQueriesSchemes}}.description", "Comma separated list of url schemes that `canExecute` will respect on iOS. If the url scheme isn't mentioned here `canExecute` will return false starting with iOS 9. Notice that this collides with `ios.plistInject` when used with the `LSApplicationQueriesSchemes...` value so you should use one or the other. For example, to enable `canExecute` for a url like `myurl://xys` you can use: `myurl,myotherurl`"); + } + if (!handWritten.contains("ios.beforeFinishLaunching")) { set("{{#Ios#ios.beforeFinishLaunching}}.label", "Before finish launching"); set("{{#Ios#ios.beforeFinishLaunching}}.type", "TextArea"); set("{{#Ios#ios.beforeFinishLaunching}}.description", "Objective-C code that can be injected into the iOS app delegate at the top of the body of the didFinishLaunchingWithOptions callback method"); + } + if (!handWritten.contains("ios.bundleVersion")) { set("{{#Ios#ios.bundleVersion}}.label", "Bundle version"); set("{{#Ios#ios.bundleVersion}}.type", "TextField"); set("{{#Ios#ios.bundleVersion}}.description", "Indicates the version number of the bundle, this is useful if you want to create a minor version number change for the beta testing support"); + } + if (!handWritten.contains("ios.dependencyManager")) { set("{{#Ios#ios.dependencyManager}}.label", "Dependency manager"); set("{{#Ios#ios.dependencyManager}}.type", "Select"); set("{{#Ios#ios.dependencyManager}}.values", "auto,cocoapods,spm,both,none"); set("{{#Ios#ios.dependencyManager}}.description", "Which native dependency manager to use: auto picks one from whichever of ios.pods and ios.spm.packages is set, and cocoapods, spm or both require the matching hint to be set. An unrecognized value fails the build."); + } + if (!handWritten.contains("ios.deployment_target")) { set("{{#Ios#ios.deployment_target}}.label", "Deployment target"); set("{{#Ios#ios.deployment_target}}.type", "TextField"); set("{{#Ios#ios.deployment_target}}.description", "Minimum iOS version the build targets. Set it to the lowest iOS you actually support; a higher value excludes older devices from the App Store listing."); + } + if (!handWritten.contains("ios.glAppDelegateHeader")) { set("{{#Ios#ios.glAppDelegateHeader}}.label", "Gl app delegate header"); set("{{#Ios#ios.glAppDelegateHeader}}.type", "TextArea"); set("{{#Ios#ios.glAppDelegateHeader}}.description", "Objective-C code that can be injected into the iOS app delegate at the top of the file. For example, if you need to include headers or make special imports for other injected code"); + } + if (!handWritten.contains("ios.includePush")) { set("{{#Ios#ios.includePush}}.label", "Include push"); set("{{#Ios#ios.includePush}}.type", "Checkbox"); set("{{#Ios#ios.includePush}}.description", "true/false (defaults to false). Whether to include the push capabilities in the iOS build. Notice that the IDE plugin has an \"Include Push\" check box you *should* use under the iOS section."); + } + if (!handWritten.contains("ios.interface_orientation")) { set("{{#Ios#ios.interface_orientation}}.label", "Interface orientation"); set("{{#Ios#ios.interface_orientation}}.type", "TextField"); set("{{#Ios#ios.interface_orientation}}.description", "UIInterfaceOrientationPortrait by default. Indicates the orientation, one or more of (separated by colon :): `UIInterfaceOrientationPortrait`, `UIInterfaceOrientationPortraitUpsideDown`, `UIInterfaceOrientationLandscapeLeft`, `UIInterfaceOrientationLandscapeRight`. Notice that the IDE plugin has an \"Interface Orientation\" combo box you *should* use under the iOS section."); + } + if (!handWritten.contains("ios.minDeploymentTarget")) { set("{{#Ios#ios.minDeploymentTarget}}.label", "Min deployment target"); set("{{#Ios#ios.minDeploymentTarget}}.type", "TextField"); set("{{#Ios#ios.minDeploymentTarget}}.description", "The null and empty-string reads of this hint are presence checks; 6.0 is the substantive default (IPhoneBuilder.java:4671)."); + } + if (!handWritten.contains("ios.newStorageLocation")) { set("{{#Ios#ios.newStorageLocation}}.label", "New storage location"); set("{{#Ios#ios.newStorageLocation}}.type", "Checkbox"); set("{{#Ios#ios.newStorageLocation}}.description", "true/false defaults to false but defined on new projects as true by default. This changes the storage directory on iOS from using caches to using the documents directory which is the recommended location but might break compatibility. This is described in https://github.com/codenameone/CodenameOne/issues/1480[this issue]"); + } + if (!handWritten.contains("ios.objC")) { set("{{#Ios#ios.objC}}.label", "Obj c"); set("{{#Ios#ios.objC}}.type", "Checkbox"); set("{{#Ios#ios.objC}}.description", "Added the `-ObjC` compile flag to the project files which some native libraries require"); + } + if (!handWritten.contains("ios.plistInject")) { set("{{#Ios#ios.plistInject}}.label", "Plist inject"); set("{{#Ios#ios.plistInject}}.type", "TextArea"); set("{{#Ios#ios.plistInject}}.description", "entries to inject into the iOS plist file during build."); + } + if (!handWritten.contains("ios.pods")) { set("{{#Ios#ios.pods}}.label", "Pods"); set("{{#Ios#ios.pods}}.type", "TextArea"); set("{{#Ios#ios.pods}}.description", "A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be linked to the app to build it. For example, `AFNetworking ~> 2.6, ORStackView ~> 3.0, SwiftyJSON ~> 2.3`"); + } + if (!handWritten.contains("ios.pods.platform")) { set("{{#Ios#ios.pods.platform}}.label", "Pods platform"); set("{{#Ios#ios.pods.platform}}.type", "TextField"); set("{{#Ios#ios.pods.platform}}.description", "Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a minimum platform level. For example, `ios.pods.platform=7.0`."); + } + if (!handWritten.contains("ios.pods.sources")) { set("{{#Ios#ios.pods.sources}}.label", "Pods sources"); set("{{#Ios#ios.pods.sources}}.type", "TextArea"); set("{{#Ios#ios.pods.sources}}.description", "Extra CocoaPods spec repositories to search, in addition to the default trunk."); + } + if (!handWritten.contains("ios.prerendered_icon")) { set("{{#Ios#ios.prerendered_icon}}.label", "Prerendered icon"); set("{{#Ios#ios.prerendered_icon}}.type", "Checkbox"); set("{{#Ios#ios.prerendered_icon}}.description", "true/false defaults to false. The iOS build process adapts the submitted icon for iOS conventions (adding an overlay) that might not be appropriate on some icons. Setting this to true leaves the icon unchanged (only scaled)."); + } + if (!handWritten.contains("ios.project_type")) { set("{{#Ios#ios.project_type}}.label", "Project type"); set("{{#Ios#ios.project_type}}.type", "Select"); set("{{#Ios#ios.project_type}}.values", "ios,ipad,iphone"); set("{{#Ios#ios.project_type}}.description", "one of ios, ipad, iphone (defaults to ios). Indicates whether the resulting binary is targeted to the iphone only or ipad only. Notice that the IDE plugin has a \"Project Type\" combo box you *should* use under the iOS section."); + } + if (!handWritten.contains("ios.spm.packages")) { set("{{#Ios#ios.spm.packages}}.label", "Spm packages"); set("{{#Ios#ios.spm.packages}}.type", "TextArea"); set("{{#Ios#ios.spm.packages}}.description", "Swift Package Manager packages to link, one per entry, each written as identity|url|requirement."); + } + if (!handWritten.contains("ios.teamId")) { set("{{#Ios#ios.teamId}}.label", "Team id"); set("{{#Ios#ios.teamId}}.type", "TextField"); set("{{#Ios#ios.teamId}}.description", "Specifies the team ID associated with the iOS provisioning profile and certificate. Use `ios.debug.teamId` and `ios.release.teamId` to specify different team IDs for debug and release builds respectively."); + } + if (!handWritten.contains("ios.themeMode")) { set("{{#Ios#ios.themeMode}}.label", "Theme mode"); set("{{#Ios#ios.themeMode}}.type", "Select"); set("{{#Ios#ios.themeMode}}.values", "auto,modern,ios7,legacy"); set("{{#Ios#ios.themeMode}}.description", "`auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the existing iOS 7 flat theme so pre-refactor screenshot goldens and apps see no behavior change. `modern` / `liquid` opts in to the CSS-generated iOS Modern (liquid-glass) theme shipped from `native-themes/ios-modern/theme.css`. `ios7` / `flat` is the same as `auto` - pre-liquid iOS 7 flat theme; `legacy` / `iphone` loads the pre-iOS 7 iPhone theme. The `auto` -> modern flip is planned for a future release."); + } + if (!handWritten.contains("ios.uiscene")) { set("{{#Ios#ios.uiscene}}.label", "Uiscene"); set("{{#Ios#ios.uiscene}}.type", "Checkbox"); set("{{#Ios#ios.uiscene}}.description", "true/false (defaults to true). Enables iOS UIScene lifecycle support. UIScene lets iOS manage one or more app UI sessions independently, improving lifecycle handling in modern iOS versions. Apple has indicated UIScene will be required starting with iOS 27, so this is now on by default; set the flag to `false` only if you need to temporarily fall back to the legacy `UIApplicationDelegate` lifecycle."); + } + if (!handWritten.contains("ios.urlScheme")) { set("{{#Ios#ios.urlScheme}}.label", "Url scheme"); set("{{#Ios#ios.urlScheme}}.type", "TextField"); set("{{#Ios#ios.urlScheme}}.description", "Allows intercepting a URL call using the syntax `urlPrefix`"); + } set("{{@Android}}.label", "Android"); + if (!handWritten.contains("android.activity.launchMode")) { set("{{#Android#android.activity.launchMode}}.label", "Activity launch mode"); set("{{#Android#android.activity.launchMode}}.type", "TextField"); set("{{#Android#android.activity.launchMode}}.description", "Allows explicitly setting the `android:launchMode` attribute of the main activity in android. Default is \"singleTop,\" but for some applications you may need to change this behaviour. In particular, apps that are meant to open a file type will need to set this to \"singleTask.\" See https://developer.android.com/guide/topics/manifest/activity-element.html[Android docs for the activity element] for more information about the `android:launchMode` attribute."); + } + if (!handWritten.contains("android.appBundle")) { set("{{#Android#android.appBundle}}.label", "App bundle"); set("{{#Android#android.appBundle}}.type", "Checkbox"); set("{{#Android#android.appBundle}}.description", "Produces an Android App Bundle (.aab) rather than an APK. Required for new Play Store submissions."); + } + if (!handWritten.contains("android.buildToolsVersion")) { set("{{#Android#android.buildToolsVersion}}.label", "Build tools version"); set("{{#Android#android.buildToolsVersion}}.type", "TextField"); set("{{#Android#android.buildToolsVersion}}.description", "Android build-tools version. It also selects the compile SDK, so there is no separate compile-SDK hint."); + } + if (!handWritten.contains("android.captureRecord")) { set("{{#Android#android.captureRecord}}.label", "Capture record"); set("{{#Android#android.captureRecord}}.type", "TextField"); set("{{#Android#android.captureRecord}}.description", "Indicates whether the `RECORD_AUDIO` permission should be requested. Can be `enabled` or any other value to disable this option"); + } + if (!handWritten.contains("android.debug")) { set("{{#Android#android.debug}}.label", "Debug"); set("{{#Android#android.debug}}.type", "Checkbox"); set("{{#Android#android.debug}}.description", "true/false defaults to true - indicates whether to include the debug version in the build. Defaults conditionally rather than to a fixed value: when android.release is on it defaults to false, and when release is off it defaults to true, so a build that selects neither still produces something installable (AndroidGradleBuilder.java:447-451)."); + } + if (!handWritten.contains("android.disableR8")) { set("{{#Android#android.disableR8}}.label", "Disable r8"); set("{{#Android#android.disableR8}}.type", "Checkbox"); set("{{#Android#android.disableR8}}.description", "Turns off R8, falling back to the older shrinker. Note that hardening requires R8, so this conflicts with harden.level."); + } + if (!handWritten.contains("android.enableProguard")) { set("{{#Android#android.enableProguard}}.label", "Enable proguard"); set("{{#Android#android.enableProguard}}.type", "Checkbox"); set("{{#Android#android.enableProguard}}.description", "Boolean true/false defaults to true. Allows disabling the proguard obfuscation even on release builds, notice that this isn't recommended"); + } + if (!handWritten.contains("android.gradleDep")) { set("{{#Android#android.gradleDep}}.label", "Gradle dep"); set("{{#Android#android.gradleDep}}.type", "TextArea"); set("{{#Android#android.gradleDep}}.description", "Gradle dependency statements to add to the app module, such as implementation 'com.example:lib:1.0'."); + } + if (!handWritten.contains("android.hideStatusBar")) { set("{{#Android#android.hideStatusBar}}.label", "Hide status bar"); set("{{#Android#android.hideStatusBar}}.type", "Checkbox"); set("{{#Android#android.hideStatusBar}}.description", "Hides the Android status bar."); + } + if (!handWritten.contains("android.installLocation")) { set("{{#Android#android.installLocation}}.label", "Install location"); set("{{#Android#android.installLocation}}.type", "Select"); set("{{#Android#android.installLocation}}.values", "auto,internalOnly,preferExternal"); set("{{#Android#android.installLocation}}.description", "Maps to android:installLocation manifest entry defaults to auto. Can also be set to internalOnly or preferExternal."); + } + if (!handWritten.contains("android.licenseKey")) { set("{{#Android#android.licenseKey}}.label", "License key"); set("{{#Android#android.licenseKey}}.type", "TextField"); set("{{#Android#android.licenseKey}}.description", "The license key for the Android app, this is required if you use in-app purchase on Android"); + } + if (!handWritten.contains("android.min_sdk_version")) { set("{{#Android#android.min_sdk_version}}.label", "Min sdk version"); set("{{#Android#android.min_sdk_version}}.type", "TextField"); set("{{#Android#android.min_sdk_version}}.description", "The least SDK required to run this app, the default value changes based on functionality but can be as low as 7. This corresponds to the XML attribute `android:minSdkVersion`."); + } + if (!handWritten.contains("android.multidex")) { set("{{#Android#android.multidex}}.label", "Multidex"); set("{{#Android#android.multidex}}.type", "Checkbox"); set("{{#Android#android.multidex}}.description", "Boolean true/false defaults to false. Multidex allows Android binaries to reference more than 65536 methods. This slows builds a bit so you have it off by default but if you get a build error mentioning this limit you should turn this on."); + } + if (!handWritten.contains("android.newFirebaseMessaging")) { set("{{#Android#android.newFirebaseMessaging}}.label", "New firebase messaging"); set("{{#Android#android.newFirebaseMessaging}}.type", "Checkbox"); set("{{#Android#android.newFirebaseMessaging}}.description", "Uses the current Firebase Cloud Messaging integration. Requires AndroidX and Gradle 8.13 or newer."); + } + if (!handWritten.contains("android.proguardKeep")) { set("{{#Android#android.proguardKeep}}.label", "Proguard keep"); set("{{#Android#android.proguardKeep}}.type", "TextArea"); set("{{#Android#android.proguardKeep}}.description", "Arguments for the keep option in proguard allowing you to keep a pattern of files for example, `-keep class com.mypackage.ProblemClass { *; }`"); + } + if (!handWritten.contains("android.release")) { set("{{#Android#android.release}}.label", "Release"); set("{{#Android#android.release}}.type", "Checkbox"); set("{{#Android#android.release}}.description", "true/false defaults to true - indicates whether to include the release version in the build"); + } + if (!handWritten.contains("android.repositories")) { set("{{#Android#android.repositories}}.label", "Repositories"); set("{{#Android#android.repositories}}.type", "TextArea"); set("{{#Android#android.repositories}}.description", "Extra Gradle repositories to resolve dependencies from."); + } + if (!handWritten.contains("android.targetSDKVersion")) { set("{{#Android#android.targetSDKVersion}}.label", "Target sDKVersion"); set("{{#Android#android.targetSDKVersion}}.type", "TextField"); set("{{#Android#android.targetSDKVersion}}.description", "Indicates the Android SDK used to compile the Android build defaults to 21. Notice that not all targets will work since the source might have some limitations and not all SDK targets are installed on the build servers."); + } + if (!handWritten.contains("and.themeMode")) { set("{{#Android#and.themeMode}}.label", "Theme mode"); set("{{#Android#and.themeMode}}.type", "Select"); set("{{#Android#and.themeMode}}.values", "auto,modern,hololight,legacy"); set("{{#Android#and.themeMode}}.description", "`auto`, `modern` / `material`, `hololight` (default for existing apps), `legacy`. `auto` and `modern` / `material` opt in to the CSS-generated Android Material 3 theme from `native-themes/android-material/theme.css`. `hololight` is Android Holo Light (what the framework shipped on API 14+ before this refactor). `legacy` loads the pre-Holo Android theme. The legacy alias `cn1.androidTheme` is still accepted, and `and.hololight=true` still maps to `hololight`. The default stays on `hololight` for existing apps until you flip in a future release."); + } + if (!handWritten.contains("android.topDependency")) { set("{{#Android#android.topDependency}}.label", "Top dependency"); set("{{#Android#android.topDependency}}.type", "TextArea"); set("{{#Android#android.topDependency}}.description", "Statements added to the top-level Gradle build file rather than the app module."); + } + if (!handWritten.contains("android.useAndroidX")) { set("{{#Android#android.useAndroidX}}.label", "Use android x"); set("{{#Android#android.useAndroidX}}.type", "Checkbox"); set("{{#Android#android.useAndroidX}}.description", "Use Android X instead of support libraries. This will also run a find/replace on all source files to replace support libraries and artifacts with AndroidX equivalents."); + } + if (!handWritten.contains("android.xapplication")) { set("{{#Android#android.xapplication}}.label", "Xapplication"); set("{{#Android#android.xapplication}}.type", "TextArea"); set("{{#Android#android.xapplication}}.description", "defaults to an empty string. Allows developers of native Android code to add text within the application block to define things such as widgets, services etc."); + } + if (!handWritten.contains("android.xgradle")) { set("{{#Android#android.xgradle}}.label", "Xgradle"); set("{{#Android#android.xgradle}}.type", "TextArea"); set("{{#Android#android.xgradle}}.description", "Arbitrary text spliced into the generated app-module Gradle file."); + } + if (!handWritten.contains("android.xpermissions")) { set("{{#Android#android.xpermissions}}.label", "Xpermissions"); set("{{#Android#android.xpermissions}}.type", "TextArea"); set("{{#Android#android.xpermissions}}.description", "more permissions for the Android manifest"); + } set("{{@Desktop}}.label", "Desktop"); + if (!handWritten.contains("desktop.adaptToRetina")) { set("{{#Desktop#desktop.adaptToRetina}}.label", "Adapt to retina"); set("{{#Desktop#desktop.adaptToRetina}}.type", "Checkbox"); set("{{#Desktop#desktop.adaptToRetina}}.description", "Boolean true/false defaults to true. When set to true some values will ve implicitly doubled to deal with retina displays and icons etc. Will use higher DPI's"); + } + if (!handWritten.contains("desktop.fullscreen")) { set("{{#Desktop#desktop.fullscreen}}.label", "Fullscreen"); set("{{#Desktop#desktop.fullscreen}}.type", "Checkbox"); set("{{#Desktop#desktop.fullscreen}}.description", "Starts the desktop build in full-screen mode."); + } + if (!handWritten.contains("desktop.height")) { set("{{#Desktop#desktop.height}}.label", "Height"); set("{{#Desktop#desktop.height}}.type", "TextField"); set("{{#Desktop#desktop.height}}.description", "Height in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 600."); + } + if (!handWritten.contains("desktop.interactiveScrollbars")) { set("{{#Desktop#desktop.interactiveScrollbars}}.label", "Interactive scrollbars"); set("{{#Desktop#desktop.interactiveScrollbars}}.type", "Checkbox"); set("{{#Desktop#desktop.interactiveScrollbars}}.description", "Enables grab-able, click-to-page desktop scrollbars."); + } + if (!handWritten.contains("desktop.resizable")) { set("{{#Desktop#desktop.resizable}}.label", "Resizable"); set("{{#Desktop#desktop.resizable}}.type", "Checkbox"); set("{{#Desktop#desktop.resizable}}.description", "Boolean true/false defaults to true. Indicates whether the UI in the desktop build is resizable"); + } + if (!handWritten.contains("desktop.titleBar")) { set("{{#Desktop#desktop.titleBar}}.label", "Title bar"); set("{{#Desktop#desktop.titleBar}}.type", "Select"); set("{{#Desktop#desktop.titleBar}}.values", "native,custom,toolbar"); set("{{#Desktop#desktop.titleBar}}.description", "How the desktop window is framed: native for the OS title bar and menu bar, custom for an undecorated window with a Codename One drawn title bar, or toolbar for the legacy in-app Toolbar. An unrecognized value falls back to native with a warning."); + } + if (!handWritten.contains("desktop.width")) { set("{{#Desktop#desktop.width}}.label", "Width"); set("{{#Desktop#desktop.width}}.type", "TextField"); set("{{#Desktop#desktop.width}}.description", "Width in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 800."); + } set("{{@OnDeviceDebug}}.label", "On-Device Debugging"); + if (!handWritten.contains("android.onDeviceDebug")) { set("{{#OnDeviceDebug#android.onDeviceDebug}}.label", "Android"); set("{{#OnDeviceDebug#android.onDeviceDebug}}.type", "Checkbox"); set("{{#OnDeviceDebug#android.onDeviceDebug}}.description", "Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable=\"true\"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable=\"true\"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it -- release builds are unaffected. See the On-Device Debugging (Android) chapter for the full flow."); + } + if (!handWritten.contains("ios.onDeviceDebug")) { set("{{#OnDeviceDebug#ios.onDeviceDebug}}.label", "Ios"); set("{{#OnDeviceDebug#ios.onDeviceDebug}}.type", "Checkbox"); set("{{#OnDeviceDebug#ios.onDeviceDebug}}.description", "Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits source-line and locals metadata so a desktop proxy can serve the running app to any JDWP-speaking debugger. Has no effect on release builds. See the On-Device Debugging (iOS) chapter for the full flow."); + } + if (!handWritten.contains("ios.onDeviceDebug.proxyHost")) { set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyHost}}.label", "Ios proxy host"); set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyHost}}.type", "TextField"); set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyHost}}.description", "Hostname or IP address the device-side listener dials to reach the desktop proxy. Default `127.0.0.1` (correct for the native iOS simulator). For a physical device, set this to the developer laptop's LAN IP. Has no effect unless `ios.onDeviceDebug=true`."); + } + if (!handWritten.contains("ios.onDeviceDebug.proxyPort")) { set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyPort}}.label", "Ios proxy port"); set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyPort}}.type", "TextField"); set("{{#OnDeviceDebug#ios.onDeviceDebug.proxyPort}}.description", "TCP port on `ios.onDeviceDebug.proxyHost` where the proxy is listening for the device. Default `55333`. Has no effect unless `ios.onDeviceDebug=true`."); + } + if (!handWritten.contains("ios.onDeviceDebug.waitForAttach")) { set("{{#OnDeviceDebug#ios.onDeviceDebug.waitForAttach}}.label", "Ios wait for attach"); set("{{#OnDeviceDebug#ios.onDeviceDebug.waitForAttach}}.type", "Checkbox"); set("{{#OnDeviceDebug#ios.onDeviceDebug.waitForAttach}}.description", "Boolean true/false defaults to false. When `true`, the app blocks at startup until the proxy connects and the IDE tells the VM to continue. Useful when the breakpoint to investigate fires during app boot. Has no effect unless `ios.onDeviceDebug=true`."); + } set("{{@Build}}.label", "General"); + if (!handWritten.contains("facebook.appId")) { set("{{#Build#facebook.appId}}.label", "Facebook app id"); set("{{#Build#facebook.appId}}.type", "TextField"); set("{{#Build#facebook.appId}}.description", "The application ID for an app that requires native Facebook login integration, this defaults to null which means native Facebook support shouldn't be in the app"); + } + if (!handWritten.contains("gcm.sender_id")) { set("{{#Build#gcm.sender_id}}.label", "Gcm sender id"); set("{{#Build#gcm.sender_id}}.type", "TextField"); set("{{#Build#gcm.sender_id}}.description", "The Android/chrome push identifier, see the push section for more details"); + } + if (!handWritten.contains("nativeTheme")) { set("{{#Build#nativeTheme}}.label", "Native theme"); set("{{#Build#nativeTheme}}.type", "Select"); set("{{#Build#nativeTheme}}.values", "modern,legacy,custom"); set("{{#Build#nativeTheme}}.description", "`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both `ios.themeMode` and `and.themeMode` together when those aren't set explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the framework native theme entirely. The legacy alias `cn1.nativeTheme` is still accepted."); + } + if (!handWritten.contains("noExtraResources")) { set("{{#Build#noExtraResources}}.label", "No extra resources"); set("{{#Build#noExtraResources}}.type", "Checkbox"); set("{{#Build#noExtraResources}}.description", "true/false (defaults to false). Blocks codename one from injecting its own resources when set to true, the only effect this has is in slightly reducing archive size. This might have adverse effects on some features of Codename One so it isn't recommended."); + } set("{{@Hardening}}.label", "App Hardening"); + if (!handWritten.contains("harden.allowUnhardenedLocalBuild")) { set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.label", "Allow unhardened local build"); set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.type", "Checkbox"); set("{{#Hardening#harden.allowUnhardenedLocalBuild}}.description", "Permits a local or source build to run with hardening requested but not applied. Without it such a build is refused, so a hardened app is never shipped from a target that can't actually harden it."); + } + if (!handWritten.contains("harden.controlFlow")) { set("{{#Hardening#harden.controlFlow}}.label", "Control flow"); set("{{#Hardening#harden.controlFlow}}.type", "Select"); set("{{#Hardening#harden.controlFlow}}.values", "off,on"); set("{{#Hardening#harden.controlFlow}}.description", "Overrides control-flow obfuscation independently of harden.level."); + } + if (!handWritten.contains("harden.keep")) { set("{{#Hardening#harden.keep}}.label", "Keep"); set("{{#Hardening#harden.keep}}.type", "TextArea"); set("{{#Hardening#harden.keep}}.description", "Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at runtime and so can't be found by the automatic analysis. Same syntax as android.proguardKeep, so existing rules port directly. Rules are separated by newlines only, because a semicolon is legal inside a rule body such as { *; }."); + } + if (!handWritten.contains("harden.level")) { set("{{#Hardening#harden.level}}.label", "Level"); set("{{#Hardening#harden.level}}.type", "Select"); set("{{#Hardening#harden.level}}.values", "off,standard,aggressive,paranoid"); set("{{#Hardening#harden.level}}.description", "Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized value fails the build rather than being treated as off."); + } + if (!handWritten.contains("harden.rename")) { set("{{#Hardening#harden.rename}}.label", "Rename"); set("{{#Hardening#harden.rename}}.type", "Checkbox"); set("{{#Hardening#harden.rename}}.description", "Overrides symbol renaming independently of harden.level."); + } + if (!handWritten.contains("harden.strings")) { set("{{#Hardening#harden.strings}}.label", "Strings"); set("{{#Hardening#harden.strings}}.type", "Select"); set("{{#Hardening#harden.strings}}.values", "off,constants,all"); set("{{#Hardening#harden.strings}}.description", "Overrides string obfuscation independently of harden.level: off, constants or all."); + } } /** Idempotent setter: does not overwrite user or project-level metadata. */ diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java index 1f709f35387..f85a6fc37e9 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java @@ -243,8 +243,32 @@ static void register() { BuildHintCatalogDefaults.register(); } + /** + * The hints this class describes by hand. + * + *

{@link BuildHintCatalogDefaults} consults it so the two never describe + * the same hint. The group name is part of the property key, so a hint + * registered under both {@code hardening} and {@code Hardening} is not + * overwritten -- it is a second group, and the editor renders both, giving + * the user duplicate controls for one setting.

+ */ + private static final java.util.Set DECLARED = new java.util.HashSet(); + + /** Hint names {@link #register} describes, for the generated companion to skip. */ + static java.util.Set declaredHints() { + return java.util.Collections.unmodifiableSet(DECLARED); + } + /** Idempotent setter: does not overwrite user / project-level hint metadata. */ private static void set(String suffix, String value) { + int hash = suffix.indexOf('#'); + if (suffix.startsWith("{{#") && hash >= 0) { + int second = suffix.indexOf('#', hash + 1); + int close = suffix.indexOf("}}", second + 1); + if (second > 0 && close > second) { + DECLARED.add(suffix.substring(second + 1, close)); + } + } String key = "codename1.arg." + suffix; if (System.getProperty(key) == null) { System.setProperty(key, value); diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java index a6f3017d02a..067e2c4c14f 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java @@ -484,18 +484,25 @@ private static String simulatorSchemaSource(Map sb.append(" * BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and re-run\n"); sb.append(" * scripts/gen-build-hint-annotations.sh.

\n"); sb.append(" *\n"); - sb.append(" *

Registered after {@link BuildHintSchemaDefaults}, whose hand-written\n"); - sb.append(" * entries take precedence because the shared setter never overwrites.

\n"); + sb.append(" *

Registered after {@link BuildHintSchemaDefaults} and skipping every hint\n"); + sb.append(" * that class already describes. Precedence cannot be left to the setter:\n"); + sb.append(" * the group name is part of the property key, so registering harden.level\n"); + sb.append(" * under both `hardening` and `Hardening` does not overwrite anything -- it\n"); + sb.append(" * makes a second group, and the editor renders both, giving the user\n"); + sb.append(" * duplicate controls for one setting.

\n"); sb.append(" */\n"); sb.append("final class BuildHintCatalogDefaults {\n\n"); sb.append(" private BuildHintCatalogDefaults() {\n }\n\n"); sb.append(" static void register() {\n"); + sb.append(" java.util.Set handWritten = BuildHintSchemaDefaults.declaredHints();\n"); for (Map.Entry> e : byGroup.entrySet()) { String group = e.getKey().annotationSimpleName(); sb.append("\n set(\"{{@").append(group).append("}}.label\", ") .append(quote(toAscii(groupLabel(e.getKey())))).append(");\n"); for (BuildHints.Hint h : e.getValue()) { String key = "{{#" + group + "#" + h.name() + "}}"; + sb.append(" if (!handWritten.contains(\"").append(esc(h.name())) + .append("\")) {\n"); sb.append(" set(\"").append(key).append(".label\", ") .append(quote(humanize(h.attr()))).append(");\n"); sb.append(" set(\"").append(key).append(".type\", \"") @@ -515,6 +522,7 @@ private static String simulatorSchemaSource(Map sb.append(" set(\"").append(key).append(".description\", ") .append(quote(toAscii(h.doc()))).append(");\n"); } + sb.append(" }\n"); } } sb.append(" }\n\n"); diff --git a/maven/integration-tests/build-hint-annotations-test.sh b/maven/integration-tests/build-hint-annotations-test.sh index b5b977b4523..3160a49ad41 100755 --- a/maven/integration-tests/build-hint-annotations-test.sh +++ b/maven/integration-tests/build-hint-annotations-test.sh @@ -76,13 +76,21 @@ set +e set -e MERGED=common/target/codenameone/antProject/codenameone_settings.properties test -f $MERGED || MERGED=javase/target/codenameone/antProject/codenameone_settings.properties -if [ -f "$MERGED" ]; then - grep -q "codename1.arg.ios.pods=Alamofire,SwiftyJSON" $MERGED \ - || { echo "FAIL: annotation hints did not reach the build request"; cat $MERGED; exit 1; } - echo "OK: annotation hints reached $MERGED" -else - echo "NOTE: no build request was written for this target; skipping that assertion" +# Do not treat an absent file as "nothing to check". This is the only assertion +# that the annotations reach the build request at all -- the checks above cover +# emission and the one below covers the conflict -- so if the probe stops +# producing a merged settings file, through a change in goal ordering, target +# validation or the merge itself, the test has to fail rather than quietly skip +# the thing it exists to prove. +if [ ! -f "$MERGED" ]; then + echo "FAIL: no build request was written; the annotation merge could not be verified." + echo " Looked for common/ and javase/target/codenameone/antProject/codenameone_settings.properties" + tail -40 /tmp/cn1-hints-build.log + exit 1 fi +grep -q "codename1.arg.ios.pods=Alamofire,SwiftyJSON" $MERGED \ + || { echo "FAIL: annotation hints did not reach the build request"; cat $MERGED; exit 1; } +echo "OK: annotation hints reached $MERGED" echo "--- declaring the same hint twice must fail ---" echo "codename1.arg.ios.teamId=FROMFILE" >> $SETTINGS From 586e6bc3fb7d5e7ade62cb7a052c3ed97c97d88f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 11:52:11 +0300 Subject: [PATCH 014/115] Prove the annotations are processed instead of predicting it Four rounds of review went into guessing, from the POM, whether process-annotations would run: it can be bound on the wrong module, bound to a phase with no compiled classes, skipped outright, or skipped through a property expression the model text does not resolve. Each fix closed one case and the next review found another, which is what a static prediction of another mojo's behaviour is going to keep doing. The goal now applies the whole migration, runs process-classes over the module that holds the main class, and checks that every migrated hint came back out of the emitted resource. If any did not, both files are put back exactly as they were and the failure says what was missing. Whatever the next way to not-run turns out to be, the answer is still correct. Both files have to move together before that check: leaving the properties in place while the annotations are added is itself the duplicate-declaration case, so the build would fail for that reason and never say whether processing works. The first version of this change had that wrong, and the verification caught it. Verified on a generated project: 7 hints in, 6 migrated and confirmed emitted, java.version correctly kept. With the binding removed the goal refuses and both files come back byte-identical. The Settings tool has the same problem from the other side. It read ownership only from the emitted resource, so in the window right after a migration -- the source declares the annotations, no build has run -- every hint looked unowned and Add was offered for one the annotations already set. It now falls back to reading the annotations off the main class, matching attribute names at the top level of each annotation so a comma, bracket or equals sign inside a value cannot register as one. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 236 ++++++++---------- .../MigrateBuildHintsPropertyParsingTest.java | 63 ----- .../settings/CodenameOneSettings.java | 143 ++++++++++- .../settings/BuildHintCatalogTest.java | 38 +++ 4 files changed, 291 insertions(+), 189 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 5cccbd6c5f6..0bcfabe7ee9 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -26,6 +26,11 @@ import com.codename1.build.shared.HintType; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.shared.invoker.DefaultInvocationRequest; +import org.apache.maven.shared.invoker.DefaultInvoker; +import org.apache.maven.shared.invoker.InvocationRequest; +import org.apache.maven.shared.invoker.InvocationResult; +import org.apache.maven.shared.invoker.MavenInvocationException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -99,29 +104,6 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException throw new MojoExecutionException("No codenameone_settings.properties in " + projectDir); } - // Nothing turns an annotation back into a build hint except the - // process-annotations goal, and a mojo's defaultPhase does not bind it to - // a project -- the project's own POM has to. Migrating without that - // binding deletes working properties and replaces them with annotations no - // goal ever reads, so the hints disappear from the build silently. - if (!processAnnotationsIsBound()) { - File owner = getCN1ProjectDir(); - throw new MojoFailureException("The module that holds the main class" - + (owner == null ? "" : " (" + owner + ")") - + " does not run the cn1 process-annotations goal, so build hint annotations " - + "would never be turned back into the codename1.arg.* pairs the builders " - + "read, and migrating would silently drop them. The goal only scans the " - + "module it is bound to, so binding it elsewhere in the reactor does not " - + "help.\n\nAdd it to that module's POM first:\n" - + " \n" - + " cn1-process-classes\n" - + " process-classes\n" - + " \n" - + " process-annotations\n" - + " \n" - + " "); - } - // The annotations ship in codenameone-core. A project pinned to a release // that predates them would migrate cleanly here and then fail to compile, // so refuse rather than hand back a broken project. @@ -223,13 +205,71 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException + "migrated lines from " + settingsFile.getName() + "."); } + // Add the annotations, prove the build actually turns them back into hints, + // and only then delete the properties. Deciding that from the POM instead + // meant guessing whether process-annotations would run -- and the goal is + // skippable, bindable to a phase with no compiled classes, bindable to the + // wrong module, and skippable through a property expression. Observing the + // emitted resource answers all of those at once, and answers correctly for + // whatever the next way to not-run turns out to be. + // Apply the whole migration, prove the build turns the annotations back + // into hints, and roll both files back if it does not. + // + // Both files have to move together before the check: leaving the + // properties in place while the annotations are added *is* the + // duplicate-declaration case, so the build would fail for that reason and + // never tell us whether processing works at all. + // + // Deciding this from the POM instead meant guessing whether + // process-annotations would run, and the goal is skippable, bindable to a + // phase with no compiled classes, bindable to the wrong module, and + // skippable through a property expression. Observing the emitted resource + // answers all of those at once, and answers correctly for whatever the + // next way to not-run turns out to be. + File source = new File(mainSource); + String originalSource; + String originalSettings; try { - insertAnnotations(new File(mainSource), rendered.toString(), + originalSource = read(source); + originalSettings = read(settingsFile); + insertAnnotations(source, rendered.toString(), settings.getProperty("codename1.mainName", "").trim()); removeMigratedLines(settingsFile, migratedKeys); } catch (IOException ex) { throw new MojoExecutionException("Migration failed: " + ex.getMessage(), ex); } + + String missing = verifyAnnotationsAreProcessed(projectDir, migratedKeys); + if (missing != null) { + StringBuilder restoreFailed = new StringBuilder(); + try { + write(source, originalSource); + } catch (IOException ex) { + restoreFailed.append("\nCould not restore ").append(source).append(": ") + .append(ex.getMessage()); + } + try { + writeProperties(settingsFile, originalSettings); + } catch (IOException ex) { + restoreFailed.append("\nCould not restore ").append(settingsFile).append(": ") + .append(ex.getMessage()); + } + throw new MojoFailureException("The annotations were added but the build did not turn " + + "them into build hints, so " + source.getName() + " and " + + settingsFile.getName() + " have been put back as they were.\n\n" + + missing + "\n\nThe usual cause is that this module does not run the cn1 " + + "process-annotations goal, or runs it skipped or before compile. Add it and " + + "try again:\n" + + " \n" + + " cn1-process-classes\n" + + " process-classes\n" + + " \n" + + " process-annotations\n" + + " \n" + + " " + + restoreFailed); + } + getLog().info("cn1: migrated " + migratedKeys.size() + " build hint(s) into " + new File(mainSource).getName()); } @@ -242,120 +282,61 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException * goal is an aggregator, so {@code project} is the root POM while the binding * lives in the common module.

*/ - private boolean processAnnotationsIsBound() { - return moduleOwningTheMainClass() != null; - } - /** - * The reactor module that both holds the main class and runs - * {@code process-annotations} over it, or null. - * - *

Checking the reactor as a whole is not enough. {@code - * ProcessAnnotationsMojo} scans only the output directory of the module it is - * bound to, so a binding on a platform or utility module never sees the main - * class that {@code common} compiles. The migration would then delete the - * properties and leave annotations nothing ever reads.

+ * Runs the project's own build over the module that holds the main class and + * checks that every migrated hint came back out of it. * - *

The owning module is the one whose base directory is the Codename One - * project directory -- the directory holding - * {@code codenameone_settings.properties}, which is also where - * {@link #findMainClassSource} looks.

+ * @return null when all of them did, otherwise a description of what is + * missing, suitable for showing to the developer */ - private org.apache.maven.project.MavenProject moduleOwningTheMainClass() { - File projectDir = getCN1ProjectDir(); - if (projectDir == null) { - return null; - } - java.util.List projects = reactorProjects; - if (projects == null || projects.isEmpty()) { - projects = java.util.Collections.singletonList(project); - } - for (org.apache.maven.project.MavenProject p : projects) { - if (p.getBasedir() == null || !sameDirectory(p.getBasedir(), projectDir)) { - continue; + private String verifyAnnotationsAreProcessed(File projectDir, List migratedKeys) { + getLog().info("cn1: building " + projectDir.getName() + + " to confirm the annotations produce the hints..."); + File pom = new File(projectDir, "pom.xml"); + InvocationRequest request = new DefaultInvocationRequest(); + request.setPomFile(pom.isFile() ? pom : new File(project.getBasedir(), "pom.xml")); + request.setGoals(Collections.singletonList("process-classes")); + Properties props = new Properties(); + props.setProperty("skipTests", "true"); + request.setProperties(props); + request.setBatchMode(true); + try { + InvocationResult result = new DefaultInvoker().execute(request); + if (result.getExitCode() != 0) { + return "The build failed with exit code " + result.getExitCode() + + ", so the annotations could not be checked."; } - return bindsProcessAnnotations(p) ? p : null; + } catch (MavenInvocationException ex) { + return "The build could not be run (" + ex.getMessage() + + "), so the annotations could not be checked."; } - return null; - } - private static boolean sameDirectory(File a, File b) { - try { - return a.getCanonicalFile().equals(b.getCanonicalFile()); - } catch (IOException ex) { - return a.getAbsoluteFile().equals(b.getAbsoluteFile()); + File emitted = new File(projectDir, "target/classes/" + ANNOTATION_HINTS_RESOURCE); + if (!emitted.isFile()) { + return "No " + ANNOTATION_HINTS_RESOURCE + " was written under " + + projectDir.getName() + "/target/classes."; } - } - - /** - * Whether this module runs {@code process-annotations} somewhere it can - * actually see compiled classes. - * - *

Being declared is not enough. {@code ProcessAnnotationsMojo} returns - * immediately when {@code skip} is set, and again when its output directory - * does not exist -- which is the case for every phase before {@code compile}. - * An execution that is skipped, or bound to {@code generate-sources}, emits - * no annotation resource at all, so migrating against it would delete the - * working properties and leave nothing behind.

- */ - static boolean bindsProcessAnnotations(org.apache.maven.project.MavenProject p) { - java.util.List plugins = p.getBuildPlugins(); - if (plugins == null) { - return false; + Properties produced = new Properties(); + try (FileInputStream in = new FileInputStream(emitted)) { + produced.load(in); + } catch (IOException ex) { + return "Could not read " + emitted + ": " + ex.getMessage(); } - for (org.apache.maven.model.Plugin plugin : plugins) { - if (!"codenameone-maven-plugin".equals(plugin.getArtifactId())) { - continue; - } - for (org.apache.maven.model.PluginExecution e : plugin.getExecutions()) { - if (e.getGoals() == null || !e.getGoals().contains("process-annotations")) { - continue; - } - if (!phaseSeesCompiledClasses(e.getPhase())) { - continue; - } - if (isSkipped(e.getConfiguration()) || isSkipped(plugin.getConfiguration())) { - continue; - } - return true; + List absent = new ArrayList(); + for (String key : migratedKeys) { + if (produced.getProperty(key) == null) { + absent.add(key); } } - return false; - } - - /** - * The default lifecycle from {@code compile} onward -- the phases by which - * {@code target/classes} exists. - */ - private static final java.util.List PHASES_WITH_CLASSES = - java.util.Arrays.asList("compile", "process-classes", - "generate-test-sources", "process-test-sources", - "generate-test-resources", "process-test-resources", - "test-compile", "process-test-classes", "test", - "prepare-package", "package", - "pre-integration-test", "integration-test", "post-integration-test", - "verify", "install", "deploy"); - - /** - * @param phase the execution's phase, or null to accept the goal's own - * default of {@code process-classes} - */ - private static boolean phaseSeesCompiledClasses(String phase) { - if (phase == null || phase.trim().length() == 0) { - return true; + if (!absent.isEmpty()) { + return "These hints were annotated but did not come back out of the build: " + absent; } - return PHASES_WITH_CLASSES.contains(phase.trim().toLowerCase()); + return null; } - /** Reads {@code true} out of a plugin or execution configuration. */ - private static boolean isSkipped(Object configuration) { - if (!(configuration instanceof org.codehaus.plexus.util.xml.Xpp3Dom)) { - return false; - } - org.codehaus.plexus.util.xml.Xpp3Dom skip = - ((org.codehaus.plexus.util.xml.Xpp3Dom) configuration).getChild("skip"); - return skip != null && "true".equalsIgnoreCase(String.valueOf(skip.getValue()).trim()); - } + /** Name of the resource the annotation processor emits into target/classes. */ + private static final String ANNOTATION_HINTS_RESOURCE = + "META-INF/codenameone/build-hints.properties"; /** * Whether the codenameone-core on this project's compile classpath actually @@ -742,6 +723,11 @@ private static String read(File f) throws IOException { return sb.toString(); } + /** Restores a source file after a failed migration. */ + private static void writeSource(File f, String content) throws IOException { + write(f, content); + } + private static void write(File f, String content) throws IOException { Writer w = new OutputStreamWriter(new FileOutputStream(f), "UTF-8"); try { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 61db8909385..159ebd3a080 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -22,15 +22,9 @@ */ package com.codename1.maven; -import org.apache.maven.model.Plugin; -import org.apache.maven.model.PluginExecution; -import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.util.xml.Xpp3Dom; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNull; /// Covers the properties parsing in `MigrateBuildHintsMojo`. @@ -112,63 +106,6 @@ public void aDefaultPackageSourceStillGetsAUsableAnchor() { MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); } - /// process-annotations scans only the output of the module it is bound to, so - /// a binding on a platform or utility module never sees the main class that - /// the common module compiles. Accepting one would let the migration delete - /// the properties and leave annotations nothing reads. - @Test - public void onlyAnEnabledExecutionOnTheOwningModuleCounts() { - assertTrue(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", "process-classes", false))); - // No phase means the goal's own default, process-classes. - assertTrue(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", null, false))); - assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("css", "process-classes", false))); - // Declared but never run is the same as absent for this purpose. - assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", "none", false))); - } - - /// ProcessAnnotationsMojo returns immediately when skip is set, and again - /// when its output directory does not exist -- which is every phase before - /// compile. Such an execution emits no annotation resource, so migrating - /// against it would delete the properties and leave nothing behind. - @Test - public void anExecutionThatCannotSeeCompiledClassesDoesNotCount() { - assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", "generate-sources", false))); - assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", "process-resources", false))); - assertFalse(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", "process-classes", true))); - // compile is the earliest phase where target/classes exists. - assertTrue(MigrateBuildHintsMojo.bindsProcessAnnotations( - moduleBinding("process-annotations", "compile", false))); - } - - private static MavenProject moduleBinding(String goal, String phase, boolean skip) { - PluginExecution e = new PluginExecution(); - if (phase != null) { - e.setPhase(phase); - } - e.addGoal(goal); - if (skip) { - Xpp3Dom config = new Xpp3Dom("configuration"); - Xpp3Dom flag = new Xpp3Dom("skip"); - flag.setValue("true"); - config.addChild(flag); - e.setConfiguration(config); - } - Plugin plugin = new Plugin(); - plugin.setGroupId("com.codenameone"); - plugin.setArtifactId("codenameone-maven-plugin"); - plugin.addExecution(e); - MavenProject p = new MavenProject(); - p.getBuild().addPlugin(plugin); - return p; - } - @Test public void aValueOnlyLineHasNoSeparator() { assertEquals("bare", MigrateBuildHintsMojo.propertyKeyOf("bare")); diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 880a7f61bb1..02e0414c184 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2101,7 +2101,12 @@ private java.util.Map loadAnnotationOwnedHints() { String url = ProjectIO.fsUrl(path); FileSystemStorage fs = FileSystemStorage.getInstance(); if (!fs.exists(url)) { - return out; + // Not built yet. Read the annotations off the source instead -- + // the window right after cn1:migrate-build-hints is exactly when + // the source declares them and no build has emitted anything, and + // treating them as unowned there would offer Add for a hint the + // annotations already set, which fails the next build. + return annotationOwnedHintsFromSource(); } in = fs.openInputStream(url); String text = Util.readToString(in, "ISO-8859-1"); @@ -2125,4 +2130,140 @@ private java.util.Map loadAnnotationOwnedHints() { } return out; } + + /// Reads the build hint annotations straight off the main class. + /// + /// Only the attribute *names* are needed -- what each hint is set to does not + /// matter, only that an annotation owns it -- so this scans the annotation + /// list above the class declaration for `name =` at the top level of each + /// annotation's parentheses and maps those to hint names through the catalog. + /// Values are skipped wholesale, so a comma or bracket inside a string cannot + /// confuse it. + private java.util.Map annotationOwnedHintsFromSource() { + java.util.Map out = new java.util.HashMap<>(); + String main = settings == null ? null : settings.get("codename1.mainName"); + String pkg = settings == null ? null : settings.get("codename1.packageName"); + if (binding == null || binding.projectDir() == null || main == null || main.isEmpty()) { + return out; + } + String rel = (pkg == null || pkg.isEmpty() ? "" : pkg.replace('.', '/') + "/") + main; + for (String ext : new String[]{".java", ".kt"}) { + for (String root : new String[]{"/src/main/java/", "/src/main/kotlin/", "/src/"}) { + String path = binding.projectDir() + root + rel + ext; + String text = readIfPresent(path); + if (text == null) { + continue; + } + collectAnnotationOwnedHints(text, out); + return out; + } + } + return out; + } + + private String readIfPresent(String path) { + InputStream in = null; + try { + String url = ProjectIO.fsUrl(path); + FileSystemStorage fs = FileSystemStorage.getInstance(); + if (!fs.exists(url)) { + return null; + } + in = fs.openInputStream(url); + return Util.readToString(in, "UTF-8"); + } catch (Exception ex) { + Log.e(ex); + return null; + } finally { + Util.cleanup(in); + } + } + + /// Maps every `@Group(attr = ...)` on the main class to the hints it sets. + static void collectAnnotationOwnedHints(String source, java.util.Map out) { + for (com.codename1.build.shared.BuildHints.Hint h : com.codename1.build.shared.BuildHints.entries()) { + if (!h.isAnnotated()) { + continue; + } + String marker = "@" + h.group().annotationSimpleName(); + int at = source.indexOf(marker); + while (at >= 0) { + int open = source.indexOf('(', at); + if (open < 0) { + break; + } + String args = balancedArgs(source, open); + if (args != null && declaresAttribute(args, h.attr())) { + out.put(com.codename1.build.shared.BuildHints.canonicalName(h.name()), + marker + "(" + h.attr() + ")"); + break; + } + at = source.indexOf(marker, at + marker.length()); + } + } + } + + /// The text inside the parentheses starting at `open`, or null when unbalanced. + private static String balancedArgs(String source, int open) { + int depth = 0; + boolean inString = false; + for (int i = open; i < source.length(); i++) { + char c = source.charAt(i); + if (inString) { + if (c == '\\') { + i++; + } else if (c == '"') { + inString = false; + } + continue; + } + if (c == '"') { + inString = true; + } else if (c == '(' || c == '{' || c == '[') { + depth++; + } else if (c == ')' || c == '}' || c == ']') { + depth--; + if (depth == 0) { + return source.substring(open + 1, i); + } + } + } + return null; + } + + /// Whether `args` assigns `attr` at the top level, ignoring anything inside a + /// nested value or a string. + private static boolean declaresAttribute(String args, String attr) { + int depth = 0; + boolean inString = false; + StringBuilder word = new StringBuilder(); + for (int i = 0; i < args.length(); i++) { + char c = args.charAt(i); + if (inString) { + if (c == '\\') { + i++; + } else if (c == '"') { + inString = false; + } + continue; + } + if (c == '"') { + inString = true; + } else if (c == '(' || c == '{' || c == '[') { + depth++; + } else if (c == ')' || c == '}' || c == ']') { + depth--; + } else if (depth == 0 && c == '=' && (i + 1 >= args.length() || args.charAt(i + 1) != '=')) { + if (word.toString().trim().equals(attr)) { + return true; + } + word.setLength(0); + } else if (depth == 0 && c == ',') { + word.setLength(0); + } else if (depth == 0) { + word.append(c); + } + } + return false; + } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 825cfd4d7dd..f41fbd9eb9a 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -157,6 +157,44 @@ public void aliasesResolveToTheirCanonicalName() { com.codename1.build.shared.BuildHints.canonicalName("ios.pods")); } + /** + * Right after cn1:migrate-build-hints the source declares the annotations and + * no build has emitted the manifest yet. Treating them as unowned there would + * offer Add for a hint the annotations already set, and the next build would + * fail on the duplicate declaration -- so the source is read directly. + */ + @Test + public void annotationsAreFoundInSourceBeforeTheProjectIsBuilt() { + String src = "package com.example;\n" + + "import com.codename1.annotations.buildhints.*;\n" + + "@Ios(pods = {\"A\", \"B\"}, teamId = \"T\")\n" + + "@Desktop(titleBar = DesktopTitleBar.NATIVE)\n" + + "public class MyApp extends Lifecycle {\n}\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned); + assertEquals("@Ios(pods)", owned.get("ios.pods")); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + assertEquals("@Desktop(titleBar)", owned.get("desktop.titleBar")); + assertTrue(owned.get("ios.objC") == null, "an attribute nobody set is not owned"); + } + + /** + * Attribute detection must not be fooled by a value that contains an equals + * sign, a comma or a bracket -- android.xpermissions is XML, and gradleDep + * entries carry both. + */ + @Test + public void valuesContainingSeparatorsDoNotCreatePhantomOwnership() { + String src = "@Android(xpermissions = \"\")\n" + + "public class MyApp {}\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned); + assertEquals("@Android(xpermissions)", owned.get("android.xpermissions")); + assertTrue(owned.get("android.gradleDep") == null, + "nothing inside a string value may register as an attribute"); + assertTrue(owned.get("android.debug") == null); + } + @Test public void searchStillMatchesOnNameAndDescription() { BuildHintCatalog catalog = BuildHintCatalog.load(); From b1dff60641f3a488a39a598a17172f2e772a2c0b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:17:12 +0300 Subject: [PATCH 015/115] Round-trip the rollback snapshot, and stop trusting a stale manifest The rollback snapshot was taken with the UTF-8 read helper and restored with the ISO-8859-1 writer, so any raw high byte in an unrelated property -- an accented codename1.displayName, say -- came back changed while the goal reported that both files were put back exactly as they were. It is snapshotted with the properties encoding now, and the two helpers are explicit about which encoding they use rather than one of them being the default. Verified on a generated project carrying a raw 0xE9: after a failed migration the settings file is byte-identical and the byte is still there. The Settings tool consulted the main-class source only when the emitted manifest was missing. The manifest is a build artifact and goes stale in both directions -- absent right after a migration, and out of date the moment an attribute is added to a project that was built earlier -- so a newly annotated hint looked unowned and Add wrote the duplicate declaration the next build refuses. The source is read every time now, because it is the only current statement of what the annotations declare, and the manifest is merged on top for its origins. The union is the safe direction: over-reporting ownership only withholds an editor, while under-reporting breaks the build. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 21 +++++++++++++++++-- .../settings/CodenameOneSettings.java | 20 ++++++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 0bcfabe7ee9..b59e42597de 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -231,7 +231,7 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException String originalSettings; try { originalSource = read(source); - originalSettings = read(settingsFile); + originalSettings = readProperties(settingsFile); insertAnnotations(source, rendered.toString(), settings.getProperty("codename1.mainName", "").trim()); removeMigratedLines(settingsFile, migratedKeys); @@ -709,9 +709,26 @@ private static void writeProperties(File f, String content) throws IOException { } } + /** + * Reads a properties file as ISO-8859-1, matching {@link #writeProperties}. + * + *

The rollback snapshot has to round-trip byte for byte. Taking it through + * the UTF-8 {@link #read} and restoring it with the ISO-8859-1 writer would + * mangle any raw high byte in an unrelated property -- an accented + * {@code codename1.displayName}, say -- while the goal reports that both + * files were put back as they were.

+ */ + private static String readProperties(File f) throws IOException { + return read(f, PROPERTIES_ENCODING); + } + private static String read(File f) throws IOException { + return read(f, "UTF-8"); + } + + private static String read(File f, String encoding) throws IOException { StringBuilder sb = new StringBuilder(); - BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f), encoding)); try { int c; while ((c = r.read()) >= 0) { diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 02e0414c184..d2c7bfc2b9e 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2098,15 +2098,23 @@ private java.util.Map loadAnnotationOwnedHints() { String path = binding.projectDir() + "/target/classes/META-INF/codenameone/build-hints.properties"; InputStream in = null; try { + // Always start from the source, because it is the only current + // statement of what the annotations declare. The manifest is a build + // artifact and goes stale in both directions: absent right after + // cn1:migrate-build-hints, and out of date the moment an attribute is + // added to a project that was built earlier. Trusting it alone left + // the newly annotated hint looking unowned, and Add then wrote the + // duplicate declaration the next build refuses. + // + // The manifest is merged in on top for its origins; the union is the + // safe direction, since over-reporting ownership only withholds an + // editor, while under-reporting breaks the build. + out.putAll(annotationOwnedHintsFromSource()); + String url = ProjectIO.fsUrl(path); FileSystemStorage fs = FileSystemStorage.getInstance(); if (!fs.exists(url)) { - // Not built yet. Read the annotations off the source instead -- - // the window right after cn1:migrate-build-hints is exactly when - // the source declares them and no build has emitted anything, and - // treating them as unowned there would offer Add for a hint the - // annotations already set, which fails the next build. - return annotationOwnedHintsFromSource(); + return out; } in = fs.openInputStream(url); String text = Util.readToString(in, "ISO-8859-1"); From 76cd1dcb5fa472a32dfdd2d670b1124ee9ea0abf Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:30:32 +0300 Subject: [PATCH 016/115] Preserve source bytes, and stop comments confusing the annotation scanner The main class was read and written as UTF-8, so a project whose sources use a different encoding had its whole file reinterpreted while the annotations were spliced in: a raw byte in a comment or a string literal came back changed even when the migration succeeded. Reading project.build.sourceEncoding would only narrow that to projects which declare it correctly. Instead both ends use ISO-8859-1, which maps every byte 0-255 to the same char, so decode -> splice ASCII -> encode reproduces the original bytes exactly whatever the real encoding is. The markers this code looks for -- package, import, the class declaration -- are ASCII, and every ASCII-compatible encoding decodes those identically under that scheme. Verified on a generated project whose main class carries three raw 0xE9 bytes in a comment, making it invalid UTF-8: all three survive the migration and the annotations are still inserted correctly. The Settings tool's source scanner skipped strings but not comments, so a comment carrying an unmatched delimiter -- @Ios(/* required for issue ( */ teamId = "x") -- lost the annotation's boundary and left teamId editable, which is the case that writes the duplicate declaration. It now skips line comments, block comments and character literals as well, through one shared helper used by both the balancer and the attribute scan. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 22 ++++- .../settings/CodenameOneSettings.java | 80 +++++++++++++------ .../settings/BuildHintCatalogTest.java | 32 ++++++++ 3 files changed, 108 insertions(+), 26 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index b59e42597de..40bdd1e880f 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -722,10 +722,28 @@ private static String readProperties(File f) throws IOException { return read(f, PROPERTIES_ENCODING); } + /** + * Reads a source file byte-transparently. + * + *

ISO-8859-1 maps every byte 0-255 to the same char, so decoding with it, + * splicing in text that is pure ASCII, and encoding back reproduces the + * original bytes exactly -- whatever the project's real source encoding is. + * Hard-coding UTF-8 here reinterpreted the whole file, so a raw byte in a + * comment or a string literal came back changed even when the migration + * succeeded, and reading {@code project.build.sourceEncoding} would only + * narrow that to projects that declare it correctly.

+ * + *

The markers this class searches for -- {@code package}, {@code import}, + * the class declaration -- are ASCII, and every ASCII-compatible encoding + * decodes them identically under this scheme.

+ */ private static String read(File f) throws IOException { - return read(f, "UTF-8"); + return read(f, SOURCE_BYTE_TRANSPARENT_ENCODING); } + /** See {@link #read(File)}: byte-transparent, not a claim about the file. */ + private static final String SOURCE_BYTE_TRANSPARENT_ENCODING = "ISO-8859-1"; + private static String read(File f, String encoding) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f), encoding)); @@ -746,7 +764,7 @@ private static void writeSource(File f, String content) throws IOException { } private static void write(File f, String content) throws IOException { - Writer w = new OutputStreamWriter(new FileOutputStream(f), "UTF-8"); + Writer w = new OutputStreamWriter(new FileOutputStream(f), SOURCE_BYTE_TRANSPARENT_ENCODING); try { w.write(content); } finally { diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index d2c7bfc2b9e..4e5d7405e62 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2212,22 +2212,21 @@ static void collectAnnotationOwnedHints(String source, java.util.Map i) { + i = skipped - 1; continue; } - if (c == '"') { - inString = true; - } else if (c == '(' || c == '{' || c == '[') { + char c = source.charAt(i); + if (c == '(' || c == '{' || c == '[') { depth++; } else if (c == ')' || c == '}' || c == ']') { depth--; @@ -2240,28 +2239,23 @@ private static String balancedArgs(String source, int open) { } /// Whether `args` assigns `attr` at the top level, ignoring anything inside a - /// nested value or a string. + /// nested value, a string, a character literal or a comment. private static boolean declaresAttribute(String args, String attr) { int depth = 0; - boolean inString = false; StringBuilder word = new StringBuilder(); for (int i = 0; i < args.length(); i++) { - char c = args.charAt(i); - if (inString) { - if (c == '\\') { - i++; - } else if (c == '"') { - inString = false; - } + int skipped = skipNonCode(args, i); + if (skipped > i) { + i = skipped - 1; continue; } - if (c == '"') { - inString = true; - } else if (c == '(' || c == '{' || c == '[') { + char c = args.charAt(i); + if (c == '(' || c == '{' || c == '[') { depth++; } else if (c == ')' || c == '}' || c == ']') { depth--; - } else if (depth == 0 && c == '=' && (i + 1 >= args.length() || args.charAt(i + 1) != '=')) { + } else if (depth == 0 && c == '=' + && (i + 1 >= args.length() || args.charAt(i + 1) != '=')) { if (word.toString().trim().equals(attr)) { return true; } @@ -2274,4 +2268,42 @@ private static boolean declaresAttribute(String args, String attr) { } return false; } + + /// If a string, character literal or comment starts at `i`, the index just + /// past it; otherwise `i`. + private static int skipNonCode(String s, int i) { + char c = s.charAt(i); + if (c == '"') { + for (int j = i + 1; j < s.length(); j++) { + if (s.charAt(j) == '\\') { + j++; + } else if (s.charAt(j) == '"') { + return j + 1; + } + } + return s.length(); + } + if (c == '\'') { + for (int j = i + 1; j < s.length(); j++) { + if (s.charAt(j) == '\\') { + j++; + } else if (s.charAt(j) == '\'') { + return j + 1; + } + } + return s.length(); + } + if (c == '/' && i + 1 < s.length()) { + char n = s.charAt(i + 1); + if (n == '/') { + int nl = s.indexOf('\n', i); + return nl < 0 ? s.length() : nl; + } + if (n == '*') { + int close = s.indexOf("*/", i + 2); + return close < 0 ? s.length() : close + 2; + } + } + return i; + } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index f41fbd9eb9a..45f7a3db8e2 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -195,6 +195,38 @@ public void valuesContainingSeparatorsDoNotCreatePhantomOwnership() { assertTrue(owned.get("android.debug") == null); } + /** + * A comment inside an annotation can carry an unmatched delimiter. Counting + * it as syntax loses the annotation's boundary, and the hint it owns stays + * editable -- so Add writes the duplicate the next build refuses. + */ + @Test + public void commentsInsideAnAnnotationDoNotBreakOwnership() { + String src = "@Ios(/* required for issue ( */ teamId = \"T\")\n" + + "public class MyApp {}\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + } + + @Test + public void lineCommentsAndCharLiteralsDoNotBreakOwnership() { + String src = "@Ios(\n" + + " // a stray ) in a line comment\n" + + " teamId = \"T\",\n" + + " urlScheme = \"x\")\n" + + "public class MyApp {}\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + assertEquals("@Ios(urlScheme)", owned.get("ios.urlScheme")); + + String withChar = "@Android(xpermissions = \"a\") // ')'\npublic class MyApp {}\n"; + java.util.Map owned2 = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(withChar, owned2); + assertEquals("@Android(xpermissions)", owned2.get("android.xpermissions")); + } + @Test public void searchStillMatchesOnNameAndDescription() { BuildHintCatalog catalog = BuildHintCatalog.load(); From 8be6d5f661b95a3c688ee3b0c8a22a3a2bcfff9c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 17:20:22 +0300 Subject: [PATCH 017/115] Refuse a build whose annotations were never processed The P1 is about the feature, not the migration convenience: a mojo's default phase does not add an execution to a project, so an existing application that follows the package documentation and adopts the annotations compiles cleanly and ships with every annotated hint missing. Nothing said so. CN1BuildMojo now checks, when no annotation manifest was found, whether the application classes carry build hint annotations at all -- read out of the class file's annotation table, so it sees what the compiler emitted rather than what the source appears to say. If they do, the build fails with the execution to add. Both a directory and a jar are scanned, because a reactor `package` build hands the dependency module's jar rather than its output directory, which is exactly the shape this has to work in. The package documentation now states the requirement too. Verified on a generated project: with the binding removed and one @Ios on the main class the build refuses; with the binding restored it applies the hint and carries on. Two more from the same review: - Verification accepted a manifest an earlier build had left behind, so with processing now skipped or unbound the check passed against a stale file, the properties were deleted, and the next clean build dropped the hints. The resource is removed before the nested build, so what is checked is what that invocation produced. - The Settings source scan matched only the imported simple name, missing the equally valid `@com.codename1.annotations.buildhints.Ios(...)`. Both spellings are matched now, with the name boundary checked so `@Ios` cannot match `@IosPrivacy`. The boundary test is hand-rolled because Character.isJavaIdentifierPart is outside the API subset this class compiles against -- the bytecode compliance gate caught that. Co-Authored-By: Claude Opus 5 (1M context) --- .../annotations/buildhints/package-info.java | 16 +++ .../build/shared/BuildHintCodeGenerator.java | 16 +++ .../com/codename1/maven/CN1BuildMojo.java | 124 +++++++++++++++++- .../maven/MigrateBuildHintsMojo.java | 11 +- .../settings/CodenameOneSettings.java | 53 ++++++-- .../settings/BuildHintCatalogTest.java | 26 ++++ 6 files changed, 231 insertions(+), 15 deletions(-) diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/package-info.java b/CodenameOne/src/com/codename1/annotations/buildhints/package-info.java index f040888412c..a4c36dbcccf 100644 --- a/CodenameOne/src/com/codename1/annotations/buildhints/package-info.java +++ b/CodenameOne/src/com/codename1/annotations/buildhints/package-info.java @@ -46,6 +46,22 @@ /// continues to work exactly as before. Setting the same hint in both places is /// a build error. /// +/// A project generated recently already runs the goal that turns these into +/// build hints. An older one may not: a goal's default phase does not add an +/// execution to a project, so the annotations would compile and then be +/// ignored. The build refuses rather than shipping without them, and the module +/// that compiles the main class needs: +/// +/// ```xml +/// +/// cn1-process-classes +/// process-classes +/// +/// process-annotations +/// +/// +/// ``` +/// /// Generated from com.codename1.build.shared.BuildHints by /// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and /// re-run scripts/gen-build-hint-annotations.sh. diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java index 067e2c4c14f..626ec0c9867 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java @@ -376,6 +376,22 @@ private static String packageInfoSource(Map> by + "`codenameone_settings.properties`, which continues to work exactly as " + "before. Setting the same hint in both places is a build error.", "")); sb.append("///\n"); + sb.append(doc("A project generated recently already runs the goal that turns these into " + + "build hints. An older one may not: a goal's default phase does not add an " + + "execution to a project, so the annotations would compile and then be ignored. " + + "The build refuses rather than shipping without them, and the module that " + + "compiles the main class needs:", "")); + sb.append("///\n"); + sb.append("/// ```xml\n"); + sb.append("/// \n"); + sb.append("/// cn1-process-classes\n"); + sb.append("/// process-classes\n"); + sb.append("/// \n"); + sb.append("/// process-annotations\n"); + sb.append("/// \n"); + sb.append("/// \n"); + sb.append("/// ```\n"); + sb.append("///\n"); sb.append(GENERATED_NOTE); sb.append("package ").append(PKG).append(";\n"); return sb.toString(); diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index 8c01a5432f7..363e4b9adbe 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -2548,7 +2548,8 @@ private SortedProperties mergeRequiredProperties(String libraryName, Properties * written by the annotation processor on every build and deleted by it when * the last annotation goes away, so it always reflects the current source.

*/ - private void mergeAnnotationBuildHints(Properties target, List classpathElements) { + private void mergeAnnotationBuildHints(Properties target, List classpathElements) + throws MojoFailureException { if (target == null || classpathElements == null) { return; } @@ -2587,6 +2588,127 @@ private void mergeAnnotationBuildHints(Properties target, List classpath return; } } + // Nothing was applied. If the compiled classes carry build hint + // annotations anyway, the processor never ran -- a mojo's defaultPhase + // does not add an execution to a project's POM, so an app that adopts the + // annotations without binding process-annotations compiles cleanly and + // ships with every annotated hint missing. Refuse rather than build that. + String annotated = classCarryingBuildHintAnnotations(classpathElements); + if (annotated != null) { + throw new MojoFailureException(annotated + " carries build hint annotations, but no " + + ANNOTATION_HINTS_RESOURCE + " was produced, so none of them reached this " + + "build.\n\nThe cn1 process-annotations goal has to run on the module that " + + "compiles it:\n" + + " \n" + + " cn1-process-classes\n" + + " process-classes\n" + + " \n" + + " process-annotations\n" + + " \n" + + " "); + } + } + + /** + * The first application class found carrying a build hint annotation, or null. + * + *

Read straight out of the class file's annotation table rather than from + * source, so it sees exactly what the compiler emitted.

+ */ + private String classCarryingBuildHintAnnotations(List classpathElements) { + java.util.Collection descriptors = + com.codename1.build.shared.BuildHintAnnotationBinding.descriptors(); + for (String element : classpathElements) { + File f = new File(element); + if (f.isDirectory()) { + String hit = findAnnotatedClass(f, descriptors); + if (hit != null) { + return hit; + } + continue; + } + // A reactor `package` build hands us the dependency module's jar + // rather than its output directory, which is exactly the shape this + // check has to work in. + if (f.isFile() && f.getName().endsWith(".jar")) { + String hit = findAnnotatedClassInJar(f, descriptors); + if (hit != null) { + return hit; + } + } + } + return null; + } + + private String findAnnotatedClassInJar(File jar, java.util.Collection descriptors) { + try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(jar)) { + java.util.Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + java.util.zip.ZipEntry entry = entries.nextElement(); + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; + } + try (InputStream in = zip.getInputStream(entry)) { + String name = carriesBuildHintAnnotation(in, descriptors) + ? entry.getName() : null; + if (name != null) { + return name.substring(0, name.length() - ".class".length()) + .replace('/', '.'); + } + } + } + } catch (IOException | RuntimeException ex) { + getLog().debug("cn1: could not scan " + jar + ": " + ex.getMessage()); + } + return null; + } + + private boolean carriesBuildHintAnnotation(InputStream in, + java.util.Collection descriptors) + throws IOException { + final boolean[] seen = {false}; + new org.objectweb.asm.ClassReader(in).accept( + new org.objectweb.asm.ClassVisitor(org.objectweb.asm.Opcodes.ASM9) { + @Override + public org.objectweb.asm.AnnotationVisitor visitAnnotation( + String desc, boolean visible) { + if (descriptors.contains(desc)) { + seen[0] = true; + } + return null; + } + }, + org.objectweb.asm.ClassReader.SKIP_CODE + | org.objectweb.asm.ClassReader.SKIP_DEBUG + | org.objectweb.asm.ClassReader.SKIP_FRAMES); + return seen[0]; + } + + private String findAnnotatedClass(File dir, java.util.Collection descriptors) { + File[] children = dir.listFiles(); + if (children == null) { + return null; + } + for (File f : children) { + if (f.isDirectory()) { + String hit = findAnnotatedClass(f, descriptors); + if (hit != null) { + return hit; + } + continue; + } + if (!f.getName().endsWith(".class")) { + continue; + } + try (InputStream in = new FileInputStream(f)) { + if (carriesBuildHintAnnotation(in, descriptors)) { + return f.getName().substring(0, f.getName().length() - ".class".length()); + } + } catch (IOException | RuntimeException ex) { + getLog().debug("cn1: could not scan " + f + ": " + ex.getMessage()); + } + } + return null; } /** diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 40bdd1e880f..7dcbc474ec3 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -292,6 +292,16 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException private String verifyAnnotationsAreProcessed(File projectDir, List migratedKeys) { getLog().info("cn1: building " + projectDir.getName() + " to confirm the annotations produce the hints..."); + // Delete any manifest an earlier build left behind first. Checking that + // the file exists and holds the right keys proves nothing if it was + // already there: with processing now skipped or unbound the nested build + // leaves it untouched, the check passes, the properties are deleted, and + // the next clean build removes the stale artifact and the hints with it. + File emitted = new File(projectDir, "target/classes/" + ANNOTATION_HINTS_RESOURCE); + if (emitted.isFile() && !emitted.delete()) { + return "Could not remove the previous " + ANNOTATION_HINTS_RESOURCE + + ", so this build's output could not be told apart from it."; + } File pom = new File(projectDir, "pom.xml"); InvocationRequest request = new DefaultInvocationRequest(); request.setPomFile(pom.isFile() ? pom : new File(project.getBasedir(), "pom.xml")); @@ -311,7 +321,6 @@ private String verifyAnnotationsAreProcessed(File projectDir, List migra + "), so the annotations could not be checked."; } - File emitted = new File(projectDir, "target/classes/" + ANNOTATION_HINTS_RESOURCE); if (!emitted.isFile()) { return "No " + ANNOTATION_HINTS_RESOURCE + " was written under " + projectDir.getName() + "/target/classes."; diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 4e5d7405e62..be14fccbed1 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2193,20 +2193,38 @@ static void collectAnnotationOwnedHints(String source, java.util.Map= 0) { - int open = source.indexOf('(', at); - if (open < 0) { - break; - } - String args = balancedArgs(source, open); - if (args != null && declaresAttribute(args, h.attr())) { - out.put(com.codename1.build.shared.BuildHints.canonicalName(h.name()), - marker + "(" + h.attr() + ")"); - break; + String simple = h.group().annotationSimpleName(); + // Both spellings are valid: the imported simple name, and the fully + // qualified one, which needs no import. Missing the qualified form + // left the hint editable and Add wrote the duplicate declaration. + String[] markers = { + "@" + simple, + "@com.codename1.annotations.buildhints." + simple, + }; + boolean found = false; + for (int m = 0; m < markers.length && !found; m++) { + int at = source.indexOf(markers[m]); + while (at >= 0) { + // "@Ios" must not match "@IosPrivacy": the next character has + // to end the name. + int after = at + markers[m].length(); + if (after < source.length() && continuesAName(source.charAt(after))) { + at = source.indexOf(markers[m], after); + continue; + } + int open = source.indexOf('(', at); + if (open < 0) { + break; + } + String args = balancedArgs(source, open); + if (args != null && declaresAttribute(args, h.attr())) { + out.put(com.codename1.build.shared.BuildHints.canonicalName(h.name()), + "@" + simple + "(" + h.attr() + ")"); + found = true; + break; + } + at = source.indexOf(markers[m], after); } - at = source.indexOf(marker, at + marker.length()); } } } @@ -2269,6 +2287,15 @@ private static boolean declaresAttribute(String args, String attr) { return false; } + /// Whether `c` could continue a Java identifier. + /// + /// Hand-rolled because Character.isJavaIdentifierPart is outside the + /// Codename One API subset, and this class is compiled as app code. + private static boolean continuesAName(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') || c == '_' || c == '$'; + } + /// If a string, character literal or comment starts at `i`, the index just /// past it; otherwise `i`. private static int skipNonCode(String s, int i) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 45f7a3db8e2..8abfb969e3e 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -227,6 +227,32 @@ public void lineCommentsAndCharLiteralsDoNotBreakOwnership() { assertEquals("@Android(xpermissions)", owned2.get("android.xpermissions")); } + /** + * The fully qualified spelling needs no import and is equally valid. Missing + * it left the hint editable, and Add then wrote the duplicate declaration. + */ + @Test + public void fullyQualifiedAnnotationsAreRecognized() { + String src = "@com.codename1.annotations.buildhints.Ios(teamId = \"T\")\n" + + "public class MyApp {}\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + } + + /** `@Ios` must not match `@IosPrivacy`, which is a different annotation. */ + @Test + public void aSimpleNameDoesNotMatchALongerAnnotation() { + String src = "@IosPrivacy(cameraUsageDescription = \"why\")\n" + + "public class MyApp {}\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned); + assertEquals("@IosPrivacy(cameraUsageDescription)", + owned.get("ios.NSCameraUsageDescription")); + assertTrue(owned.get("ios.teamId") == null, + "@IosPrivacy must not be read as @Ios"); + } + @Test public void searchStillMatchesOnNameAndDescription() { BuildHintCatalog catalog = BuildHintCatalog.load(); From fb5a6c7169c652710839bd4c963868891ab27501 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:00:13 +0300 Subject: [PATCH 018/115] Do not refuse a build over an annotation that sets nothing Three from the same review. @Ios() with every member left at its default is legal Java -- it is what is left after the last attribute is deleted -- and the processor emits the manifest for it, stamped with the main class but carrying no hint. The merge judged by the hint count, read that as "the processor never ran", and refused the build until the annotation itself was deleted. The manifest's presence is what proves processing happened, so that is what the check now reads; the refusal still fires for the case it exists for, annotations in the compiled classes with no manifest anywhere. The migration goal restored both files when the verification build failed, but not when the mutation itself did. If the annotations went in and the properties rewrite then failed -- unwritable file, full disk, a partial write -- the project was left declaring the same hint twice, which is exactly the state the next build refuses to compile: worse than not having migrated at all. The restore is one helper now and runs for either failure. A dangling javadoc left over from a removed method went with it. pr.yml ignores scripts/** and re-includes a fixed list, so a PR touching only the catalog gate, its miner, or its baseline started no workflow at all -- the gate could be broken, or its empty baseline relaxed, without ever running. The five files are re-included in both the pull_request and push filters. The merge test needed the annotated class present alongside the empty manifest to trip the refusal at all; without it the test passed against the bug it was written for. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/pr.yml | 18 ++ .../com/codename1/maven/CN1BuildMojo.java | 14 +- .../maven/MigrateBuildHintsMojo.java | 58 +++-- .../maven/AnnotationBuildHintMergeTest.java | 204 ++++++++++++++++++ .../BuildHintAnnotationProcessorTest.java | 15 ++ 5 files changed, 288 insertions(+), 21 deletions(-) create mode 100644 maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e70275e9810..84eb9531958 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -27,6 +27,15 @@ on: - 'scripts/ci/retry.sh' - 'scripts/ci/apt-get-update.sh' - 'scripts/ci/apt-get-install.sh' + # The build hint gates are run from this workflow and nowhere else, and one + # of them holds an empty baseline. Ignoring the whole directory meant a + # change that breaks a gate, or that adds a line to the baseline, could + # merge without the gate it weakens ever running. + - 'scripts/check-build-hint-catalog.sh' + - 'scripts/check-build-hint-catalog.py' + - 'scripts/build_hint_miner.py' + - 'scripts/build-hint-catalog-baseline.txt' + - 'scripts/gen-build-hint-annotations.sh' - '!docs/**' - '!**/*.md' - '!.github/workflows/developer-guide-docs.yml' @@ -59,6 +68,15 @@ on: - 'scripts/ci/retry.sh' - 'scripts/ci/apt-get-update.sh' - 'scripts/ci/apt-get-install.sh' + # The build hint gates are run from this workflow and nowhere else, and one + # of them holds an empty baseline. Ignoring the whole directory meant a + # change that breaks a gate, or that adds a line to the baseline, could + # merge without the gate it weakens ever running. + - 'scripts/check-build-hint-catalog.sh' + - 'scripts/check-build-hint-catalog.py' + - 'scripts/build_hint_miner.py' + - 'scripts/build-hint-catalog-baseline.txt' + - 'scripts/gen-build-hint-annotations.sh' - '!docs/**' - '!**/*.md' - '!.github/workflows/developer-guide-docs.yml' diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index 363e4b9adbe..562e03a27d1 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -2562,6 +2562,13 @@ private void mergeAnnotationBuildHints(Properties target, List classpath ? main.trim() : pkg.trim() + "." + main.trim(); } } + // The manifest's presence, not its contents, is what proves the processor + // ran. An annotation with every member left at its default -- @Ios() after + // the last attribute was deleted -- is legal Java, and the processor emits + // a manifest carrying only the main-class stamp for it. Judging by the hint + // count alone would read that as "never processed" and refuse a build that + // is in fact perfectly configured. + boolean processed = false; for (String element : classpathElements) { Properties found = readAnnotationHints(new File(element)); if (found == null) { @@ -2575,6 +2582,7 @@ private void mergeAnnotationBuildHints(Properties target, List classpath + " -- they were generated for " + stamped); continue; } + processed = true; int applied = 0; for (String key : found.stringPropertyNames()) { if (!key.startsWith("codename1.arg.")) { @@ -2588,7 +2596,11 @@ private void mergeAnnotationBuildHints(Properties target, List classpath return; } } - // Nothing was applied. If the compiled classes carry build hint + if (processed) { + getLog().debug("cn1: annotations were processed and set no build hint"); + return; + } + // No manifest at all. If the compiled classes carry build hint // annotations anyway, the processor never ran -- a mojo's defaultPhase // does not add an execution to a project's POM, so an app that adopts the // annotations without binding process-annotations compiles cleanly and diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 7dcbc474ec3..005d258d59f 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -232,28 +232,30 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException try { originalSource = read(source); originalSettings = readProperties(settingsFile); + } catch (IOException ex) { + throw new MojoExecutionException("Migration failed: " + ex.getMessage(), ex); + } + + // Anything that throws from here on has to put both files back. The half + // that fails is not always the second one: if the annotations go in and + // the properties rewrite then fails -- an unwritable file, a full disk, a + // partial write -- the project is left declaring the same hint twice, + // which is exactly the state the next build refuses to compile. Leaving + // the developer with that is worse than not migrating at all. + try { insertAnnotations(source, rendered.toString(), settings.getProperty("codename1.mainName", "").trim()); removeMigratedLines(settingsFile, migratedKeys); - } catch (IOException ex) { - throw new MojoExecutionException("Migration failed: " + ex.getMessage(), ex); + } catch (IOException | RuntimeException ex) { + throw new MojoExecutionException("Migration failed, so " + source.getName() + " and " + + settingsFile.getName() + " have been put back as they were: " + + ex.getMessage() + + restore(source, originalSource, settingsFile, originalSettings), ex); } String missing = verifyAnnotationsAreProcessed(projectDir, migratedKeys); if (missing != null) { - StringBuilder restoreFailed = new StringBuilder(); - try { - write(source, originalSource); - } catch (IOException ex) { - restoreFailed.append("\nCould not restore ").append(source).append(": ") - .append(ex.getMessage()); - } - try { - writeProperties(settingsFile, originalSettings); - } catch (IOException ex) { - restoreFailed.append("\nCould not restore ").append(settingsFile).append(": ") - .append(ex.getMessage()); - } + String restoreFailed = restore(source, originalSource, settingsFile, originalSettings); throw new MojoFailureException("The annotations were added but the build did not turn " + "them into build hints, so " + source.getName() + " and " + settingsFile.getName() + " have been put back as they were.\n\n" @@ -275,13 +277,29 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException } /** - * Whether any module in the reactor binds the {@code process-annotations} - * goal. + * Puts both files back as they were. * - *

Checked across the reactor rather than on {@code project} because this - * goal is an aggregator, so {@code project} is the root POM while the binding - * lives in the common module.

+ * @return an empty string when both were restored, otherwise a description of + * what could not be, to append to the failure being reported */ + private String restore(File source, String originalSource, + File settingsFile, String originalSettings) { + StringBuilder failed = new StringBuilder(); + try { + write(source, originalSource); + } catch (IOException ex) { + failed.append("\nCould not restore ").append(source).append(": ") + .append(ex.getMessage()); + } + try { + writeProperties(settingsFile, originalSettings); + } catch (IOException ex) { + failed.append("\nCould not restore ").append(settingsFile).append(": ") + .append(ex.getMessage()); + } + return failed.toString(); + } + /** * Runs the project's own build over the module that holds the main class and * checks that every migrated hint came back out of it. diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java new file mode 100644 index 00000000000..7c32c5f3dc2 --- /dev/null +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Where the hints that came from annotations meet the build request. + * + *

The merge has to distinguish three states that look alike from here: the + * processor ran and produced hints, it ran and produced none, and it never ran + * at all. Only the third is a broken build, and getting that wrong either ships + * an app with its build configuration silently missing or refuses one that is + * perfectly configured.

+ */ +public class AnnotationBuildHintMergeTest { + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private static final String RESOURCE = "META-INF/codenameone/build-hints.properties"; + + @Test + public void hintsFromTheManifestReachTheBuildRequest() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n" + + "codename1.arg.ios.pods=Alamofire\n"); + Properties target = new Properties(); + + merge(target, classes, "MyApp", "com.example"); + + assertEquals("Alamofire", target.getProperty("codename1.arg.ios.pods")); + } + + /** + * A manifest with no hints in it is what {@code @Ios()} produces once the + * last attribute is deleted -- still legal, still processed. Judging by the + * hint count alone read that as "the processor never ran" and refused every + * build until the annotation itself was removed. + */ + @Test + public void anEmptyManifestIsProofTheProcessorRan() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n"); + // The annotated class has to be there too -- that is the whole situation: + // an annotation the compiler recorded and a manifest that carries no hint + // for it. Without the class the refusal path has nothing to trip on and + // the test would pass against the bug it exists for. + writeAnnotatedClass(classes); + Properties target = new Properties(); + + merge(target, classes, "MyApp", "com.example"); + + assertTrue("no hint should have been applied", target.isEmpty()); + } + + /** + * The refusal still has to fire for the case it exists for: annotations in + * the compiled classes with no manifest anywhere means the goal is unbound, + * and every annotated hint is missing from the build. + */ + @Test + public void annotatedClassesWithNoManifestAreRefused() throws Exception { + File classes = tmp.newFolder(); + writeAnnotatedClass(classes); + try { + merge(new Properties(), classes, "MyApp", "com.example"); + fail("expected the build to be refused"); + } catch (InvocationTargetException ex) { + assertTrue(String.valueOf(ex.getCause().getMessage()), + ex.getCause().getMessage().contains("process-annotations")); + } + } + + /** No annotations and no manifest is an ordinary properties-file project. */ + @Test + public void aProjectWithNoAnnotationsIsLeftAlone() throws Exception { + Properties target = new Properties(); + merge(target, tmp.newFolder(), "MyApp", "com.example"); + assertTrue(target.isEmpty()); + } + + /** A manifest stamped for another project is somebody else's configuration. */ + @Test + public void aManifestStampedForAnotherMainClassIsIgnored() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.other.TheirApp\n" + + "codename1.arg.ios.pods=Alamofire\n"); + Properties target = new Properties(); + + merge(target, classes, "MyApp", "com.example"); + + assertNull(target.getProperty("codename1.arg.ios.pods")); + } + + // ------------------------------------------------------------------ + // helpers + // ------------------------------------------------------------------ + + private File manifest(String body) throws Exception { + File classes = tmp.newFolder(); + File out = new File(classes, RESOURCE); + out.getParentFile().mkdirs(); + try (Writer w = new OutputStreamWriter(new FileOutputStream(out), "ISO-8859-1")) { + w.write(body); + } + return classes; + } + + /** Compiles a main class carrying one build hint annotation. */ + private void writeAnnotatedClass(File classes) throws Exception { + com.codename1.maven.annotations.JavaSourceCompiler.compile( + com.codename1.maven.annotations.JavaSourceCompiler.singleSource( + "com.example.MyApp", + "package com.example;\n" + + "import com.codename1.annotations.buildhints.Ios;\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "public class MyApp {\n}\n"), + classes, + Arrays.asList(new File(Class.forName("com.codename1.annotations.buildhints.Ios") + .getProtectionDomain().getCodeSource().getLocation().toURI()))); + } + + /** + * Drives the shipped merge rather than a restatement of it, so a change to + * the rule is a change to what this asserts. + */ + private void merge(Properties target, File classesDir, String mainName, String pkg) + throws Exception { + CN1BuildMojo mojo = new CN1BuildMojo(); + + Properties settings = new Properties(); + settings.setProperty("codename1.mainName", mainName); + settings.setProperty("codename1.packageName", pkg); + Field props = findField(mojo.getClass(), "properties"); + props.setAccessible(true); + props.set(mojo, settings); + + List cp = Collections.singletonList(classesDir.getAbsolutePath()); + Method m = findMethod(mojo.getClass(), "mergeAnnotationBuildHints", + Properties.class, List.class); + m.setAccessible(true); + m.invoke(mojo, target, cp); + } + + private static Field findField(Class type, String name) throws NoSuchFieldException { + for (Class c = type; c != null; c = c.getSuperclass()) { + try { + return c.getDeclaredField(name); + } catch (NoSuchFieldException keepLooking) { + // up the chain + } + } + throw new NoSuchFieldException(name); + } + + private static Method findMethod(Class type, String name, Class... args) + throws NoSuchMethodException { + for (Class c = type; c != null; c = c.getSuperclass()) { + try { + return c.getDeclaredMethod(name, args); + } catch (NoSuchMethodException keepLooking) { + // up the chain + } + } + throw new NoSuchMethodException(name); + } +} diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 8ea6d65479e..b7fe0094f31 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -262,6 +262,21 @@ public void aCommentedOutPropertyIsNotAConflict() throws Exception { assertFalse(ctx.hasErrors()); } + /// An annotation with every member left at its default is legal Java, and + /// `@Ios()` is what is left after the last attribute is deleted. The manifest + /// is still emitted for it, carrying only the main-class stamp: its presence + /// is what tells the build that processing ran at all, and dropping it here + /// would make a harmless annotation indistinguishable from an unbound goal. + @Test + public void anAnnotationWithNoMembersStillEmitsAStampedManifest() throws Exception { + Properties p = hintsOf("@Ios()"); + assertEquals(MAIN, p.getProperty("cn1.buildHints.mainClass")); + for (String key : p.stringPropertyNames()) { + assertFalse("no hint should have been written, got " + key, + key.startsWith("codename1.arg.")); + } + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ From 51fb76af1588bc640b36ea3b6d942b8f2fb46822 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:56:29 +0300 Subject: [PATCH 019/115] Tell a current annotation manifest from last build's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three from the same review, all cases where something looked applied and was not. The main-class stamp says which class produced the manifest, not when. Nothing clears target/classes between builds, so a project that ran process-annotations once and then stopped -- goal unbound, skipped, moved to a phase that no longer runs -- keeps a manifest naming the right class while the annotations beside it change. The merge accepted it, applied the older values, and the guard added for exactly this never fired. The processor now records a fingerprint of the annotations it read, taken over the raw members rather than the hints they convert into so it moves for anything the developer can change: a different value, an added or removed attribute, a whole annotation gained or lost. The merge recomputes it from the main class on the classpath -- directory or jar -- and refuses a manifest that does not match, naming it as left over from an earlier build. It refuses only on positive evidence: no main class name, no class file, no recorded fingerprint, or an unreadable one, and the manifest is taken at face value as before. A hint set by @Hardening reached the settings only in createAntProject, which runs after the early hardening pre-flight and after hardeningCacheKey is read for the Android up-to-date check. The early pass computed "unhardened" from the properties file while the finished build recorded "hardened:...", so the keys never matched and an up-to-date APK was rebuilt on every invocation. Worse, an unsupported hardening request made through an annotation escaped the refusal that pass exists to perform. Annotation hints are merged there too, before the -D overlay so a command-line hint still wins. Properties.load turns € in the settings file into a real euro sign, and migrate-build-hints writes the source back through ISO-8859-1 to keep the untouched part byte-identical -- so emitting the character raw wrote '?' for anything unmappable and a high byte for anything else, corrupting a UTF-8 source. The verification build would not have noticed: it checks that the hint came back, not what its value was. Non-ASCII is written as \uXXXX, which Java and Kotlin both accept, and a backslash before one still survives -- Java recognises a unicode escape only after an even number of backslashes, so there is a test pinning that rather than leaving it to luck. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/CN1BuildMojo.java | 96 ++++++++++++++++++- .../maven/MigrateBuildHintsMojo.java | 19 +++- .../BuildHintAnnotationProcessor.java | 87 +++++++++++++++++ .../maven/AnnotationBuildHintMergeTest.java | 75 +++++++++++++++ .../MigrateBuildHintsPropertyParsingTest.java | 29 ++++++ 5 files changed, 303 insertions(+), 3 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index 562e03a27d1..9511736a5c6 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -321,10 +321,22 @@ private void applyHardeningPreflight() throws MojoFailureException { getLog().debug("Could not read codenameone_settings.properties for hardening pre-flight", ex); } } + // A hint set by @Hardening reaches the settings only in createAntProject, which runs after + // the Android up-to-date short-circuit below and after hardeningCacheKey is read from it. + // Without this the early pass computes "unhardened" from the file while the completed build + // records "hardened:...", the two never match, and an up-to-date APK is rebuilt on every + // invocation -- and, worse, an unsupported hardening request made through an annotation + // escapes the refusal this early pass exists to perform. + try { + mergeAnnotationBuildHints(settings, project.getCompileClasspathElements()); + } catch (org.apache.maven.artifact.DependencyResolutionRequiredException ex) { + getLog().debug("Could not read annotation build hints for the hardening pre-flight", ex); + } // Overlay -D command-line hints (e.g. -Dcodename1.arg.harden.level=standard) so an explicit // hardening request made only on the command line is seen by this early check -- and, because this // runs before the Android up-to-date cache short-circuit, is not silently dropped when a prior APK // is newer than the sources (getSourcesModificationTime does not account for build hints). + // After the annotations, so -D still wins over one. overlayCommandLineBuildHints(settings); applyHardeningPreflight(settings); } @@ -2569,6 +2581,7 @@ private void mergeAnnotationBuildHints(Properties target, List classpath // count alone would read that as "never processed" and refuse a build that // is in fact perfectly configured. boolean processed = false; + String stale = null; for (String element : classpathElements) { Properties found = readAnnotationHints(new File(element)); if (found == null) { @@ -2582,6 +2595,20 @@ private void mergeAnnotationBuildHints(Properties target, List classpath + " -- they were generated for " + stamped); continue; } + // The stamp says which class produced this file, not when. Nothing + // clears target/classes between builds, so a project that ran the + // processor once and then stopped -- goal unbound, skipped, or bound + // to a phase that no longer runs -- keeps a manifest naming the right + // class while the annotations beside it have changed. Comparing the + // recorded fingerprint against the compiled class is what tells those + // apart; without it the build silently ships the older values and the + // guard below never runs. + String mismatch = digestMismatch(new File(element), expectedMain, found); + if (mismatch != null) { + getLog().debug("cn1: ignoring build hints from " + element + " -- " + mismatch); + stale = mismatch; + continue; + } processed = true; int applied = 0; for (String key : found.stringPropertyNames()) { @@ -2607,8 +2634,12 @@ private void mergeAnnotationBuildHints(Properties target, List classpath // ships with every annotated hint missing. Refuse rather than build that. String annotated = classCarryingBuildHintAnnotations(classpathElements); if (annotated != null) { - throw new MojoFailureException(annotated + " carries build hint annotations, but no " - + ANNOTATION_HINTS_RESOURCE + " was produced, so none of them reached this " + throw new MojoFailureException(annotated + " carries build hint annotations, but " + + (stale == null + ? "no " + ANNOTATION_HINTS_RESOURCE + " was produced" + : "the only " + ANNOTATION_HINTS_RESOURCE + " on the classpath is left " + + "over from an earlier build (" + stale + ")") + + ", so none of them reached this " + "build.\n\nThe cn1 process-annotations goal has to run on the module that " + "compiles it:\n" + " \n" @@ -2621,6 +2652,67 @@ private void mergeAnnotationBuildHints(Properties target, List classpath } } + /** + * Why a manifest cannot have come from the class beside it, or null when it can. + * + *

Answered only when both halves are actually available: with no + * {@code codename1.mainName}, no class file for it in this classpath element, + * or no recorded fingerprint, there is nothing to compare and the manifest is + * taken at face value -- the same as before this check existed. It refuses + * only on positive evidence of a mismatch.

+ */ + private String digestMismatch(File element, String expectedMain, Properties manifest) { + String recorded = manifest.getProperty( + com.codename1.maven.processors.BuildHintAnnotationProcessor.SOURCE_DIGEST_KEY); + if (expectedMain == null || recorded == null || recorded.length() == 0) { + return null; + } + try { + com.codename1.maven.annotations.AnnotatedClass cls = readClass(element, expectedMain); + if (cls == null) { + return null; + } + String actual = com.codename1.maven.processors.BuildHintAnnotationProcessor + .sourceDigest(cls); + if (recorded.equals(actual)) { + return null; + } + return "it was generated from a different set of annotations on " + + expectedMain + " than the one compiled into " + element; + } catch (IOException | com.codename1.maven.annotations.ProcessingException ex) { + // Unreadable is not evidence of staleness. + getLog().debug("cn1: could not fingerprint " + expectedMain + " in " + element, ex); + return null; + } + } + + /** Reads one compiled class out of a classpath directory or jar. */ + private com.codename1.maven.annotations.AnnotatedClass readClass(File element, String binaryName) + throws IOException, com.codename1.maven.annotations.ProcessingException { + String path = binaryName.replace('.', '/') + ".class"; + if (element.isDirectory()) { + File f = new File(element, path.replace('/', File.separatorChar)); + if (!f.isFile()) { + return null; + } + try (InputStream in = new FileInputStream(f)) { + return com.codename1.maven.annotations.ClassScanner.readClass(in, f); + } + } + if (element.isFile() && element.getName().endsWith(".jar")) { + try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(element)) { + java.util.zip.ZipEntry entry = zip.getEntry(path); + if (entry == null) { + return null; + } + try (InputStream in = zip.getInputStream(entry)) { + return com.codename1.maven.annotations.ClassScanner.readClass(in, element); + } + } + } + return null; + } + /** * The first application class found carrying a build hint annotation, or null. * diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 005d258d59f..f5dfd75555b 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -484,6 +484,15 @@ static String enumConstant(String wire) { * {@code implementation "com.x:y:${'$'}{version}"} would either fail to * compile as an unresolved reference or silently resolve to something else. * Java has no such construct, so the escape is emitted only for Kotlin.

+ * + *

Everything outside ASCII is written as a {@code \}{@code uXXXX} escape, + * which both languages accept. {@code Properties.load} turns a + * {@code \}{@code u20ac} in the file into a real euro sign, and the source is + * written back through ISO-8859-1 to keep the rest of the file byte-identical + * -- so emitting the character raw would replace it with {@code ?}, or write a + * high byte that corrupts a UTF-8 source. Neither shows up in the + * verification build, which checks that the hint came back, not what its + * value was.

*/ static String quoteFor(String s, boolean kotlin) { StringBuilder sb = new StringBuilder("\""); @@ -498,7 +507,15 @@ static String quoteFor(String s, boolean kotlin) { case '$': sb.append(kotlin ? "\\$" : "$"); break; - default: sb.append(c); + default: + if (c < 0x20 || c > 0x7e) { + sb.append("\\u"); + for (int shift = 12; shift >= 0; shift -= 4) { + sb.append(Character.forDigit((c >> shift) & 0xf, 16)); + } + } else { + sb.append(c); + } } } return sb.append('"').toString(); diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 83bf3482f59..b1d48c568b1 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -72,6 +72,18 @@ public class BuildHintAnnotationProcessor extends AbstractAnnotationProcessor { /// foreign copy on the classpath can be recognised rather than merged. private static final String MAIN_CLASS_KEY = "cn1.buildHints.mainClass"; + /// Digest of the annotations this file was generated from. + /// + /// The main-class stamp only says *which* class produced it, which is the + /// same class an out-of-date copy names. Nothing removes `target/classes` + /// between builds, so a project that ran this processor once and then stopped + /// -- the goal unbound, skipped, or bound to a phase that no longer runs -- + /// keeps a manifest that looks entirely valid while the annotations beside it + /// have moved on. Recording what it was built from lets the consumer compare + /// it against the class file actually on the classpath and refuse instead of + /// shipping last week's configuration. + public static final String SOURCE_DIGEST_KEY = "cn1.buildHints.sourceDigest"; + /// hint name to value, sorted so the emitted bytes are stable. private final Map hints = new TreeMap(); /// hint name to "@Ios(pods)". @@ -358,6 +370,79 @@ private String wireValue(AnnotatedClass cls, String descriptor, String member, O /// Not `Properties.store`: it writes a timestamp comment, so the bytes would /// differ on every build. That churns the resource in every incremental /// build and defeats the staged-jar staleness comparison in `CN1BuildMojo`. + /// A stable fingerprint of every build hint annotation on `cls`. + /// + /// Taken over the raw annotation members rather than over the hints they + /// convert into, so it changes for anything the developer can change: a + /// different value, an added or removed attribute, a whole annotation + /// gained or lost. Two builds of the same source produce the same string; + /// there is no timestamp or path in it. + public static String sourceDigest(AnnotatedClass cls) throws ProcessingException { + StringBuilder sb = new StringBuilder(); + Set known = new HashSet(BuildHintAnnotationBinding.descriptors()); + // Sorted, because the class file's annotation order is the source's and a + // reordering is not a change. + for (String descriptor : new TreeMap( + cls.getClassAnnotations()).keySet()) { + if (!known.contains(descriptor)) { + continue; + } + sb.append(descriptor).append('{'); + AnnotationValues values = cls.getClassAnnotation(descriptor); + for (Map.Entry e + : new TreeMap(values.all()).entrySet()) { + sb.append(e.getKey()).append('='); + renderForDigest(e.getValue(), sb); + sb.append(';'); + } + sb.append('}'); + } + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + byte[] digest = md.digest(sb.toString().getBytes("UTF-8")); + StringBuilder hex = new StringBuilder(); + for (byte b : digest) { + hex.append(Character.forDigit((b >> 4) & 0xf, 16)); + hex.append(Character.forDigit(b & 0xf, 16)); + } + return hex.toString(); + } catch (java.security.NoSuchAlgorithmException | UnsupportedEncodingException ex) { + throw new ProcessingException("Could not fingerprint the build hint annotations", ex); + } + } + + /// The type is part of the rendering, so an int 1 and the string "1" -- which + /// print alike but are different annotations -- do not fingerprint alike. + private static void renderForDigest(Object value, StringBuilder sb) { + if (value == null) { + sb.append("null"); + } else if (value instanceof String[]) { + // How ASM delivers an enum member: {descriptor, CONSTANT_NAME}. + String[] e = (String[]) value; + sb.append("enum:").append(e.length > 0 ? e[0] : "") + .append('.').append(e.length > 1 ? e[1] : ""); + } else if (value instanceof List) { + sb.append('['); + for (Object item : (List) value) { + renderForDigest(item, sb); + sb.append(','); + } + sb.append(']'); + } else if (value instanceof AnnotationValues) { + AnnotationValues nested = (AnnotationValues) value; + sb.append(nested.getDescriptor()).append('{'); + for (Map.Entry e + : new TreeMap(nested.all()).entrySet()) { + sb.append(e.getKey()).append('='); + renderForDigest(e.getValue(), sb); + sb.append(';'); + } + sb.append('}'); + } else { + sb.append(value.getClass().getName()).append(':').append(value); + } + } + private byte[] serialize(ProcessorContext ctx) throws ProcessingException { StringBuilder sb = new StringBuilder(); sb.append("# Generated from build hint annotations by the Codename One Maven plugin.\n"); @@ -366,6 +451,8 @@ private byte[] serialize(ProcessorContext ctx) throws ProcessingException { if (main != null) { sb.append(MAIN_CLASS_KEY).append('=').append(escape(main)).append('\n'); } + sb.append(SOURCE_DIGEST_KEY).append('=') + .append(sourceDigest(annotated.get(0))).append('\n'); for (Map.Entry e : hints.entrySet()) { sb.append(escape(BuildHints.ARG_PREFIX + e.getKey())).append('=') .append(escape(e.getValue())).append('\n'); diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java index 7c32c5f3dc2..c84d5571ba2 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java @@ -129,6 +129,63 @@ public void aManifestStampedForAnotherMainClassIsIgnored() throws Exception { assertNull(target.getProperty("codename1.arg.ios.pods")); } + /** + * The processor ran once and then stopped -- goal unbound, skipped, or moved + * to a phase that no longer runs -- and the annotation changed afterwards. + * Nothing clears {@code target/classes}, so the old manifest is still there, + * still naming the right main class. Trusting it ships the previous values + * and hides the fact that the goal is not running at all. + */ + @Test + public void aManifestThatDoesNotMatchTheCompiledAnnotationsIsRefused() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n" + + "cn1.buildHints.sourceDigest=" + digestOf("@Ios(teamId = \"OLD\")") + "\n" + + "codename1.arg.ios.teamId=OLD\n"); + writeAnnotatedClass(classes); // compiled with teamId = ABCDE12345 + + Properties target = new Properties(); + try { + merge(target, classes, "MyApp", "com.example"); + fail("expected the stale manifest to be refused"); + } catch (InvocationTargetException ex) { + assertTrue(String.valueOf(ex.getCause().getMessage()), + ex.getCause().getMessage().contains("left over from an earlier build")); + } + assertNull("the stale value must not have been applied", + target.getProperty("codename1.arg.ios.teamId")); + } + + /** The fingerprint of the annotations the build actually compiled matches. */ + @Test + public void aManifestGeneratedFromTheCompiledAnnotationsIsAccepted() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n" + + "cn1.buildHints.sourceDigest=" + + digestOf("@Ios(teamId = \"ABCDE12345\")") + "\n" + + "codename1.arg.ios.teamId=ABCDE12345\n"); + writeAnnotatedClass(classes); + + Properties target = new Properties(); + merge(target, classes, "MyApp", "com.example"); + + assertEquals("ABCDE12345", target.getProperty("codename1.arg.ios.teamId")); + } + + /** + * A manifest with no fingerprint in it cannot be judged, so it is taken at + * face value rather than refused on a guess. + */ + @Test + public void aManifestWithNoFingerprintIsStillTrusted() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n" + + "codename1.arg.ios.teamId=OLD\n"); + writeAnnotatedClass(classes); + + Properties target = new Properties(); + merge(target, classes, "MyApp", "com.example"); + + assertEquals("OLD", target.getProperty("codename1.arg.ios.teamId")); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ @@ -201,4 +258,22 @@ private static Method findMethod(Class type, String name, Class... args) } throw new NoSuchMethodException(name); } + + /** The fingerprint the processor would record for a main class annotated so. */ + private String digestOf(String annotations) throws Exception { + File dir = tmp.newFolder(); + com.codename1.maven.annotations.JavaSourceCompiler.compile( + com.codename1.maven.annotations.JavaSourceCompiler.singleSource( + "com.example.MyApp", + "package com.example;\n" + + "import com.codename1.annotations.buildhints.*;\n" + + annotations + "\n" + + "public class MyApp {\n}\n"), + dir, + Arrays.asList(new File(Class.forName("com.codename1.annotations.buildhints.Ios") + .getProtectionDomain().getCodeSource().getLocation().toURI()))); + return com.codename1.maven.processors.BuildHintAnnotationProcessor.sourceDigest( + com.codename1.maven.annotations.ClassScanner.readClass( + new File(dir, "com/example/MyApp.class"))); + } } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 159ebd3a080..65d72fdbd9f 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -110,4 +110,33 @@ public void aDefaultPackageSourceStillGetsAUsableAnchor() { public void aValueOnlyLineHasNoSeparator() { assertEquals("bare", MigrateBuildHintsMojo.propertyKeyOf("bare")); } + + /// `Properties.load` turns a `\\u20ac` in the settings file into a real euro + /// sign, and the migrated source is written back through ISO-8859-1 to keep + /// the untouched part of the file byte-identical. Emitting the character raw + /// would write `?` for anything ISO-8859-1 cannot map, and a high byte for + /// anything it can -- which corrupts a UTF-8 source. The verification build + /// would not notice: it checks that the hint came back, not its value. + @Test + public void aNonAsciiCharacterIsWrittenAsAnAsciiEscape() { + assertEquals("\"a\\u20acb\"", MigrateBuildHintsMojo.quoteFor("a\u20acb", false)); + assertEquals("\"a\\u20acb\"", MigrateBuildHintsMojo.quoteFor("a\u20acb", true)); + // Latin-1 is mappable and still escaped: the source may well be UTF-8. + assertEquals("\"caf\\u00e9\"", MigrateBuildHintsMojo.quoteFor("caf\u00e9", false)); + } + + /// A backslash before an escaped character has to stay a backslash. In Java a + /// unicode escape is recognised before parsing and only after an even number + /// of backslashes, so the doubled pair plus our own opener is what makes this + /// come out right rather than a coincidence. + @Test + public void aBackslashBeforeAnEscapedCharacterSurvives() { + assertEquals("\"\\\\\\u00e9\"", MigrateBuildHintsMojo.quoteFor("\\\u00e9", false)); + } + + /// Control characters without a short escape would otherwise go in raw. + @Test + public void aControlCharacterIsEscaped() { + assertEquals("\"\\u0001\"", MigrateBuildHintsMojo.quoteFor("\u0001", false)); + } } From 9c5a58622dc6c578f915b77577856df2bd4b5331 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 22:52:04 +0300 Subject: [PATCH 020/115] Give the same answer whether or not target/classes was cleaned The fingerprint added last round covers the annotations and nothing else, so editing codenameone_settings.properties cannot invalidate it. With processing skipped or unbound, a line added for a hint an annotation already sets left a manifest that still matched -- and the merge quietly replaced the value the developer had just written. The next clean build regenerated the manifest, the processor saw both declarations, and the build failed. Same source, two different outcomes, decided by whether target/classes happened to be cleaned. The merge now refuses a hint the properties file also declares instead of overlaying it, with the message the processor would have given. Aliases count as the same setting, so and.captureRecord in the file still collides with @Android(captureRecord). This is a safety net rather than the primary check: when the processor runs it has already failed for the same reason and can point at the offending line. It only matters in the builds the processor never saw, which are exactly the ones that were silently wrong. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/CN1BuildMojo.java | 54 +++++++++++++++++++ .../maven/AnnotationBuildHintMergeTest.java | 48 +++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index 9511736a5c6..5f9c8828289 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -2615,6 +2615,19 @@ private void mergeAnnotationBuildHints(Properties target, List classpath if (!key.startsWith("codename1.arg.")) { continue; } + // The processor refuses a hint declared as an annotation and as a + // properties line, but it can only refuse the builds it runs in. + // The fingerprint above covers the annotations and nothing else, + // so with processing skipped a line added to the properties file + // afterwards leaves a manifest that still matches -- and this + // overlay would quietly replace the value the developer just + // wrote, until the next clean build regenerated the manifest and + // failed. Same declaration, same answer, whether or not + // target/classes happened to be cleaned. + String conflict = conflictingPropertiesDeclaration(target, key, found); + if (conflict != null) { + throw new MojoFailureException(conflict); + } target.setProperty(key, found.getProperty(key)); applied++; } @@ -2652,6 +2665,47 @@ private void mergeAnnotationBuildHints(Properties target, List classpath } } + /** + * The duplicate-declaration message for a hint set by an annotation and by a + * properties line, or null when there is no clash. + * + *

Only reached when the processor did not run this build; when it did, it + * has already failed for the same reason and with more to say -- it can point + * at the offending line. An alias counts as the same setting, matching what + * the processor checks, so declaring {@code and.captureRecord} in the file + * still collides with {@code @Android(captureRecord)}.

+ */ + private String conflictingPropertiesDeclaration(Properties settings, String key, + Properties manifest) { + String name = key.substring("codename1.arg.".length()); + java.util.Set names = new java.util.LinkedHashSet(); + names.add(name); + for (com.codename1.build.shared.BuildHints.Hint h + : com.codename1.build.shared.BuildHints.entries()) { + if (name.equals(h.aliasOf()) + || name.equals(com.codename1.build.shared.BuildHints.canonicalName(h.name()))) { + names.add(h.name()); + } + } + for (String candidate : names) { + String candidateKey = "codename1.arg." + candidate; + String fromFile = settings.getProperty(candidateKey); + if (fromFile == null) { + continue; + } + String origin = manifest.getProperty("cn1.buildHints.origin." + name); + return candidateKey + " is declared twice.\n" + + " annotation : " + (origin == null ? "on the main class" : origin) + + " = " + manifest.getProperty(key) + "\n" + + " properties : codenameone_settings.properties\n" + + " " + candidateKey + "=" + fromFile + "\n" + + " A build hint has one source of truth. Delete the properties line and " + + "keep the annotation, or delete the annotation attribute and keep the line. " + + "(-D" + candidateKey + "=... overrides either and is not a conflict.)"; + } + return null; + } + /** * Why a manifest cannot have come from the class beside it, or null when it can. * diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java index c84d5571ba2..4f642fe1ba1 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java @@ -186,6 +186,54 @@ public void aManifestWithNoFingerprintIsStillTrusted() throws Exception { assertEquals("OLD", target.getProperty("codename1.arg.ios.teamId")); } + /** + * The fingerprint covers the annotations and nothing else, so editing the + * properties file cannot invalidate it. With processing skipped, a line added + * for a hint an annotation already sets left a manifest that still matched -- + * and the overlay quietly replaced the value the developer had just written, + * until the next clean build regenerated the manifest and failed instead. + */ + @Test + public void aPropertiesLineForAnAnnotatedHintIsRefusedNotOverwritten() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n" + + "cn1.buildHints.sourceDigest=" + + digestOf("@Ios(teamId = \"ABCDE12345\")") + "\n" + + "cn1.buildHints.origin.ios.teamId=@Ios(teamId)\n" + + "codename1.arg.ios.teamId=ABCDE12345\n"); + writeAnnotatedClass(classes); + + Properties target = new Properties(); + target.setProperty("codename1.arg.ios.teamId", "FROMFILE"); + try { + merge(target, classes, "MyApp", "com.example"); + fail("expected the duplicate declaration to be refused"); + } catch (InvocationTargetException ex) { + String message = String.valueOf(ex.getCause().getMessage()); + assertTrue(message, message.contains("declared twice")); + assertTrue(message, message.contains("@Ios(teamId)")); + } + assertEquals("the file's value must not have been replaced", + "FROMFILE", target.getProperty("codename1.arg.ios.teamId")); + } + + /** A hint only the file sets is not a conflict -- that is the escape hatch. */ + @Test + public void aPropertiesLineForAnUnannotatedHintIsLeftAlone() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n" + + "cn1.buildHints.sourceDigest=" + + digestOf("@Ios(teamId = \"ABCDE12345\")") + "\n" + + "codename1.arg.ios.teamId=ABCDE12345\n"); + writeAnnotatedClass(classes); + + Properties target = new Properties(); + target.setProperty("codename1.arg.ios.pods", "Alamofire"); + + merge(target, classes, "MyApp", "com.example"); + + assertEquals("Alamofire", target.getProperty("codename1.arg.ios.pods")); + assertEquals("ABCDE12345", target.getProperty("codename1.arg.ios.teamId")); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ From b6f16f24650d1990b38cff41c443f663236e6913 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:31:45 +0300 Subject: [PATCH 021/115] Catalogue the ten build hints the Wear change added Merged master, which brought in #5583 (complications on the watch, and a Wear artifact beside the phone APK). It adds ten hints the builders read, and the catalog gate failed on the merge result: every hint the code reads has to be described, and the empty baseline means there is nowhere to park one. That is the gate working, not a conflict. #5583 was written before the catalog existed, so it had nothing to add its hints to. Each row's type and default come from the call site rather than from the name: android.blockLabel boolean, false android.surfaces.complicationUpdateSeconds int, 0 android.watchModule boolean, true android.watchVersionCode int, no default -- unset means derive from the offset below android.watchVersionCodeOffset int, 100000000 android.wear.complicationsVersion string, 1.2.1 android.wear.tilesVersion string, 1.4.1 android.wear.protoLayoutVersion string, 1.2.1 android.wear.guavaVersion string, 31.1-android watchNative.surfaces.deploymentTarget string, 10.0 Catalogued, not annotated: the catalog has to describe every hint, but exposing one as a typed attribute is a curation decision, and inventing API for somebody else's feature in a merge commit is not that. They are documented, typed and value-checked, and can be annotated later without churn. The one nuance worth recording is watchNative.surfaces.deploymentTarget, whose default is the watch app's floor rather than the extension's: WidgetKit reaches back to watchOS 9, but the extension is embedded in the watch app, so the lower number would advertise support that does not exist. The regenerated developer-guide table is the only other change -- no annotation churn, as intended. Co-Authored-By: Claude Opus 5 (1M context) --- .../_generated-build-hints.adoc | 60 +++++++++++++ .../build/shared/BuildHintsAndroid.java | 87 +++++++++++++++++++ .../build/shared/BuildHintsApple.java | 11 +++ 3 files changed, 158 insertions(+) diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc index 1173af133f9..6eab2c84824 100644 --- a/docs/developer-guide/_generated-build-hints.adoc +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -220,6 +220,12 @@ |_(none)_ |Boolean true/false defaults to false. Disables the external storage (SD card) permission +|android.blockLabel +|boolean +|`false` +|_(none)_ +|Boolean true/false defaults to false. Leaves `android:label` off the generated `` tag so a label set through `android.xapplication_attr` or a merged manifest is the one that survives. Honoured by the wear module's tag as well as the phone's. + |android.blockReadMediaPermissions |boolean |_(none)_ @@ -1026,6 +1032,12 @@ |_(none)_ | +|android.surfaces.complicationUpdateSeconds +|int +|`0` +|_(none)_ +|`UPDATE_PERIOD_SECONDS` on the generated complication service. Zero, the default, means the system never polls on a timer and the complication updates only when the app pushes new data. + |android.surfaces.exactAlarms |boolean |`false` @@ -1110,18 +1122,60 @@ |_(none)_ |Allows overriding the auto generated version number with a custom internal version number specifically used for the XML attribute `android:versionCode` +|android.watchModule +|boolean +|`true` +|_(none)_ +|Boolean true/false defaults to true. Set to false to build the phone app alone in a companion build: the wearable link stays, the watch module is not generated, and the phone output is exactly what it was before the watch app existed. + +|android.watchVersionCode +|int +|_(none)_ +|_(none)_ +|The wear module's version code, stated outright. Play requires it to be higher than the phone's, so a value that is not a whole number above `android.versionCode` fails the build rather than being silently replaced. Unset, it is derived from `android.watchVersionCodeOffset`. + +|android.watchVersionCodeOffset +|int +|`100000000` +|_(none)_ +|How far above the phone's version code the wear module's sits when `android.watchVersionCode` is not set. The default leaves room for the phone app to keep incrementing without ever catching up. + |android.wear |boolean |`false` |_(none)_ | +|android.wear.complicationsVersion +|string +|`1.2.1` +|_(none)_ +|Version of `androidx.wear.watchface:watchface-complications-data-source` added to the wear module. Kept out of `android.gradleDependencies` because that hint feeds the phone module too, and these libraries declare minSdk 26. + +|android.wear.guavaVersion +|string +|`31.1-android` +|_(none)_ +|Version of `com.google.guava:guava` added to the wear module alongside the tiles and complications libraries, which need it at runtime. + +|android.wear.protoLayoutVersion +|string +|`1.2.1` +|_(none)_ +|Version of the `androidx.wear.protolayout` libraries the generated tile service builds its layout with. + |android.wear.standalone |string |_(none)_ |_(none)_ | +|android.wear.tilesVersion +|string +|`1.4.1` +|_(none)_ +|Version of `androidx.wear.tiles` added to the wear module when the app declares a tile. + |android.web_loading_hidden |boolean |`false` @@ -3054,6 +3108,12 @@ |_(none)_ | +|watchNative.surfaces.deploymentTarget +|string +|`10.0` +|_(none)_ +|Deployment target of the WidgetKit extension that carries the watch complication. This is the WATCH APP's floor rather than the extension's own: WidgetKit reaches back to watchOS 9, but the extension is embedded in the watch app, so advertising a version the app itself cannot install on claims support that does not exist. + |win.desktop-vm |string |_(none)_ diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java index ccf306ecffc..f278177df10 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java @@ -301,6 +301,17 @@ static void register(List h) { .consumedBy("AndroidGradleBuilder") .doc("Boolean true/false defaults to false. Disables the external storage (SD card) permission")); + h.add(new Hint("android.blockLabel") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("false") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to false. Leaves `android:label` off the generated " + + "`` tag so a label set through `android.xapplication_attr` or a merged " + + "manifest is the one that survives. Honoured by the wear module's tag as well as the " + + "phone's.")); + h.add(new Hint("android.blockReadMediaPermissions") .group(HintGroup.ANDROID) .type(HintType.BOOLEAN) @@ -1228,6 +1239,16 @@ static void register(List h) { .platform("android") .consumedBy("AndroidGradleBuilder")); + h.add(new Hint("android.surfaces.complicationUpdateSeconds") + .group(HintGroup.ANDROID) + .type(HintType.INT) + .def("0") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("`UPDATE_PERIOD_SECONDS` on the generated complication service. Zero, the default, means " + + "the system never polls on a timer and the complication updates only when the app " + + "pushes new data.")); + h.add(new Hint("android.surfaces.exactAlarms") .group(HintGroup.ANDROID) .type(HintType.BOOLEAN) @@ -1337,6 +1358,36 @@ static void register(List h) { .doc("Allows overriding the auto generated version number with a custom internal version " + "number specifically used for the XML attribute `android:versionCode`")); + h.add(new Hint("android.watchModule") + .group(HintGroup.ANDROID) + .type(HintType.BOOLEAN) + .def("true") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Boolean true/false defaults to true. Set to false to build the phone app alone in a " + + "companion build: the wearable link stays, the watch module is not generated, and the " + + "phone output is exactly what it was before the watch app existed.")); + + h.add(new Hint("android.watchVersionCode") + .group(HintGroup.ANDROID) + .type(HintType.INT) + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("The wear module's version code, stated outright. Play requires it to be higher than the " + + "phone's, so a value that is not a whole number above `android.versionCode` fails the " + + "build rather than being silently replaced. Unset, it is derived from " + + "`android.watchVersionCodeOffset`.")); + + h.add(new Hint("android.watchVersionCodeOffset") + .group(HintGroup.ANDROID) + .type(HintType.INT) + .def("100000000") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("How far above the phone's version code the wear module's sits when " + + "`android.watchVersionCode` is not set. The default leaves room for the phone app to " + + "keep incrementing without ever catching up.")); + h.add(new Hint("android.wear") .group(HintGroup.ANDROID) .type(HintType.BOOLEAN) @@ -1344,6 +1395,42 @@ static void register(List h) { .platform("android") .consumedBy("AndroidGradleBuilder")); + h.add(new Hint("android.wear.complicationsVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("1.2.1") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Version of `androidx.wear.watchface:watchface-complications-data-source` added to the " + + "wear module. Kept out of `android.gradleDependencies` because that hint feeds the " + + "phone module too, and these libraries declare minSdk 26.")); + + h.add(new Hint("android.wear.guavaVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("31.1-android") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Version of `com.google.guava:guava` added to the wear module alongside the tiles and " + + "complications libraries, which need it at runtime.")); + + h.add(new Hint("android.wear.protoLayoutVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("1.2.1") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Version of the `androidx.wear.protolayout` libraries the generated tile service builds " + + "its layout with.")); + + h.add(new Hint("android.wear.tilesVersion") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .def("1.4.1") + .platform("android") + .consumedBy("AndroidGradleBuilder") + .doc("Version of `androidx.wear.tiles` added to the wear module when the app declares a tile.")); + h.add(new Hint("android.wear.standalone") .group(HintGroup.ANDROID) .type(HintType.STRING) diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java index b8abef16dd3..384371d03ae 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java @@ -282,6 +282,17 @@ static void register(List h) { .platform("watch") .consumedBy("WatchNativeBuilder")); + h.add(new Hint("watchNative.surfaces.deploymentTarget") + .group(HintGroup.WATCH_NATIVE) + .type(HintType.STRING) + .def("10.0") + .platform("watch") + .consumedBy("IPhoneBuilder") + .doc("Deployment target of the WidgetKit extension that carries the watch complication. This " + + "is the WATCH APP's floor rather than the extension's own: WidgetKit reaches back to " + + "watchOS 9, but the extension is embedded in the watch app, so advertising a version " + + "the app itself cannot install on claims support that does not exist.")); + h.add(new Hint("watchNative.mainClass") .group(HintGroup.WATCH_NATIVE) .type(HintType.STRING) From eb58dac840f6d3111e262c9ce773b9f3845dac30 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:45:55 +0300 Subject: [PATCH 022/115] Write the new catalog rows in the guide's own voice The developer guide gate treats Vale warnings as errors, and the ten rows added in the previous commit brought seven alerts with them -- the guide requires contractions, so "is not", "cannot", "does not" and "it is" all fail, and "silently" is on the adverb list. Reworded in the catalog, which is where the prose lives; the table is generated from it. The meaning is unchanged in every case, including the two that needed more than a contraction: "a value other than a whole number" rather than "that is not", and "refuses to install on ... support the user never gets" rather than "cannot install on ... does not exist". Vale is clean across all 116 files of the guide. Co-Authored-By: Claude Opus 5 (1M context) --- docs/developer-guide/_generated-build-hints.adoc | 8 ++++---- .../codename1/build/shared/BuildHintsAndroid.java | 12 ++++++------ .../com/codename1/build/shared/BuildHintsApple.java | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc index 6eab2c84824..ed0d17f8384 100644 --- a/docs/developer-guide/_generated-build-hints.adoc +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -1126,19 +1126,19 @@ |boolean |`true` |_(none)_ -|Boolean true/false defaults to true. Set to false to build the phone app alone in a companion build: the wearable link stays, the watch module is not generated, and the phone output is exactly what it was before the watch app existed. +|Boolean true/false defaults to true. Set to false to build the phone app alone in a companion build: the wearable link stays, no watch module is generated, and the phone output matches what it was before the watch app existed. |android.watchVersionCode |int |_(none)_ |_(none)_ -|The wear module's version code, stated outright. Play requires it to be higher than the phone's, so a value that is not a whole number above `android.versionCode` fails the build rather than being silently replaced. Unset, it is derived from `android.watchVersionCodeOffset`. +|The wear module's version code, stated outright. Play requires it to be higher than the phone's, so a value other than a whole number above `android.versionCode` fails the build rather than being replaced without a word. Leave it unset to derive the value from `android.watchVersionCodeOffset`. |android.watchVersionCodeOffset |int |`100000000` |_(none)_ -|How far above the phone's version code the wear module's sits when `android.watchVersionCode` is not set. The default leaves room for the phone app to keep incrementing without ever catching up. +|How far above the phone's version code the wear module's sits when `android.watchVersionCode` is unset. The default leaves room for the phone app to keep incrementing without ever catching up. |android.wear |boolean @@ -3112,7 +3112,7 @@ |string |`10.0` |_(none)_ -|Deployment target of the WidgetKit extension that carries the watch complication. This is the WATCH APP's floor rather than the extension's own: WidgetKit reaches back to watchOS 9, but the extension is embedded in the watch app, so advertising a version the app itself cannot install on claims support that does not exist. +|Deployment target of the WidgetKit extension that carries the watch complication. This is the WATCH APP's floor rather than the extension's own: WidgetKit reaches back to watchOS 9, but the extension is embedded in the watch app, so advertising a version the app itself refuses to install on claims support the user never gets. |win.desktop-vm |string diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java index f278177df10..cfae2b80a6e 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java @@ -1365,8 +1365,8 @@ static void register(List h) { .platform("android") .consumedBy("AndroidGradleBuilder") .doc("Boolean true/false defaults to true. Set to false to build the phone app alone in a " - + "companion build: the wearable link stays, the watch module is not generated, and the " - + "phone output is exactly what it was before the watch app existed.")); + + "companion build: the wearable link stays, no watch module is generated, and the " + + "phone output matches what it was before the watch app existed.")); h.add(new Hint("android.watchVersionCode") .group(HintGroup.ANDROID) @@ -1374,9 +1374,9 @@ static void register(List h) { .platform("android") .consumedBy("AndroidGradleBuilder") .doc("The wear module's version code, stated outright. Play requires it to be higher than the " - + "phone's, so a value that is not a whole number above `android.versionCode` fails the " - + "build rather than being silently replaced. Unset, it is derived from " - + "`android.watchVersionCodeOffset`.")); + + "phone's, so a value other than a whole number above `android.versionCode` fails the " + + "build rather than being replaced without a word. Leave it unset to derive the value " + + "from `android.watchVersionCodeOffset`.")); h.add(new Hint("android.watchVersionCodeOffset") .group(HintGroup.ANDROID) @@ -1385,7 +1385,7 @@ static void register(List h) { .platform("android") .consumedBy("AndroidGradleBuilder") .doc("How far above the phone's version code the wear module's sits when " - + "`android.watchVersionCode` is not set. The default leaves room for the phone app to " + + "`android.watchVersionCode` is unset. The default leaves room for the phone app to " + "keep incrementing without ever catching up.")); h.add(new Hint("android.wear") diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java index 384371d03ae..be49840b404 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java @@ -291,7 +291,7 @@ static void register(List h) { .doc("Deployment target of the WidgetKit extension that carries the watch complication. This " + "is the WATCH APP's floor rather than the extension's own: WidgetKit reaches back to " + "watchOS 9, but the extension is embedded in the watch app, so advertising a version " - + "the app itself cannot install on claims support that does not exist.")); + + "the app itself refuses to install on claims support the user never gets.")); h.add(new Hint("watchNative.mainClass") .group(HintGroup.WATCH_NATIVE) From 9437da72471a246eba6df2115c41ac858b0688e6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 11:52:34 +0300 Subject: [PATCH 023/115] Stop the catalog gate claiming coverage it never checked Six from the same review. The miner reads literals, so a hint whose name is BUILT rather than written was invisible to it -- and the gate then printed "all described" while the hint had no catalog row at all. Two shapes occur: getArg(HINT, null) NativeVerifyOption, HINT="nativeVerify" getArg(platform + ".maps.provider", ...) MapsProviderInjector The first is now resolved: a same-file `static final String` whose value is a literal is substituted. The second cannot be -- the platform is only known at run time -- so it is REPORTED rather than skipped. Every site that builds a name must be listed in scripts/build-hint-computed-sites.txt with what it expands to, and every expansion must be catalogued or match a dynamic pattern, so a new one forces a catalog decision instead of disappearing. Only expressions containing a literal are reported; a helper forwarding a variable gets its literal from its caller, which the ordinary pass already mines. Verified both failure modes fire by removing a site and by pointing one at a hint that does not exist. Five sites, three already covered. The two that were not are exactly the ones named: android.maps.provider, ios.maps.provider, and nativeVerify with its ios/linux/windows overrides -- all now in the catalog. The migration trimmed every value. A string, XML or text block can begin or end with meaningful whitespace: an ios.glAppDelegateHeader ending in a newline after a // comment loses it and comments out whatever the builder generates next, and the verification build does not notice because it checks that the key came back, not what it holds. Trimming is now confined to the scalar types, where the space cannot be part of the value. propertyKeyOf did not decode \uXXXX, so a key written codename1.arg.ios... was read as u0069os.teamId, the original line was left in place, and the migration rolled back over a duplicate declaration it had created itself. The Settings source scan missed a Kotlin `import ... as Alias`, under which the annotation's own name appears nowhere. The hint read as unowned, Add wrote the properties line, and the next process-annotations failed on that duplicate. The simulator published a stale manifest without noticing. Judged on timestamps rather than the fingerprint the native path uses -- recomputing that means parsing the class file's annotation table and the simulator has no bytecode reader -- which is sound in the direction that matters, since process-classes always follows compile within a build. It warns and declines to publish rather than running on the previous values of hints it can actually see. codenameone-build-hint-catalog is a runtime dependency of the plugin, so both release gates now confirm it. Without that a Central deploy that reports failure after publishing, or a truncated R2 copy, could advertise a release whose plugin cannot resolve its own dependency. Co-Authored-By: Claude Opus 5 (1M context) --- .../workflows/release-on-maven-central.yml | 21 +++-- .../com/codename1/impl/javase/Simulator.java | 50 +++++++++++ .../_generated-build-hints.adoc | 38 +++++++- .../build/shared/BuildHintsAndroid.java | 7 ++ .../build/shared/BuildHintsDesktop.java | 14 +++ .../build/shared/BuildHintsGeneral.java | 18 +++- .../codename1/build/shared/BuildHintsIos.java | 14 +++ .../maven/MigrateBuildHintsMojo.java | 49 +++++++++-- .../MigrateBuildHintsPropertyParsingTest.java | 16 ++++ scripts/build-hint-computed-sites.txt | 22 +++++ scripts/build_hint_miner.py | 86 ++++++++++++++++++- scripts/check-build-hint-catalog.py | 42 ++++++++- .../settings/CodenameOneSettings.java | 61 +++++++++++-- .../settings/BuildHintCatalogTest.java | 24 ++++++ 14 files changed, 436 insertions(+), 26 deletions(-) create mode 100644 scripts/build-hint-computed-sites.txt diff --git a/.github/workflows/release-on-maven-central.yml b/.github/workflows/release-on-maven-central.yml index aaa3360b592..4303aedc1dc 100644 --- a/.github/workflows/release-on-maven-central.yml +++ b/.github/workflows/release-on-maven-central.yml @@ -113,12 +113,13 @@ jobs: # "Deployment failed while publishing" even when the bundle was # actually accepted and published. As a safety net for that # false-positive case, poll Maven Central for the key artifacts: - # the codenameone-maven-plugin (proxy for the core release), its - # platform-feature-catalog dependency, and both archetypes. The - # catalog is a separate reactor artifact used by the local builders - # for built-in platform dependency selection, so confirming only the - # plugin could leave a released plugin with an unavailable runtime - # dependency. Skipped when the deploy already reported success (the + # the codenameone-maven-plugin (proxy for the core release), the two + # catalogs it depends on at runtime, and both archetypes. Each catalog + # is a separate reactor artifact -- platform-feature-catalog for + # built-in platform dependency selection, build-hint-catalog for the + # build hint table the plugin reads -- so confirming only the plugin + # could leave a released plugin whose own dependency will not resolve. + # Skipped when the deploy already reported success (the # artifacts may still be propagating from Sonatype Central to repo1; # that propagation can take 30+ minutes and isn't worth blocking on). set +e @@ -127,16 +128,19 @@ jobs: "https://repo1.maven.org/maven2/com/codenameone/codenameone-maven-plugin/${GITHUB_REF_NAME}/codenameone-maven-plugin-${GITHUB_REF_NAME}.pom") catalog_code=$(curl -s -o /dev/null -w "%{http_code}" \ "https://repo1.maven.org/maven2/com/codenameone/codenameone-platform-feature-catalog/${GITHUB_REF_NAME}/codenameone-platform-feature-catalog-${GITHUB_REF_NAME}.pom") + hints_code=$(curl -s -o /dev/null -w "%{http_code}" \ + "https://repo1.maven.org/maven2/com/codenameone/codenameone-build-hint-catalog/${GITHUB_REF_NAME}/codenameone-build-hint-catalog-${GITHUB_REF_NAME}.pom") app_code=$(curl -s -o /dev/null -w "%{http_code}" \ "https://repo1.maven.org/maven2/com/codenameone/cn1app-archetype/${GITHUB_REF_NAME}/cn1app-archetype-${GITHUB_REF_NAME}.pom") lib_code=$(curl -s -o /dev/null -w "%{http_code}" \ "https://repo1.maven.org/maven2/com/codenameone/cn1lib-archetype/${GITHUB_REF_NAME}/cn1lib-archetype-${GITHUB_REF_NAME}.pom") if [ "$plugin_code" = "200" ] && [ "$catalog_code" = "200" ] && \ + [ "$hints_code" = "200" ] && \ [ "$app_code" = "200" ] && [ "$lib_code" = "200" ]; then - echo "Confirmed plugin + platform-feature-catalog + cn1{app,lib}-archetype ${GITHUB_REF_NAME} on Maven Central" + echo "Confirmed plugin + platform-feature-catalog + build-hint-catalog + cn1{app,lib}-archetype ${GITHUB_REF_NAME} on Maven Central" exit 0 fi - echo "[$i/90] Waiting on Maven Central (plugin=$plugin_code, catalog=$catalog_code, cn1app=$app_code, cn1lib=$lib_code)" + echo "[$i/90] Waiting on Maven Central (plugin=$plugin_code, catalog=$catalog_code, hints=$hints_code, cn1app=$app_code, cn1lib=$lib_code)" sleep 20 done echo "Artifacts ${GITHUB_REF_NAME} did not appear on Maven Central within 30 minutes" @@ -153,6 +157,7 @@ jobs: # this keeps the release green even if that rule is ever removed. set -e for artifact in codenameone-core codenameone-maven-plugin codenameone-platform-feature-catalog \ + codenameone-build-hint-catalog \ cn1app-archetype cn1lib-archetype; do url="${R2_BASE_URL}/com/codenameone/${artifact}/${GITHUB_REF_NAME}/${artifact}-${GITHUB_REF_NAME}.pom?cb=${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" code=$(curl -s -o /dev/null -w "%{http_code}" "$url") diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index 5fa3c829dba..e7fc7cdf4c7 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -503,6 +503,30 @@ private static void publishAnnotationBuildHints(File projectDir) { } } } + File staleAgainst = classNewerThanManifest(projectDir, p, f); + if (staleAgainst != null) { + // Nothing removes target/classes between builds, so a project that ran + // process-annotations once and then stopped -- goal unbound, skipped, + // or bound to a phase that no longer runs -- keeps a manifest that + // looks entirely valid while the annotations beside it have moved on. + // The device build refuses this outright; the simulator would + // otherwise run on the previous values of hints it can actually see, + // such as desktop.titleBar and nativeTheme, and show the wrong thing + // with no indication why. + // + // Judged on timestamps rather than the manifest's own fingerprint: + // recomputing that means parsing the class file's annotation table, + // and the simulator has no bytecode reader. The comparison is sound in + // the direction that matters -- process-classes always follows compile + // within a build, so a main class newer than the manifest cannot have + // produced it. + System.err.println("Warning: " + f + " is older than " + + staleAgainst.getName() + ", so it was produced by an earlier build " + + "and its build hints were NOT applied."); + System.err.println(" Rebuild the project so the cn1 process-annotations " + + "goal regenerates it."); + return; + } int applied = 0; for (String key : p.stringPropertyNames()) { if (!key.startsWith("codename1.arg.")) { @@ -517,4 +541,30 @@ private static void publishAnnotationBuildHints(File projectDir) { System.out.println("Applied " + applied + " build hint(s) from annotations"); } } + + /** + * The compiled main class when it is newer than the manifest, or null. + * + *

Null whenever the question cannot be answered -- no main class recorded, + * no class file for it, no readable timestamps -- so the manifest is taken at + * face value rather than discarded on a guess.

+ */ + private static File classNewerThanManifest(File projectDir, java.util.Properties manifest, + File manifestFile) { + String main = manifest.getProperty("cn1.buildHints.mainClass"); + if (main == null || main.trim().length() == 0) { + return null; + } + File classFile = new File(new File(projectDir, "target" + File.separator + "classes"), + main.trim().replace('.', File.separatorChar) + ".class"); + if (!classFile.isFile()) { + return null; + } + long classTime = classFile.lastModified(); + long manifestTime = manifestFile.lastModified(); + if (classTime == 0L || manifestTime == 0L) { + return null; + } + return classTime > manifestTime ? classFile : null; + } } diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc index ed0d17f8384..939eb90d6a6 100644 --- a/docs/developer-guide/_generated-build-hints.adoc +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -636,6 +636,12 @@ |_(none)_ |Embeds XML content into the section of the Android manifest file. This is https://developer.android.com/training/package-visibility[required in Android 11 for package visibility]. See https://developer.android.com/guide/topics/manifest/queries-element[queries element Android documentation]. +|android.maps.provider +|string +|_(none)_ +|_(none)_ +|Android's own native map provider, overriding `maps.provider`. + |android.messagingService |string |_(none)_ @@ -1582,7 +1588,7 @@ |string |_(none)_ |_(none)_ -| +|Selects the native map provider. `android.maps.provider` and `ios.maps.provider` override it for one platform. |nativeTheme |`modern`, `legacy`, `custom` @@ -1590,6 +1596,12 @@ |`@Build(nativeTheme)` |`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both `ios.themeMode` and `and.themeMode` together when those aren't set explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the framework native theme entirely. The legacy alias `cn1.nativeTheme` is still accepted. +|nativeVerify +|string +|_(none)_ +|_(none)_ +|`strict` or `warn` turns on ParparVM's native signature check for this build; anything else leaves it off, which is the default. ParparVM encodes the whole Java signature in the C function name, so a native spelled even slightly differently never reaches the linker as an error: the correctly named symbol is simply absent, the dead-code pass reads that as unused, and the feature ships inert. `ios.nativeVerify`, `linux.nativeVerify` and `windows.nativeVerify` override it for one platform. + |noExtraResources |boolean |`false` @@ -2328,6 +2340,12 @@ |_(none)_ | +|ios.maps.provider +|string +|_(none)_ +|_(none)_ +|iOS's own native map provider, overriding `maps.provider`. + |ios.metal |boolean |`true` @@ -2376,6 +2394,12 @@ |_(none)_ |Set to true to enable iOS multitasking and split-screen support. This only works if `ios.xcode_verson=9.2`. +|ios.nativeVerify +|string +|_(none)_ +|_(none)_ +|`nativeVerify` for the iOS translation alone. + |ios.newPipeline |boolean |_(none)_ @@ -2868,6 +2892,12 @@ |_(none)_ | +|linux.nativeVerify +|string +|_(none)_ +|_(none)_ +|`nativeVerify` for the native Linux translation alone. + |linux.toolchain |string |_(none)_ @@ -3198,6 +3228,12 @@ |_(none)_ | +|windows.nativeVerify +|string +|_(none)_ +|_(none)_ +|`nativeVerify` for the native Windows translation alone. + |windows.sdkRoot |string |_(none)_ diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java index cfae2b80a6e..3b081bb3a26 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java @@ -827,6 +827,13 @@ static void register(List h) { .platform("android") .consumedBy("AndroidGradleBuilder")); + h.add(new Hint("android.maps.provider") + .group(HintGroup.ANDROID) + .type(HintType.STRING) + .platform("android") + .consumedBy("MapsProviderInjector") + .doc("Android's own native map provider, overriding `maps.provider`.")); + h.add(new Hint("android.min_sdk_version") .annotatedAs(HintGroup.ANDROID, "minSdkVersion") .type(HintType.INT) diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java index b4c52de1b8e..58aa72d4642 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsDesktop.java @@ -169,6 +169,13 @@ static void register(List h) { .platform("linux") .consumedBy("LinuxNativeBuilder")); + h.add(new Hint("linux.nativeVerify") + .group(HintGroup.LINUX) + .type(HintType.STRING) + .platform("linux") + .consumedBy("LinuxNativeBuilder") + .doc("`nativeVerify` for the native Linux translation alone.")); + h.add(new Hint("linux.cc") .group(HintGroup.LINUX) .type(HintType.STRING) @@ -228,6 +235,13 @@ static void register(List h) { .platform("windows") .consumedBy("WindowsNativeBuilder")); + h.add(new Hint("windows.nativeVerify") + .group(HintGroup.WINDOWS) + .type(HintType.STRING) + .platform("windows") + .consumedBy("WindowsNativeBuilder") + .doc("`nativeVerify` for the native Windows translation alone.")); + h.add(new Hint("windows.debug") .group(HintGroup.WINDOWS) .type(HintType.BOOLEAN) diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java index ef61db1fecb..e1d7b1328f6 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java @@ -342,7 +342,23 @@ static void register(List h) { .group(HintGroup.GENERAL) .type(HintType.STRING) .platform("general") - .consumedBy("MapsProviderInjector")); + .consumedBy("MapsProviderInjector") + .doc("Selects the native map provider. `android.maps.provider` and " + + "`ios.maps.provider` override it for one platform.")); + + h.add(new Hint("nativeVerify") + .group(HintGroup.GENERAL) + .type(HintType.STRING) + .platform("general") + .consumedBy("IPhoneBuilder", "LinuxNativeBuilder", "WindowsNativeBuilder") + .doc("`strict` or `warn` turns on ParparVM's native signature check for this build; " + + "anything else leaves it off, which is the default. ParparVM encodes the whole " + + "Java signature in the C function name, so a native spelled even slightly " + + "differently never reaches the linker as an error: the correctly named symbol " + + "is simply absent, " + + "the dead-code pass reads that as unused, and the feature ships inert. " + + "`ios.nativeVerify`, `linux.nativeVerify` and `windows.nativeVerify` override " + + "it for one platform.")); h.add(new Hint("nativeTheme") .annotatedAs(HintGroup.GENERAL, "nativeTheme") diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java index 242951b086b..08902bc6b30 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java @@ -737,6 +737,13 @@ static void register(List h) { .platform("ios") .consumedBy("IPhoneBuilder")); + h.add(new Hint("ios.maps.provider") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("MapsProviderInjector") + .doc("iOS's own native map provider, overriding `maps.provider`.")); + h.add(new Hint("ios.metal") .group(HintGroup.IOS) .type(HintType.BOOLEAN) @@ -805,6 +812,13 @@ static void register(List h) { .doc("Set to true to enable iOS multitasking and split-screen support. This only works if " + "`ios.xcode_verson=9.2`.")); + h.add(new Hint("ios.nativeVerify") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("`nativeVerify` for the iOS translation alone.")); + h.add(new Hint("ios.newStorageLocation") .annotatedAs(HintGroup.IOS, "newStorageLocation") .type(HintType.BOOLEAN) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index f5dfd75555b..6976270a0ed 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -417,21 +417,27 @@ String toSourceLiteral(BuildHints.Hint hint, String value, boolean kotlin) { if (value == null) { return null; } - String v = value.trim(); + // Trimmed only where the surrounding space cannot be part of the value: + // "true ", " 24" and " modern" all mean what they say. A string does not + // get that treatment -- an ios.glAppDelegateHeader ending in a newline + // after a // comment needs that newline, and losing it comments out + // whatever the builder generates next. The verification build would not + // notice, since it checks that the key came back and not what it holds. + String v = value; switch (hint.type()) { case BOOLEAN: - if ("true".equalsIgnoreCase(v)) return "true"; - if ("false".equalsIgnoreCase(v)) return "false"; + if ("true".equalsIgnoreCase(v.trim())) return "true"; + if ("false".equalsIgnoreCase(v.trim())) return "false"; return null; case INT: try { - return String.valueOf(Integer.parseInt(v)); + return String.valueOf(Integer.parseInt(v.trim())); } catch (NumberFormatException ex) { return null; } case ENUM: for (String allowed : hint.values()) { - if (allowed.equalsIgnoreCase(v)) { + if (allowed.equalsIgnoreCase(v.trim())) { return hint.enumName() + "." + enumConstant(allowed); } } @@ -708,7 +714,13 @@ private static boolean continues(String line) { * or a comment. * *

Follows {@code java.util.Properties}: the key runs to the first - * unescaped {@code =}, {@code :} or whitespace.

+ * unescaped {@code =}, {@code :} or whitespace, and {@code \}{@code uXXXX} + * decodes to the character it names. The escape matters because the key this + * returns is compared against one {@code Properties.load} produced: a file + * writing {@code codename1.arg.\}{@code u0069os.teamId} declares + * {@code ios.teamId}, and reading it as {@code u0069os.teamId} leaves the + * original line in place, so the migration rolls back over a duplicate + * declaration it created itself.

*/ static String propertyKeyOf(String logicalLine) { int i = 0; @@ -726,7 +738,17 @@ static String propertyKeyOf(String logicalLine) { for (; i < logicalLine.length(); i++) { char c = logicalLine.charAt(i); if (c == '\\' && i + 1 < logicalLine.length()) { - key.append(logicalLine.charAt(++i)); + char escaped = logicalLine.charAt(++i); + if (escaped == 'u' && i + 4 < logicalLine.length()) { + String hex = logicalLine.substring(i + 1, i + 5); + int value = hexValue(hex); + if (value >= 0) { + key.append((char) value); + i += 4; + continue; + } + } + key.append(escaped); continue; } if (c == '=' || c == ':' || isPropertySpace(c)) { @@ -741,6 +763,19 @@ private static boolean isPropertySpace(char c) { return c == ' ' || c == '\t' || c == '\f'; } + /** Four hex digits as a char value, or -1 when they are not four hex digits. */ + private static int hexValue(String hex) { + int value = 0; + for (int i = 0; i < hex.length(); i++) { + int digit = Character.digit(hex.charAt(i), 16); + if (digit < 0) { + return -1; + } + value = value * 16 + digit; + } + return value; + } + /** The encoding {@code Properties.load(InputStream)} reads. */ private static final String PROPERTIES_ENCODING = "ISO-8859-1"; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 65d72fdbd9f..5ab19fdb443 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -139,4 +139,20 @@ public void aBackslashBeforeAnEscapedCharacterSurvives() { public void aControlCharacterIsEscaped() { assertEquals("\"\\u0001\"", MigrateBuildHintsMojo.quoteFor("\u0001", false)); } + + /// `Properties.load` decodes a Unicode escape in a KEY too, so the key this parser + /// returns has to be the decoded one. Reading the escape literally left the + /// original line in place, and the migration then rolled back over a + /// duplicate declaration it had created itself. + @Test + public void aUnicodeEscapeInAKeyIsDecoded() { + assertEquals("codename1.arg.ios.teamId", + MigrateBuildHintsMojo.propertyKeyOf("codename1.arg.\\u0069os.teamId=ABCDE")); + } + + /// Not every backslash-u is an escape. Four hex digits or it is a literal u. + @Test + public void aMalformedUnicodeEscapeIsNotDecoded() { + assertEquals("a.uZZZZb", MigrateBuildHintsMojo.propertyKeyOf("a.\\uZZZZb=1")); + } } diff --git a/scripts/build-hint-computed-sites.txt b/scripts/build-hint-computed-sites.txt new file mode 100644 index 00000000000..0edf7eec13c --- /dev/null +++ b/scripts/build-hint-computed-sites.txt @@ -0,0 +1,22 @@ +# Call sites that BUILD a hint name rather than writing it as a literal, so no +# literal anywhere in the tree names the hint. The miner cannot resolve these -- +# the platform, or the entitlement, is only known at run time -- and before this +# file existed it did not report them either, so the gate could print "all +# described" while android.maps.provider and ios.nativeVerify had no catalog row +# at all. +# +# Every site the miner finds must be listed here with what it expands to, and +# every expansion must be catalogued or match a dynamic pattern. A new computed +# site therefore forces a catalog decision instead of disappearing. +# +# Format: :|[,...] +# +# Only sites whose expression contains a string literal are reported. A helper +# that forwards a variable -- getArg(key, ...) inside a wrapper -- gets its +# literal from its caller, which the ordinary literal pass already mines. + +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MapsProviderInjector.java|android.maps.provider,ios.maps.provider +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/NativeVerifyOption.java|ios.nativeVerify,linux.nativeVerify,windows.nativeVerify +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MacNativeBuilder.java|macNative.provisioningProfile.appStore,macNative.provisioningProfile.developerID +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java|ios.entitlements.com.apple.developer.healthkit.access +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/WatchNativeBuilder.java|ios.entitlements.com.apple.developer.healthkit.access diff --git a/scripts/build_hint_miner.py b/scripts/build_hint_miner.py index 647a308e670..935d7e04c91 100644 --- a/scripts/build_hint_miner.py +++ b/scripts/build_hint_miner.py @@ -9,6 +9,19 @@ because calls nest: getArg("ios.urlSchemes", getArg("ios.urlScheme", "")). A regex that stops at the first comma both mis-reads the outer default and consumes the inner call, silently dropping a hint from the catalog. + +Not every hint name is written as a literal at the point it is read. Two shapes +occur and both used to be invisible, which let the gate report "all described" +while real hints had no catalog row at all: + + getArg(HINT, null) NativeVerifyOption, HINT="nativeVerify" + getArg(platform + ".maps.provider", ...) MapsProviderInjector + +The first is resolved: a `static final String` in the same file whose value is a +literal is substituted. The second cannot be -- the platform is only known at +run time -- so it is reported as a COMPUTED site instead of ignored, and the +checker holds those against the catalog rather than letting them pass in +silence. """ import re, os, sys, json, collections @@ -25,6 +38,15 @@ _ESCAPES = {'n': '\n', 't': '\t', 'r': '\r', 'b': '\b', 'f': '\f', '"': '"', "'": "'", '\\': '\\'} +# getArg/arg/booleanArg whose first argument is not a string literal. \s covers the +# line-wrapped calls, which are plain literal reads once the newline is crossed. +COMPUTED_OPENER = re.compile( + r'\b(?:getArg|booleanArg)\(\s*(?!")|(?= 2 and part.startswith('"') and part.endswith('"'): + lit, _ = read_literal(part, 1) + if lit is None: + return None + out.append(lit) + else: + return None + return "".join(out) for dirpath, _, files in os.walk(SRC): for fn in sorted(files): @@ -91,6 +143,28 @@ def split_args(text, i): with open(path, encoding="utf-8", errors="replace") as fh: text = fh.read() rel = os.path.relpath(path, ROOT) + constants = {m.group(1): (read_literal(m.group(0), m.group(0).index('"') + 1)[0] or "") + for m in CONST_DECL.finditer(text)} + for m in COMPUTED_OPENER.finditer(text): + open_paren = text.rindex('(', m.start(), m.end()) + expr = first_argument(text, open_paren) + if not expr: + continue + line = text.count("\n", 0, m.start()) + 1 + resolved = concat_of_literals(resolve_constants(expr, constants)) + if resolved is not None: + # Fully resolved -- an ordinary hint read that merely spelled its + # name with a constant. + hits[resolved].append(("", rel, line)) + elif '"' in expr: + # The name is being BUILT here, so no literal anywhere names it and + # the literal pass cannot see it. Worth reporting. + computed.append({"expr": " ".join(expr.split()), + "file": rel, "line": line}) + # Anything else is a forwarding helper -- getArg(key, ...) inside a + # wrapper, or the declaration of getArg itself -- whose caller passes a + # literal that the literal pass already mines. Reporting those would bury + # the handful of sites that genuinely compute a name. for pat, prefixed in OPENERS: for m in pat.finditer(text): # position of the char just after the opening quote of arg 1 @@ -112,10 +186,20 @@ def split_args(text, i): line = text.count("\n", 0, m.start()) + 1 hits[key].append((default or "null", rel, line)) +def hits_computed(): + """(expression, file, line) for every site that builds a hint name.""" + return sorted((c["expr"], c["file"], c["line"]) for c in computed) + + if __name__ == "__main__": - print(f"distinct keys mined: {len(hits)}", file=sys.stderr) + print(f"distinct keys mined: {len(hits)}, computed sites: {len(computed)}", + file=sys.stderr) out = sys.argv[1] if len(sys.argv) > 1 else "-" payload = {k: v for k, v in sorted(hits.items())} + # Under a key no hint name can take, so a consumer reading this as a plain + # name->sites map cannot mistake it for a hint. + payload["#computed"] = sorted( + (c["expr"], c["file"], c["line"]) for c in computed) if out == "-": json.dump(payload, sys.stdout, indent=1) else: diff --git a/scripts/check-build-hint-catalog.py b/scripts/check-build-hint-catalog.py index 14e9506e235..b338c58f513 100755 --- a/scripts/check-build-hint-catalog.py +++ b/scripts/check-build-hint-catalog.py @@ -15,6 +15,7 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.join(ROOT, "scripts")) BASELINE = os.path.join(ROOT, "scripts", "build-hint-catalog-baseline.txt") +COMPUTED_SITES = os.path.join(ROOT, "scripts", "build-hint-computed-sites.txt") CATALOG_CLASSES = os.path.join(ROOT, "maven/build-hint-catalog/target/classes") @@ -147,7 +148,46 @@ def main(): "green build and no effect.", file=sys.stderr) return 1 - print(f"check-build-hint-catalog: {len(miner.hits)} hints read, all described by the catalog" + # Sites that build a hint name instead of writing it. Nothing in the tree + # names the hint they read, so the pass above cannot see it -- which is how + # this gate came to report "all described" while android.maps.provider and + # ios.nativeVerify had no catalog row. + declared = {} + if os.path.exists(COMPUTED_SITES): + with open(COMPUTED_SITES, encoding="utf-8") as fh: + for line in fh: + line = line.strip() + if not line or line.startswith("#"): + continue + where, _, expansions = line.partition("|") + declared[where] = [e for e in expansions.split(",") if e] + + computed_bad = [] + for expr, path, line_no in miner.hits_computed(): + if path not in declared: + computed_bad.append( + f"{path}:{line_no} builds a hint name from `{expr}` and is not listed") + continue + for name in declared[path]: + if name in known or any(fnmatch.fnmatch(name, p) for p in patterns): + continue + computed_bad.append( + f"{path}:{line_no} expands to {name}, which the catalog does not describe") + for path in sorted(set(declared) - {p for _, p, _ in miner.hits_computed()}): + computed_bad.append(f"{path} is listed but no longer builds a hint name") + if computed_bad: + print("check-build-hint-catalog: computed hint names are unaccounted for:", + file=sys.stderr) + for line in sorted(set(computed_bad)): + print(" " + line, file=sys.stderr) + print("\nList the site in scripts/build-hint-computed-sites.txt with what it " + "expands to, and catalogue each expansion. A hint whose name is only ever " + "computed is invisible to every literal search, including this one.", + file=sys.stderr) + return 1 + + print(f"check-build-hint-catalog: {len(miner.hits)} hints read, all described by the " + f"catalog; {len(declared)} computed site(s) accounted for" + (f" ({len(baseline)} baselined)" if baseline else "")) return 0 diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index be14fccbed1..9a8db8825b9 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2187,6 +2187,45 @@ private String readIfPresent(String path) { } } + /// The name a Kotlin `import ... as Alias` gives an annotation, or null. + /// + /// Kotlin lets a file rename what it imports, and then the annotation never + /// appears under its own name anywhere in the source. Missing that reads the + /// hint as unowned, so Settings offers it for Add, writes the properties + /// line, and the next `process-annotations` fails on the duplicate the tool + /// itself created. + static String kotlinImportAlias(String source, String simple) { + String needle = "com.codename1.annotations.buildhints." + simple; + int at = source.indexOf(needle); + while (at >= 0) { + int after = at + needle.length(); + if (after >= source.length() || !continuesAName(source.charAt(after))) { + int i = after; + while (i < source.length() && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { + i++; + } + if (source.regionMatches(i, "as", 0, 2) + && i + 2 < source.length() + && !continuesAName(source.charAt(i + 2))) { + i += 2; + while (i < source.length() + && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { + i++; + } + int start = i; + while (i < source.length() && continuesAName(source.charAt(i))) { + i++; + } + if (i > start) { + return source.substring(start, i); + } + } + } + at = source.indexOf(needle, after); + } + return null; + } + /// Maps every `@Group(attr = ...)` on the main class to the hints it sets. static void collectAnnotationOwnedHints(String source, java.util.Map out) { for (com.codename1.build.shared.BuildHints.Hint h : com.codename1.build.shared.BuildHints.entries()) { @@ -2194,13 +2233,21 @@ static void collectAnnotationOwnedHints(String source, java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } + + /// The alias only counts when it really is one: the import must say `as`. + @Test + public void aPlainImportIsNotReadAsAnAlias() { + String src = "import com.codename1.annotations.buildhints.Ios\n" + + "@Ios(teamId = \"ABCDE12345\")\n"; + assertNull(CodenameOneSettings.kotlinImportAlias(src, "Ios")); + } } From 2cf08426922d4a735f101c7aa6a323f61e1bfa36 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 15:32:47 +0300 Subject: [PATCH 024/115] Verify the migration the way a real build runs Three from the same review, two of them the same mistake: the verification build was described in terms of paths and POMs rather than of the project Maven actually has in hand. Pointing Maven at the module's own POM discards the reactor, so the module's siblings resolve from the local repository instead of from the build. A project whose main module depends on another module of the same build -- normal, and not necessarily installed -- failed to resolve there and the migration rolled back over a build that a plain `mvn package` performs happily. The nested invocation now runs the reactor root with -pl on the owning module and -am, and falls back to the module POM only when there is no reactor to run. The manifest was looked for in projectDir/target/classes, which is a convention rather than a fact: a module may configure build/outputDirectory, and then a build that emitted every hint correctly was reported as having produced nothing. Read off the owning MavenProject now, with the conventional path kept for a directory that is not a reactor module at all. pr.yml still ignored two of the gate's own files. scripts/build-hint-computed-sites.txt is the accounting that keeps the miner honest about names it cannot resolve, so a PR weakening it was the one PR that would not run it. And docs/developer-guide/_generated-build-hints.adoc was swallowed by !docs/**, which sits after the scripts re-inclusions, so a hand edit to a generated table was seen only by the documentation workflows -- none of which run the drift check that would put it back. The table is re-included after the docs exclusion, since the last matching pattern decides. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/pr.yml | 14 ++++ .../maven/MigrateBuildHintsMojo.java | 70 +++++++++++++++++-- 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 84eb9531958..f2d3c94c668 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -35,8 +35,15 @@ on: - 'scripts/check-build-hint-catalog.py' - 'scripts/build_hint_miner.py' - 'scripts/build-hint-catalog-baseline.txt' + - 'scripts/build-hint-computed-sites.txt' - 'scripts/gen-build-hint-annotations.sh' - '!docs/**' + # After the docs exclusion, because the last matching pattern decides. This + # table is generated from the build hint catalog and checked for drift by + # gen-build-hint-annotations.sh, which only this workflow runs -- so while + # it was excluded, a hand edit to it was reviewed by the documentation + # workflows and by nothing that would have put it back. + - 'docs/developer-guide/_generated-build-hints.adoc' - '!**/*.md' - '!.github/workflows/developer-guide-docs.yml' - '!.github/workflows/website-docs.yml' @@ -76,8 +83,15 @@ on: - 'scripts/check-build-hint-catalog.py' - 'scripts/build_hint_miner.py' - 'scripts/build-hint-catalog-baseline.txt' + - 'scripts/build-hint-computed-sites.txt' - 'scripts/gen-build-hint-annotations.sh' - '!docs/**' + # After the docs exclusion, because the last matching pattern decides. This + # table is generated from the build hint catalog and checked for drift by + # gen-build-hint-annotations.sh, which only this workflow runs -- so while + # it was excluded, a hand edit to it was reviewed by the documentation + # workflows and by nothing that would have put it back. + - 'docs/developer-guide/_generated-build-hints.adoc' - '!**/*.md' - '!.github/workflows/developer-guide-docs.yml' - '!.github/workflows/website-docs.yml' diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 6976270a0ed..6b3004ad4f3 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -315,14 +315,34 @@ private String verifyAnnotationsAreProcessed(File projectDir, List migra // already there: with processing now skipped or unbound the nested build // leaves it untouched, the check passes, the properties are deleted, and // the next clean build removes the stale artifact and the hints with it. - File emitted = new File(projectDir, "target/classes/" + ANNOTATION_HINTS_RESOURCE); + // Where the processor will actually write, which is not always + // target/classes: a module is free to configure build/outputDirectory, + // and looking in the wrong place would report a successful build as + // having produced nothing and roll a correct migration back. + org.apache.maven.project.MavenProject owner = moduleAt(projectDir); + File outputDir = configuredOutputDirectory(owner, projectDir); + File emitted = new File(outputDir, ANNOTATION_HINTS_RESOURCE); if (emitted.isFile() && !emitted.delete()) { return "Could not remove the previous " + ANNOTATION_HINTS_RESOURCE + ", so this build's output could not be told apart from it."; } - File pom = new File(projectDir, "pom.xml"); + // Run the REACTOR and select the owning module, rather than pointing + // Maven at that module's own POM. A module POM on its own resolves its + // siblings from the local repository, so a project whose main module + // depends on another module of the same build -- normal, and not + // necessarily installed -- fails to resolve here and the migration rolls + // back over a build that a plain `mvn package` performs happily. + File modulePom = new File(projectDir, "pom.xml"); + File reactorPom = new File(project.getBasedir(), "pom.xml"); InvocationRequest request = new DefaultInvocationRequest(); - request.setPomFile(pom.isFile() ? pom : new File(project.getBasedir(), "pom.xml")); + if (reactorPom.isFile() && owner != null) { + request.setPomFile(reactorPom); + request.setProjects(Collections.singletonList( + owner.getGroupId() + ":" + owner.getArtifactId())); + request.setAlsoMake(true); + } else { + request.setPomFile(modulePom.isFile() ? modulePom : reactorPom); + } request.setGoals(Collections.singletonList("process-classes")); Properties props = new Properties(); props.setProperty("skipTests", "true"); @@ -340,8 +360,7 @@ private String verifyAnnotationsAreProcessed(File projectDir, List migra } if (!emitted.isFile()) { - return "No " + ANNOTATION_HINTS_RESOURCE + " was written under " - + projectDir.getName() + "/target/classes."; + return "No " + ANNOTATION_HINTS_RESOURCE + " was written under " + outputDir + "."; } Properties produced = new Properties(); try (FileInputStream in = new FileInputStream(emitted)) { @@ -361,7 +380,46 @@ private String verifyAnnotationsAreProcessed(File projectDir, List migra return null; } - /** Name of the resource the annotation processor emits into target/classes. */ + /** The reactor module whose basedir is {@code dir}, or null when none is. */ + private org.apache.maven.project.MavenProject moduleAt(File dir) { + if (reactorProjects == null || dir == null) { + return null; + } + File wanted = canonical(dir); + for (org.apache.maven.project.MavenProject p : reactorProjects) { + if (p.getBasedir() != null && canonical(p.getBasedir()).equals(wanted)) { + return p; + } + } + return null; + } + + /** + * The directory {@code process-classes} writes compiled output to. + * + *

Read off the module rather than assumed, since a POM may configure it. + * Falls back to the conventional path when the directory is not a reactor + * module at all -- an Ant-layout project, for instance.

+ */ + private static File configuredOutputDirectory(org.apache.maven.project.MavenProject owner, + File projectDir) { + if (owner != null && owner.getBuild() != null + && owner.getBuild().getOutputDirectory() != null + && owner.getBuild().getOutputDirectory().length() > 0) { + return new File(owner.getBuild().getOutputDirectory()); + } + return new File(projectDir, "target" + File.separator + "classes"); + } + + private static File canonical(File f) { + try { + return f.getCanonicalFile(); + } catch (IOException ex) { + return f.getAbsoluteFile(); + } + } + + /** Name of the resource the annotation processor emits into target/classes. */ /** Name of the resource the annotation processor emits into target/classes. */ private static final String ANNOTATION_HINTS_RESOURCE = "META-INF/codenameone/build-hints.properties"; From 01816a1ddc9394a7998282996f2e830264bc56ce Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:20:09 +0300 Subject: [PATCH 025/115] Read the project as configured, and the source as code Three from the same review. The nested verification build carried only skipTests, so a project needing -Pcustomer to compile, or -Dfeature=true to bind process-annotations, was verified as a different build than the developer ran. That rolls back a migration that works -- or, worse, passes one whose processing an outer -D was switching off. The session's active profiles and user properties are copied in now, with skipTests reapplied afterwards so a user property cannot quietly displace it. Profiles come from the request rather than from the resolved project, because what has to be reproduced is what was typed: a profile activated by a property activates again on its own terms, one named with -P does not unless it is passed on. The simulator looked for the manifest in target/classes, which is the default and not the fact. A module that configures build/outputDirectory had the device build apply its annotated hints while cn1:run ignored them -- exactly the asymmetry this publishing step exists to remove. The configured directory is already on the simulator's classpath, so the classpath is searched instead of the layout assumed, conventional path first since it is right nearly always and costs one stat. Only directories are searched: a jar can carry this resource, but as a dependency, and a dependency's hints belong to whoever built it. The staleness check follows the manifest to whichever directory it was found in rather than recomputing the guess. Settings found annotation markers with indexOf, so a commented-out `// @Ios(teamId = "old")` counted as ownership and the tool withheld Add and the editor for a hint the processor never emits -- indistinguishable, from the outside, from Settings being broken. Marker discovery now walks the source with the same skipNonCode scanner the argument reader already used, so what counts as code is one answer rather than two that can disagree. Tests cover a line comment, a block comment, a quoted annotation, and a live annotation preceded by a commented-out copy. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 69 ++++++++++++++++--- .../com/codename1/maven/AbstractCN1Mojo.java | 5 ++ .../maven/MigrateBuildHintsMojo.java | 45 +++++++++++- .../settings/CodenameOneSettings.java | 36 +++++++++- .../settings/BuildHintCatalogTest.java | 39 +++++++++++ 5 files changed, 181 insertions(+), 13 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index e7fc7cdf4c7..99179a211cc 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -160,7 +160,7 @@ public static void main(final String[] argv) throws Exception { files.add(commonClasses.getAbsoluteFile()); } loadSimulatorProperties(cn1Props.getParentFile()); - publishAnnotationBuildHints(cn1Props.getParentFile()); + publishAnnotationBuildHints(cn1Props.getParentFile(), classPathStr); } if (isDebug && usingHotswapAgent) { HotswapProperties hotswapProperties = new HotswapProperties(); @@ -476,14 +476,12 @@ private List getExtraClasses() { * point in {@code main} the application classes are not on any classloader * yet -- the loader is built from {@code files} further down.

*/ - private static void publishAnnotationBuildHints(File projectDir) { + private static void publishAnnotationBuildHints(File projectDir, String classPathStr) { if (projectDir == null) { return; } - File f = new File(projectDir, "target" + File.separator + "classes" - + File.separator + "META-INF" + File.separator + "codenameone" - + File.separator + "build-hints.properties"); - if (!f.isFile()) { + File f = findAnnotationManifest(projectDir, classPathStr); + if (f == null) { return; } java.util.Properties p = new java.util.Properties(); @@ -503,7 +501,7 @@ private static void publishAnnotationBuildHints(File projectDir) { } } } - File staleAgainst = classNewerThanManifest(projectDir, p, f); + File staleAgainst = classNewerThanManifest(p, f); if (staleAgainst != null) { // Nothing removes target/classes between builds, so a project that ran // process-annotations once and then stopped -- goal unbound, skipped, @@ -542,6 +540,51 @@ private static void publishAnnotationBuildHints(File projectDir) { } } + /** + * The emitted build hint manifest, or null when there is none. + * + *

{@code target/classes} is only the default: a module may configure + * {@code build/outputDirectory}, and the annotation processor writes where + * that says. Hard-coding the conventional path meant the device build applied + * the annotated hints and {@code cn1:run} silently ignored them, which is the + * asymmetry this whole publishing step exists to remove.

+ * + *

The configured directory is on the simulator's own classpath, so the + * classpath is searched rather than the layout guessed at. The conventional + * path is tried first, since it is right for almost every project and costs + * one stat.

+ */ + private static File findAnnotationManifest(File projectDir, String classPathStr) { + String resource = "META-INF" + File.separator + "codenameone" + + File.separator + "build-hints.properties"; + File conventional = new File(projectDir, "target" + File.separator + "classes" + + File.separator + resource); + if (conventional.isFile()) { + return conventional; + } + if (classPathStr == null) { + return null; + } + for (String entry : classPathStr.split(java.util.regex.Pattern.quote(File.pathSeparator))) { + if (entry.length() == 0) { + continue; + } + File dir = new File(entry); + if (!dir.isDirectory()) { + // A jar can carry this resource too, but only as a dependency -- + // and a dependency's hints belong to whoever built it, which the + // main-class stamp exists to reject. Directories are this + // project's own output. + continue; + } + File candidate = new File(dir, resource); + if (candidate.isFile()) { + return candidate; + } + } + return null; + } + /** * The compiled main class when it is newer than the manifest, or null. * @@ -549,13 +592,21 @@ private static void publishAnnotationBuildHints(File projectDir) { * no class file for it, no readable timestamps -- so the manifest is taken at * face value rather than discarded on a guess.

*/ - private static File classNewerThanManifest(File projectDir, java.util.Properties manifest, + private static File classNewerThanManifest(java.util.Properties manifest, File manifestFile) { String main = manifest.getProperty("cn1.buildHints.mainClass"); if (main == null || main.trim().length() == 0) { return null; } - File classFile = new File(new File(projectDir, "target" + File.separator + "classes"), + // Beside the manifest, whatever directory that turned out to be -- the + // two are written by the same build into the same output directory. + File classes = manifestFile.getParentFile(); // .../codenameone + classes = classes == null ? null : classes.getParentFile(); // .../META-INF + classes = classes == null ? null : classes.getParentFile(); // the output dir + if (classes == null) { + return null; + } + File classFile = new File(classes, main.trim().replace('.', File.separatorChar) + ".class"); if (!classFile.isFile()) { return null; diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java index 6ad3ac33f50..b044da429cc 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java @@ -222,6 +222,11 @@ private void setupAnt() throws MojoExecutionException, MojoFailureException { @Parameter(defaultValue = "${session}", readonly = true) private MavenSession session; + /** The session this mojo is running in, for a subclass that has to reproduce it. */ + protected MavenSession getSession() { + return session; + } + /** * Lets {@code -Dcodename1.arg.x=y} override the settings file. * diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 6b3004ad4f3..ebafa84240a 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -346,6 +346,25 @@ private String verifyAnnotationsAreProcessed(File projectDir, List migra request.setGoals(Collections.singletonList("process-classes")); Properties props = new Properties(); props.setProperty("skipTests", "true"); + // Reproduce the invocation the developer actually made. A project that + // needs `-Pcustomer` to compile, or `-Dfeature=true` to bind + // process-annotations, is a different build without them: the check + // would roll back a migration that works, or -- worse -- pass a build + // whose processing an outer -D was switching off. The user properties go + // in first so skipTests below cannot be silently overridden by one. + if (getSession() != null) { + Properties user = getSession().getUserProperties(); + if (user != null) { + for (String name : user.stringPropertyNames()) { + props.setProperty(name, user.getProperty(name)); + } + } + props.setProperty("skipTests", "true"); + List profiles = activeProfileIds(); + if (!profiles.isEmpty()) { + request.setProfiles(profiles); + } + } request.setProperties(props); request.setBatchMode(true); try { @@ -380,7 +399,31 @@ private String verifyAnnotationsAreProcessed(File projectDir, List migra return null; } - /** The reactor module whose basedir is {@code dir}, or null when none is. */ + /** + * The profiles this invocation was started with, by id. + * + *

Taken from the request rather than from the resolved project, because + * what has to be reproduced is what the developer typed: a profile activated + * by a property or a file is activated again on its own terms in the nested + * build, while one named with {@code -P} is not unless it is passed on.

+ */ + private List activeProfileIds() { + List out = new ArrayList(); + if (getSession() == null || getSession().getRequest() == null) { + return out; + } + List active = getSession().getRequest().getActiveProfiles(); + if (active != null) { + for (String id : active) { + if (id != null && id.trim().length() > 0) { + out.add(id.trim()); + } + } + } + return out; + } + + /** The reactor module whose basedir is {@code dir}, or null when none is. */ /** The reactor module whose basedir is {@code dir}, or null when none is. */ private org.apache.maven.project.MavenProject moduleAt(File dir) { if (reactorProjects == null || dir == null) { return null; diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 9a8db8825b9..05d22bfec22 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2250,13 +2250,19 @@ static void collectAnnotationOwnedHints(String source, java.util.Map= 0) { // "@Ios" must not match "@IosPrivacy": the next character has // to end the name. int after = at + markers[m].length(); if (after < source.length() && continuesAName(source.charAt(after))) { - at = source.indexOf(markers[m], after); + at = nextMarker(source, markers[m], after); continue; } int open = source.indexOf('(', at); @@ -2270,7 +2276,7 @@ static void collectAnnotationOwnedHints(String source, java.util.Map= '0' && c <= '9') || c == '_' || c == '$'; } + /// The next occurrence of `marker` that is real code, or -1. + /// + /// Comments and string literals are stepped over with the same scanner the + /// argument reader uses, so what counts as code is one answer rather than + /// two that can disagree. + static int nextMarker(String source, String marker, int from) { + int i = from; + while (i < source.length()) { + char c = source.charAt(i); + if (c == '"' || c == '\'' || c == '/') { + int skipped = skipNonCode(source, i); + if (skipped > i) { + i = skipped; + continue; + } + } + if (source.startsWith(marker, i)) { + return i; + } + i++; + } + return -1; + } + /// If a string, character literal or comment starts at `i`, the index just /// past it; otherwise `i`. private static int skipNonCode(String s, int i) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index d420a4f2e52..c77c58954e3 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -283,4 +283,43 @@ public void aPlainImportIsNotReadAsAnAlias() { + "@Ios(teamId = \"ABCDE12345\")\n"; assertNull(CodenameOneSettings.kotlinImportAlias(src, "Ios")); } + + /// A commented-out annotation is not an annotation. Reading it as one made + /// Settings withhold Add and the editor for a hint the processor never + /// emits, which looks like the tool being broken. + @Test + public void aCommentedOutAnnotationIsNotOwnership() { + String src = "package com.example;\n" + + "import com.codename1.annotations.buildhints.Ios;\n" + + "// @Ios(teamId = \"OLD\")\n" + + "public class MyApp {}\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertNull(out.get("ios.teamId")); + } + + /// Same for a block comment and for an annotation quoted inside a string. + @Test + public void aBlockCommentOrStringIsNotOwnership() { + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints( + "/* @Ios(teamId = \"OLD\") */ public class MyApp {}", out); + assertNull(out.get("ios.teamId")); + + out.clear(); + CodenameOneSettings.collectAnnotationOwnedHints( + "String doc = \"@Ios(teamId = x)\";", out); + assertNull(out.get("ios.teamId")); + } + + /// And the real one is still found when a commented-out copy precedes it. + @Test + public void aLiveAnnotationAfterACommentedOneIsStillFound() { + String src = "// @Ios(teamId = \"OLD\")\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "public class MyApp {}\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } } From d013397fba51bfb5d1b061a66eaa7b54bc4cd36d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:26:23 +0300 Subject: [PATCH 026/115] Retry the repository failure that was not on the list Unrelated to build hints; here because it is what turned this PR's clean-target (arm64) red, and opening a second PR for it is not wanted. The job died on Failed to read artifact descriptor for org.sonatype.aether:aether-spi:jar:1.7 fetching a maven-dependency-plugin transitive, and the retry wrapper then said "not a transient dependency-resolution error; not retrying" -- because that wording was in none of the eleven copies of the classifier, which have already drifted into four different alternations. It is a fetch failure by definition: Maven reached the repository and could not read the POM. That is the same class as "Could not transfer artifact", which every copy already retries, so adding it does not soften the gate the way the blanket loop those comments warn about would. Checked rather than assumed: the extended pattern matches the line CI actually printed, and still does not match a compile error or a test failure. Added to all eleven -- ten workflows and the CEF smoke script -- rather than to the one that failed, since the next hiccup lands wherever it lands. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/archetype-smoke.yml | 2 +- .github/workflows/designer.yml | 2 +- .github/workflows/identity-stack.yml | 2 +- .github/workflows/parparvm-tests-windows.yml | 2 +- .github/workflows/parparvm-tests.yml | 4 ++-- .github/workflows/pr.yml | 4 ++-- .github/workflows/protocol-e2e.yml | 2 +- .github/workflows/purchase-e2e.yml | 2 +- .github/workflows/windows-cross-build-run.yml | 2 +- .github/workflows/windows-cross-compile.yml | 2 +- scripts/run-javase-cef-ffmpeg-smoke.py | 5 +++++ 11 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/archetype-smoke.yml b/.github/workflows/archetype-smoke.yml index d2dab9f6bc2..52e5d0efb8d 100644 --- a/.github/workflows/archetype-smoke.yml +++ b/.github/workflows/archetype-smoke.yml @@ -64,7 +64,7 @@ jobs: # before it had installed anything, one of several jobs Maven Central refused on this # branch in a day. A build failure in our own code still fails on the first attempt. env: - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|or one of its dependencies could not be resolved|authorization failed for https://repo\.maven\.apache\.org|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|or one of its dependencies could not be resolved|authorization failed for https://repo\.maven\.apache\.org|Connection reset|Premature end of Content-Length' RETRY_ATTEMPTS: '5' RETRY_DELAY_SECONDS: '60' run: | diff --git a/.github/workflows/designer.yml b/.github/workflows/designer.yml index 3659f5985e9..1e717dde99f 100644 --- a/.github/workflows/designer.yml +++ b/.github/workflows/designer.yml @@ -86,7 +86,7 @@ jobs: # without it, so a test that failed once and passed on a later attempt would take # this gate green and hide the race that produced it. The wrapper's own docs # require this filter for test-running commands. - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' run: | cd maven bash $GITHUB_WORKSPACE/scripts/ci/retry.sh mvn -B -pl designer -am -DunitTests=true -Dcodename1.platform=javase \ diff --git a/.github/workflows/identity-stack.yml b/.github/workflows/identity-stack.yml index 104e0d4c4be..175925a388a 100644 --- a/.github/workflows/identity-stack.yml +++ b/.github/workflows/identity-stack.yml @@ -127,7 +127,7 @@ jobs: # without it, so a test that failed once and passed on a later attempt would take # this gate green and hide the race that produced it. The wrapper's own docs # require this filter for test-running commands. - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' run: | set -euo pipefail # Targeted run: OidcCoreTest is the new suite; the *Connect / Login / diff --git a/.github/workflows/parparvm-tests-windows.yml b/.github/workflows/parparvm-tests-windows.yml index 9da29f706a9..6a587c969a9 100644 --- a/.github/workflows/parparvm-tests-windows.yml +++ b/.github/workflows/parparvm-tests-windows.yml @@ -229,7 +229,7 @@ jobs: # exists for. A blanket loop lets an intermittent product regression pass on # attempt two and turns a blocking gate green -- which is the opposite of what # a gate is for, and worse than the flake it was hiding. - $resolutionFailure = 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|Could not resolve dependencies' + $resolutionFailure = 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not resolve dependencies' $ok = $false foreach ($delay in 0, 30, 90, 180, 300) { if ($delay -gt 0) { diff --git a/.github/workflows/parparvm-tests.yml b/.github/workflows/parparvm-tests.yml index 6b2b17a02c5..e1938be54d1 100644 --- a/.github/workflows/parparvm-tests.yml +++ b/.github/workflows/parparvm-tests.yml @@ -124,7 +124,7 @@ jobs: retry mvn -B clean package -pl JavaAPI -am -DskipTests retry mvn -B test -pl tests -am -DexcludedGroups=benchmark env: - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' JDK_8_HOME: ${{ env.JDK_8_HOME }} JDK_11_HOME: ${{ env.JDK_11_HOME }} JDK_17_HOME: ${{ env.JDK_17_HOME }} @@ -139,7 +139,7 @@ jobs: # without it, so a test that failed once and passed on a later attempt would take # this gate green and hide the race that produced it. The wrapper's own docs # require this filter for test-running commands. - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' JDK_8_HOME: ${{ env.JDK_8_HOME }} JDK_11_HOME: ${{ env.JDK_11_HOME }} JDK_17_HOME: ${{ env.JDK_17_HOME }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f2d3c94c668..aaf8630d045 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -172,7 +172,7 @@ jobs: # RETRY_ONLY_MATCHING keeps a failing test failing on the first attempt # rather than letting a re-run launder a flake into a pass. env: - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM' run: | MVN_GOAL="verify" MVN_ARGS="" @@ -495,7 +495,7 @@ jobs: # could not be resolved" before a single test had run -- a Maven Central failure, which a # missing plugin version fails identically on every attempt anyway. env: - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|or one of its dependencies could not be resolved|authorization failed for https://repo\.maven\.apache\.org|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|or one of its dependencies could not be resolved|authorization failed for https://repo\.maven\.apache\.org|Connection reset|Premature end of Content-Length' # Five attempts a minute apart rather than three at thirty seconds: Central # refused two different jobs on this branch today, and a window of about a # minute is shorter than the incidents have been. It costs nothing on a real diff --git a/.github/workflows/protocol-e2e.yml b/.github/workflows/protocol-e2e.yml index 00dd7f0568c..cf9805ca442 100644 --- a/.github/workflows/protocol-e2e.yml +++ b/.github/workflows/protocol-e2e.yml @@ -40,7 +40,7 @@ jobs: # here with "Unresolveable build extension: archetype-packaging ... could not be resolved" # before anything was compiled, which is Maven Central rather than this repository. env: - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|or one of its dependencies could not be resolved|authorization failed for https://repo\.maven\.apache\.org|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|or one of its dependencies could not be resolved|authorization failed for https://repo\.maven\.apache\.org|Connection reset|Premature end of Content-Length' # Five attempts a minute apart rather than three at thirty seconds: Central # refused two different jobs on this branch today, and a window of about a # minute is shorter than the incidents have been. It costs nothing on a real diff --git a/.github/workflows/purchase-e2e.yml b/.github/workflows/purchase-e2e.yml index fb4af2d22fe..0fe96b1b7dd 100644 --- a/.github/workflows/purchase-e2e.yml +++ b/.github/workflows/purchase-e2e.yml @@ -104,7 +104,7 @@ jobs: # with "authorization failed for https://repo.maven.apache.org/maven2" against the JUnit # BOM and the publishing plugin -- Maven Central, not this repository. env: - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|or one of its dependencies could not be resolved|authorization failed for https://repo\.maven\.apache\.org|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|or one of its dependencies could not be resolved|authorization failed for https://repo\.maven\.apache\.org|Connection reset|Premature end of Content-Length' # Five attempts a minute apart rather than three at thirty seconds: Central # refused two different jobs on this branch today, and a window of about a # minute is shorter than the incidents have been. It costs nothing on a real diff --git a/.github/workflows/windows-cross-build-run.yml b/.github/workflows/windows-cross-build-run.yml index 3e258f275cc..80fff9ce60e 100644 --- a/.github/workflows/windows-cross-build-run.yml +++ b/.github/workflows/windows-cross-build-run.yml @@ -213,7 +213,7 @@ jobs: # translation failure that came and went would have passed on attempt two and taken # the gate green with it -- laundering exactly the kind of intermittent product bug # this job exists to catch. The unit test workflow narrows it for the same reason. - RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' + RETRY_ONLY_MATCHING: 'status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Connection reset|Premature end of Content-Length' run: | cd vm # Through the retry wrapper: this resolves plugins from Maven Central and the runner diff --git a/.github/workflows/windows-cross-compile.yml b/.github/workflows/windows-cross-compile.yml index 7f245f37fb3..f65ff8b682a 100644 --- a/.github/workflows/windows-cross-compile.yml +++ b/.github/workflows/windows-cross-compile.yml @@ -121,7 +121,7 @@ jobs: # retries reuse the previous attempt's target directories, so a partial output # can decide the result. Matching the output keeps a real failure terminal on # its first occurrence, and the retry starts from clean. - resolution='status: (403|429|50[0-9])|Could not transfer artifact|Unresolveable build extension|Non-resolvable import POM|Could not resolve dependencies' + resolution='status: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not resolve dependencies' goal=install for delay in 30 120 300 0; do # PIPESTATUS, not the pipeline's status: tee succeeds even when mvn does not, diff --git a/scripts/run-javase-cef-ffmpeg-smoke.py b/scripts/run-javase-cef-ffmpeg-smoke.py index 342268286f0..7208bf848fc 100644 --- a/scripts/run-javase-cef-ffmpeg-smoke.py +++ b/scripts/run-javase-cef-ffmpeg-smoke.py @@ -61,6 +61,11 @@ def tail_text(path: Path, max_lines: int = 60) -> str: # and a non-resolvable import POM included. _TRANSFER_FAILURE = re.compile( r"Could not transfer artifact" + # Maven's wording when it reaches the repository but cannot read the POM it + # needs to resolve a dependency. Also a fetch failure, and it is what a + # Central hiccup during copy-dependencies actually prints -- a shape that + # made a green build red without a retry because nothing here named it. + r"|Failed to read artifact descriptor" r"|transfer failed for" r"|status code: (?:40[38]|409|425|429|5\d\d)" r"|Too Many Requests" From 3fbc47f587a9e17c325cb80ba622d107b233d500 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:29:09 +0300 Subject: [PATCH 027/115] Ask the build where it wrote, and key the gate by what it reads Three from the same review, each a follow-up to the previous round's fix. Trying the conventional target/classes before the classpath looked harmless and was not. A project that moves to a configured output directory without running clean leaves the old target/classes in place, manifest and class together, so the staleness check compares two obsolete files against each other, finds them consistent, and publishes last week's hints while the real ones sit on the classpath. The classpath is searched first now -- it is the output the build is actually using -- with the conventional path kept only for a launch that never passed the module's output directory at all. The computed-site file was keyed by path, so a builder already listed absorbed a SECOND computed hint for free: adding `platform + ".maps.apiKey"` beside the provider expression kept the gate green with the new hint uncatalogued. Keyed by file AND expression now, whitespace-normalised so a reformat is not a change, and split from both ends so an expression containing a pipe still parses. The line number is deliberately not part of the key, so moving code does not churn the file. Verified by adding exactly that second expression: the gate names it. Alias discovery still used a raw indexOf, so a commented-out `// import ...Ios as Old` above the live `import ...Ios as BuildIos` won, and the live @BuildIos was never looked for -- reinstating the bug the alias support was added for. It uses the same comment-aware walk as the marker search, and the occurrence must be the target of an `import` on that line, so a mention of the package in code is not read as one. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 54 ++++++++++--------- scripts/build-hint-computed-sites.txt | 18 ++++--- scripts/check-build-hint-catalog.py | 24 ++++++--- .../settings/CodenameOneSettings.java | 32 ++++++++++- .../settings/BuildHintCatalogTest.java | 23 ++++++++ 5 files changed, 112 insertions(+), 39 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index 99179a211cc..7b268f80a0f 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -557,32 +557,38 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat private static File findAnnotationManifest(File projectDir, String classPathStr) { String resource = "META-INF" + File.separator + "codenameone" + File.separator + "build-hints.properties"; - File conventional = new File(projectDir, "target" + File.separator + "classes" - + File.separator + resource); - if (conventional.isFile()) { - return conventional; - } - if (classPathStr == null) { - return null; - } - for (String entry : classPathStr.split(java.util.regex.Pattern.quote(File.pathSeparator))) { - if (entry.length() == 0) { - continue; - } - File dir = new File(entry); - if (!dir.isDirectory()) { - // A jar can carry this resource too, but only as a dependency -- - // and a dependency's hints belong to whoever built it, which the - // main-class stamp exists to reject. Directories are this - // project's own output. - continue; - } - File candidate = new File(dir, resource); - if (candidate.isFile()) { - return candidate; + // The classpath first, because it is the output the build is ACTUALLY + // using. Trying the conventional path first looked harmless and is not: + // a project that moves to a configured output directory without running + // clean leaves the old target/classes in place, complete with its old + // manifest and the old class beside it, so the staleness check compares + // two obsolete files against each other, finds them consistent, and + // publishes last week's hints while the real ones sit on the classpath. + if (classPathStr != null) { + for (String entry + : classPathStr.split(java.util.regex.Pattern.quote(File.pathSeparator))) { + if (entry.length() == 0) { + continue; + } + File dir = new File(entry); + if (!dir.isDirectory()) { + // A jar can carry this resource too, but only as a dependency -- + // and a dependency's hints belong to whoever built it, which the + // main-class stamp exists to reject. Directories are this + // project's own output. + continue; + } + File candidate = new File(dir, resource); + if (candidate.isFile()) { + return candidate; + } } } - return null; + // Only when the classpath carries none: a launch that did not pass the + // module's output directory at all still finds a conventional build. + File conventional = new File(projectDir, "target" + File.separator + "classes" + + File.separator + resource); + return conventional.isFile() ? conventional : null; } /** diff --git a/scripts/build-hint-computed-sites.txt b/scripts/build-hint-computed-sites.txt index 0edf7eec13c..bd905dbff14 100644 --- a/scripts/build-hint-computed-sites.txt +++ b/scripts/build-hint-computed-sites.txt @@ -9,14 +9,20 @@ # every expansion must be catalogued or match a dynamic pattern. A new computed # site therefore forces a catalog decision instead of disappearing. # -# Format: :|[,...] +# Format: ||[,...] +# +# Keyed by the expression, not just the file: a builder that already appears here +# would otherwise absorb a SECOND computed hint for free -- adding +# `platform + ".maps.apiKey"` beside the provider one kept the gate green while +# the new hint had no catalog row. The line number is deliberately not part of +# the key, so moving code does not churn this file. # # Only sites whose expression contains a string literal are reported. A helper # that forwards a variable -- getArg(key, ...) inside a wrapper -- gets its # literal from its caller, which the ordinary literal pass already mines. -maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MapsProviderInjector.java|android.maps.provider,ios.maps.provider -maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/NativeVerifyOption.java|ios.nativeVerify,linux.nativeVerify,windows.nativeVerify -maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MacNativeBuilder.java|macNative.provisioningProfile.appStore,macNative.provisioningProfile.developerID -maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java|ios.entitlements.com.apple.developer.healthkit.access -maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/WatchNativeBuilder.java|ios.entitlements.com.apple.developer.healthkit.access +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MapsProviderInjector.java|platform + ".maps.provider"|android.maps.provider,ios.maps.provider +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/NativeVerifyOption.java|platformPrefix + "." + HINT|ios.nativeVerify,linux.nativeVerify,windows.nativeVerify +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MacNativeBuilder.java|"macNative.provisioningProfile." + (isAppStore ? "appStore" : "developerID")|macNative.provisioningProfile.appStore,macNative.provisioningProfile.developerID +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java|"ios.entitlements.com.apple.developer.healthkit." + suffix|ios.entitlements.com.apple.developer.healthkit.access +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/WatchNativeBuilder.java|"ios.entitlements.com.apple.developer.healthkit." + canonicalKey|ios.entitlements.com.apple.developer.healthkit.access diff --git a/scripts/check-build-hint-catalog.py b/scripts/check-build-hint-catalog.py index b338c58f513..6cb813f3b39 100755 --- a/scripts/check-build-hint-catalog.py +++ b/scripts/check-build-hint-catalog.py @@ -159,22 +159,32 @@ def main(): line = line.strip() if not line or line.startswith("#"): continue - where, _, expansions = line.partition("|") - declared[where] = [e for e in expansions.split(",") if e] + # Split from both ends: the expression sits in the middle and may + # itself contain a pipe, while the file and the expansion list + # cannot. + path, _, rest = line.partition("|") + expr, _, expansions = rest.rpartition("|") + declared[(path, " ".join(expr.split()))] = [ + e for e in expansions.split(",") if e] computed_bad = [] - for expr, path, line_no in miner.hits_computed(): - if path not in declared: + mined = miner.hits_computed() + for expr, path, line_no in mined: + key = (path, " ".join(expr.split())) + if key not in declared: + # Keyed by the expression as well as the file, so a builder already + # listed cannot absorb a second computed hint for free. computed_bad.append( f"{path}:{line_no} builds a hint name from `{expr}` and is not listed") continue - for name in declared[path]: + for name in declared[key]: if name in known or any(fnmatch.fnmatch(name, p) for p in patterns): continue computed_bad.append( f"{path}:{line_no} expands to {name}, which the catalog does not describe") - for path in sorted(set(declared) - {p for _, p, _ in miner.hits_computed()}): - computed_bad.append(f"{path} is listed but no longer builds a hint name") + mined_keys = {(path, " ".join(expr.split())) for expr, path, _ in mined} + for path, expr in sorted(set(declared) - mined_keys): + computed_bad.append(f"{path} is listed for `{expr}`, which no longer builds a hint name") if computed_bad: print("check-build-hint-catalog: computed hint names are unaccounted for:", file=sys.stderr) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 05d22bfec22..9633dbc05d9 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2196,9 +2196,18 @@ private String readIfPresent(String path) { /// itself created. static String kotlinImportAlias(String source, String simple) { String needle = "com.codename1.annotations.buildhints." + simple; - int at = source.indexOf(needle); + // Same comment-aware walk the marker search uses, and the occurrence has + // to be a live `import` directive. A commented-out earlier alias -- + // `// import ...Ios as Old` above the real `import ...Ios as BuildIos` -- + // otherwise won, the live `@BuildIos` was never looked for, and the hint + // read as unowned again: the exact bug the alias support was added for. + int at = nextMarker(source, needle, 0); while (at >= 0) { int after = at + needle.length(); + if (!precededByImport(source, at)) { + at = nextMarker(source, needle, after); + continue; + } if (after >= source.length() || !continuesAName(source.charAt(after))) { int i = after; while (i < source.length() && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { @@ -2221,11 +2230,30 @@ static String kotlinImportAlias(String source, String simple) { } } } - at = source.indexOf(needle, after); + at = nextMarker(source, needle, after); } return null; } + /// Whether the token at `at` is the target of an `import` on the same line. + /// + /// Without this a mention of the package in code or in a doc string would be + /// read as an import directive. + private static boolean precededByImport(String source, int at) { + int i = at - 1; + while (i >= 0 && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { + i--; + } + if (i < 5) { + return false; + } + if (!source.regionMatches(i - 5, "import", 0, 6)) { + return false; + } + int before = i - 6; + return before < 0 || !continuesAName(source.charAt(before)); + } + /// Maps every `@Group(attr = ...)` on the main class to the hints it sets. static void collectAnnotationOwnedHints(String source, java.util.Map out) { for (com.codename1.build.shared.BuildHints.Hint h : com.codename1.build.shared.BuildHints.entries()) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index c77c58954e3..fc17d6d13bb 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -322,4 +322,27 @@ public void aLiveAnnotationAfterACommentedOneIsStillFound() { CodenameOneSettings.collectAnnotationOwnedHints(src, out); assertEquals("@Ios(teamId)", out.get("ios.teamId")); } + + /// A commented-out earlier alias must not win over the live import. It did, + /// and then the live `@BuildIos` was never looked for at all -- the very bug + /// the alias support exists to prevent. + @Test + public void aCommentedOutAliasDoesNotShadowTheLiveOne() { + String src = "// import com.codename1.annotations.buildhints.Ios as Old\n" + + "import com.codename1.annotations.buildhints.Ios as BuildIos\n" + + "@BuildIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + assertEquals("BuildIos", CodenameOneSettings.kotlinImportAlias(src, "Ios")); + + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } + + /// The package named somewhere that is not an import is not an alias. + @Test + public void aMentionThatIsNotAnImportIsNotAnAlias() { + assertNull(CodenameOneSettings.kotlinImportAlias( + "val doc = com.codename1.annotations.buildhints.Ios as Whatever", "Ios")); + } } From 9162f412479521814442545071e652cf6f5b75cf Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:39:50 +0300 Subject: [PATCH 028/115] Collapse the two capture-record spellings, and stop guessing Three from the same review. and.captureRecord is not an abbreviation of android.captureRecord: the builder reads the long name and then lets the short one override it, so the two name one setting. Uncatalogued as an alias, @Android(captureRecord) and a properties line spelling it the short way were both accepted -- and the properties line wins in the builder, so the compile-checked annotation was silently ineffective, which is exactly the failure this feature exists to remove. Marked as an alias, with a processor test asserting the pair now conflicts. and.facebook_permissions has the same override relationship and is marked too: nothing can conflict with it today since the long name is not annotated, but recording it means annotating that name later cannot reintroduce this. Settings looked for an annotation's argument list with indexOf('('), and parentheses are optional -- a bare @Ios is legal Java and Kotlin. It therefore adopted whatever call came next, so `@Ios` above a `configure(teamId = "...")` read as owning ios.teamId and the tool withheld Add and the editor for a hint the processor never emits. The next LIVE character after the name must now be the paren, comments skipped, so an annotation's own list is still found across one and a bare annotation adopts nothing. The simulator never checked the manifest's main-class stamp. Change codename1.mainName without a clean build and the old class and its manifest stay together in the output directory, perfectly consistent with each other, so the timestamp check passes them and the previous application's hints get published. The native merge has refused this since it was written; the simulator does now too. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 50 +++++++++++++++++++ .../_generated-build-hints.adoc | 4 +- .../build/shared/BuildHintsAndroid.java | 20 +++++++- .../BuildHintAnnotationProcessorTest.java | 17 +++++++ .../settings/CodenameOneSettings.java | 37 ++++++++++++-- .../settings/BuildHintCatalogTest.java | 35 +++++++++++++ 6 files changed, 155 insertions(+), 8 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index 7b268f80a0f..163c8ac1ff0 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -501,6 +501,18 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat } } } + String stampedFor = p.getProperty("cn1.buildHints.mainClass"); + String expectedMain = configuredMainClass(projectDir); + if (stampedFor != null && expectedMain != null && !stampedFor.equals(expectedMain)) { + // Somebody else's configuration. codename1.mainName changing without a + // clean build leaves the old class and its manifest together in the + // output directory, and the timestamp check finds that pair perfectly + // consistent -- so without the stamp the simulator runs the previous + // application's hints. The native merge already refuses this. + System.err.println("Warning: " + f + " was generated for " + stampedFor + + ", not " + expectedMain + ", so its build hints were NOT applied."); + return; + } File staleAgainst = classNewerThanManifest(p, f); if (staleAgainst != null) { // Nothing removes target/classes between builds, so a project that ran @@ -541,6 +553,44 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat } /** + * The main class this project is configured to launch, or null. + * + *

Read from the settings file rather than from the system properties: the + * file is what the annotation processor stamped the manifest against, and a + * {@code -D} override is a launch choice rather than a change of identity.

+ */ + private static String configuredMainClass(File projectDir) { + File settings = new File(projectDir, "codenameone_settings.properties"); + if (!settings.isFile()) { + return null; + } + java.util.Properties p = new java.util.Properties(); + FileInputStream in = null; + try { + in = new FileInputStream(settings); + p.load(in); + } catch (IOException ex) { + return null; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + // read-only stream; nothing useful to do + } + } + } + String main = p.getProperty("codename1.mainName"); + if (main == null || main.trim().length() == 0) { + return null; + } + String pkg = p.getProperty("codename1.packageName"); + return (pkg == null || pkg.trim().length() == 0) + ? main.trim() : pkg.trim() + "." + main.trim(); + } + + /** + * The emitted build hint manifest, or null when there is none. /** * The emitted build hint manifest, or null when there is none. * *

{@code target/classes} is only the default: a module may configure diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc index 939eb90d6a6..a2bc407525c 100644 --- a/docs/developer-guide/_generated-build-hints.adoc +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -14,13 +14,13 @@ |string |_(none)_ |_(none)_ -| +|Override alias of `android.captureRecord`, read after it and winning when set. |and.facebook_permissions |string |_(none)_ |_(none)_ -| +|Override alias of `android.facebook_permissions`, read after it and winning when set. `IPhoneBuilder` also falls back to it when `ios.facebook_permissions` is unset. |and.themeMode |`auto`, `modern`, `hololight`, `legacy` diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java index 3b081bb3a26..1e4c10d7916 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java @@ -44,17 +44,33 @@ private BuildHintsAndroid() { } static void register(List h) { + // Not an abbreviation: the builder reads android.captureRecord and then + // lets and.captureRecord override it, so the two name ONE setting. + // Without the alias, @Android(captureRecord) and a properties line + // spelling it the short way are not seen as a conflict -- and the + // properties line wins, leaving the compile-checked annotation silently + // ineffective, which is the whole failure this feature exists to remove. h.add(new Hint("and.captureRecord") + .aliasOf("android.captureRecord") .group(HintGroup.ANDROID) .type(HintType.STRING) .platform("android") - .consumedBy("AndroidGradleBuilder")); + .consumedBy("AndroidGradleBuilder") + .doc("Override alias of `android.captureRecord`, read after it and winning when set.")); + // Same override relationship (AndroidGradleBuilder reads the long name and + // then this one). Not annotated today, so nothing can conflict with it yet + // -- recorded so Settings collapses the pair, and so annotating the long + // name later cannot reintroduce the captureRecord bug. h.add(new Hint("and.facebook_permissions") + .aliasOf("android.facebook_permissions") .group(HintGroup.ANDROID) .type(HintType.STRING) .platform("android") - .consumedBy("AndroidGradleBuilder", "IPhoneBuilder")); + .consumedBy("AndroidGradleBuilder", "IPhoneBuilder") + .doc("Override alias of `android.facebook_permissions`, read after it and winning " + + "when set. `IPhoneBuilder` also falls back to it when " + + "`ios.facebook_permissions` is unset.")); h.add(new Hint("and.themeMode") .annotatedAs(HintGroup.ANDROID, "themeMode") diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index b7fe0094f31..1c9dc66d71b 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -277,6 +277,23 @@ public void anAnnotationWithNoMembersStillEmitsAStampedManifest() throws Excepti } } + /// `and.captureRecord` is not an abbreviation: the builder reads + /// `android.captureRecord` and then lets the short spelling override it, so + /// the two name one setting. Without the alias the annotation and a + /// properties line spelling it the short way were both accepted -- and the + /// properties line wins in the builder, leaving the compile-checked + /// annotation silently ineffective. + @Test + public void theShortSpellingOfAnAliasedHintStillConflicts() throws Exception { + Properties s = new Properties(); + s.load(new ByteArrayInputStream( + ("codename1.mainName=MyApp\ncodename1.packageName=com.example\n" + + "codename1.arg.and.captureRecord=disabled\n").getBytes("ISO-8859-1"))); + File classes = compile("@Android(captureRecord = \"enabled\")"); + ProcessorContext ctx = run(classes, s, MAIN, false); + assertErrorContaining(ctx, "and.captureRecord"); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 9633dbc05d9..b5176c910b7 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2293,9 +2293,16 @@ static void collectAnnotationOwnedHints(String source, java.util.Map= '0' && c <= '9') || c == '_' || c == '$'; } - /// The next occurrence of `marker` that is real code, or -1. + /// The index of the next character that is neither whitespace nor part of a + /// comment, starting at `from`; -1 when the source ends first. + static int nextLiveChar(String source, int from) { + int i = from; + while (i < source.length()) { + char c = source.charAt(i); + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f') { + i++; + continue; + } + if (c == '/') { + int skipped = skipNonCode(source, i); + if (skipped > i) { + i = skipped; + continue; + } + } + return i; + } + return -1; + } + + /// The next occurrence of `marker` that is real code, or -1. /// The next occurrence of `marker` that is real code, or -1. /// /// Comments and string literals are stepped over with the same scanner the /// argument reader uses, so what counts as code is one answer rather than diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index fc17d6d13bb..d79994e2431 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -345,4 +345,39 @@ public void aMentionThatIsNotAnImportIsNotAnAlias() { assertNull(CodenameOneSettings.kotlinImportAlias( "val doc = com.codename1.annotations.buildhints.Ios as Whatever", "Ios")); } + + /// Parentheses are optional on an annotation, so searching forward for the + /// next `(` adopted whatever call came after it. Settings then withheld the + /// Add and editor controls for a hint the processor never emits. + @Test + public void aBareAnnotationDoesNotAdoptTheNextCall() { + String src = "@Ios\n" + + "class MyApp {\n" + + " fun setUp() { configure(teamId = \"ABCDE12345\") }\n" + + "}\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertNull(out.get("ios.teamId")); + } + + /// A comment between the name and its own argument list is still its own. + @Test + public void anAnnotationsOwnArgumentListIsStillFoundAcrossAComment() { + String src = "@Ios /* why */ (teamId = \"ABCDE12345\")\nclass MyApp\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } + + /// The two capture-record spellings are one setting: the builder reads the + /// long name and then lets the short one override it. Without the alias an + /// annotation and a properties line are not seen as a conflict, and the + /// properties line silently wins over the compile-checked annotation. + @Test + public void theShortCaptureRecordSpellingIsAnAliasOfTheLongOne() { + assertEquals("android.captureRecord", + com.codename1.build.shared.BuildHints.canonicalName("and.captureRecord")); + assertEquals("android.facebook_permissions", + com.codename1.build.shared.BuildHints.canonicalName("and.facebook_permissions")); + } } From 392e2a5f9a80000624c27a96adaf2db864fd2ea9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:51:09 +0300 Subject: [PATCH 029/115] Let the simulator see a duplicate declaration too Two from the same review. Publishing an annotation hint the properties file also declares buries the error rather than reporting it: buildHint() reads the system property before the settings file, so the line the developer just added is silently ignored in the simulator while the device build refuses to run at all. Reachable because editing the properties file does not touch the class, so the timestamp check still finds the manifest current. The pair is detected before publishing now, and the annotation value is withheld with a message naming the hint. Aliases have to collapse for that check, and the catalog that knows about them is a build-time artifact this port cannot reach. Rather than give the JavaSE port a dependency on it, the processor writes the other spellings of each hint into the manifest -- cn1.buildHints.alias. -- alongside the origin it already records, and only where a hint has more than one. The conflict check and the manifest now derive that set from one method instead of two that can disagree. This is the third time the simulator has lagged a check the native merge already performed. Recorded in the reply as such: if a fourth appears the two paths want a shared decision rather than parallel implementations. skipNonCode had no triple-quote branch, so a Kotlin raw string or Java text block read as an empty literal followed by a new one -- and an embedded quote then opened a literal that swallowed the annotation after it, leaving the hint unowned and Add free to write the duplicate. Both directions are tested: a raw string containing a quote no longer hides the annotation after it, and an annotation written inside one is still not ownership. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 68 +++++++++++++++++-- .../BuildHintAnnotationProcessor.java | 53 ++++++++++++--- .../settings/CodenameOneSettings.java | 8 +++ .../settings/BuildHintCatalogTest.java | 22 ++++++ 4 files changed, 135 insertions(+), 16 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index 163c8ac1ff0..c2ceefcfa7e 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -537,11 +537,26 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat + "goal regenerates it."); return; } + // A hint declared BOTH ways is a build error, and the native merge says + // so. Publishing it here instead would bury it: buildHint() reads the + // system property before the settings file, so the line the developer + // just added to codenameone_settings.properties would be silently + // ignored by the simulator while the device build refused to run at all. + // Reachable because editing the properties file does not touch the class, + // so the timestamp check above still finds the manifest current. + java.util.Properties declared = loadProjectSettings(projectDir); int applied = 0; for (String key : p.stringPropertyNames()) { if (!key.startsWith("codename1.arg.")) { continue; } + String conflict = declaredInPropertiesToo(p, declared, key); + if (conflict != null) { + System.err.println("Warning: " + conflict + " is declared both as an annotation " + + "and in codenameone_settings.properties, so the annotation value was " + + "NOT applied. Delete one of them -- a build will refuse this."); + continue; + } if (System.getProperty(key) == null) { System.setProperty(key, p.getProperty(key)); applied++; @@ -553,13 +568,40 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat } /** - * The main class this project is configured to launch, or null. + * The properties key that declares the same setting as {@code key} in the + * settings file, or null when the file declares none of its spellings. * - *

Read from the settings file rather than from the system properties: the - * file is what the annotation processor stamped the manifest against, and a - * {@code -D} override is a launch choice rather than a change of identity.

+ *

An alias and its target name one setting -- the builder reads + * {@code android.captureRecord} and then lets {@code and.captureRecord} + * override it -- so either spelling in the file collides. The spellings come + * out of the manifest, which the annotation processor writes them into, + * because the catalog that knows about aliases is a build-time artifact and + * this port cannot reach it.

*/ - private static String configuredMainClass(File projectDir) { + private static String declaredInPropertiesToo(java.util.Properties manifest, + java.util.Properties declared, String key) { + if (declared == null) { + return null; + } + if (declared.getProperty(key) != null) { + return key; + } + String name = key.substring("codename1.arg.".length()); + String aliases = manifest.getProperty("cn1.buildHints.alias." + name); + if (aliases == null) { + return null; + } + for (String alias : aliases.split(",")) { + String other = "codename1.arg." + alias.trim(); + if (alias.trim().length() > 0 && declared.getProperty(other) != null) { + return other; + } + } + return null; + } + + /** The project's settings file, or null when it cannot be read. */ + private static java.util.Properties loadProjectSettings(File projectDir) { File settings = new File(projectDir, "codenameone_settings.properties"); if (!settings.isFile()) { return null; @@ -569,6 +611,7 @@ private static String configuredMainClass(File projectDir) { try { in = new FileInputStream(settings); p.load(in); + return p; } catch (IOException ex) { return null; } finally { @@ -580,6 +623,21 @@ private static String configuredMainClass(File projectDir) { } } } + } + + /** + * The main class this project is configured to launch, or null. /** + * The main class this project is configured to launch, or null. + * + *

Read from the settings file rather than from the system properties: the + * file is what the annotation processor stamped the manifest against, and a + * {@code -D} override is a launch choice rather than a change of identity.

+ */ + private static String configuredMainClass(File projectDir) { + java.util.Properties p = loadProjectSettings(projectDir); + if (p == null) { + return null; + } String main = p.getProperty("codename1.mainName"); if (main == null || main.trim().length() == 0) { return null; diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index b1d48c568b1..ba88da93aad 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -68,6 +68,10 @@ public class BuildHintAnnotationProcessor extends AbstractAnnotationProcessor { /// -- the conflict message, the simulator's hint editor -- can name it. private static final String ORIGIN_PREFIX = "cn1.buildHints.origin."; + /// Other names for the same setting, for a consumer that cannot reach the + /// catalog to resolve an alias itself. + private static final String ALIAS_PREFIX = "cn1.buildHints.alias."; + /// Stamps the emitted file with the main class it came from, so a stale or /// foreign copy on the classpath can be recognised rather than merged. private static final String MAIN_CLASS_KEY = "cn1.buildHints.mainClass"; @@ -218,16 +222,7 @@ private void checkConflicts(ProcessorContext ctx) { } Map lines = propertyLines(ctx); for (Map.Entry e : hints.entrySet()) { - // An alias and its target name one setting, so declaring the alias - // in the file still collides with the annotation. - Set names = new LinkedHashSet(); - names.add(e.getKey()); - for (BuildHints.Hint h : BuildHints.entries()) { - if (e.getKey().equals(h.aliasOf()) || e.getKey().equals( - BuildHints.canonicalName(h.name()))) { - names.add(h.name()); - } - } + Set names = spellingsOf(e.getKey()); for (String name : names) { String key = BuildHints.ARG_PREFIX + name; if (settings.getProperty(key) == null) { @@ -370,7 +365,23 @@ private String wireValue(AnnotatedClass cls, String descriptor, String member, O /// Not `Properties.store`: it writes a timestamp comment, so the bytes would /// differ on every build. That churns the resource in every incremental /// build and defeats the staged-jar staleness comparison in `CN1BuildMojo`. - /// A stable fingerprint of every build hint annotation on `cls`. + /// Every name that denotes the same setting as `hint`, itself included. + /// + /// An alias and its target are one setting -- the builder reads + /// `android.captureRecord` and then lets `and.captureRecord` override it -- + /// so declaring either in the properties file collides with the annotation. + static Set spellingsOf(String hint) { + Set names = new LinkedHashSet(); + names.add(hint); + for (BuildHints.Hint h : BuildHints.entries()) { + if (hint.equals(h.aliasOf()) || hint.equals(BuildHints.canonicalName(h.name()))) { + names.add(h.name()); + } + } + return names; + } + + /// A stable fingerprint of every build hint annotation on `cls`. /// A stable fingerprint of every build hint annotation on `cls`. /// /// Taken over the raw annotation members rather than over the hints they /// convert into, so it changes for anything the developer can change: a @@ -461,6 +472,26 @@ private byte[] serialize(ProcessorContext ctx) throws ProcessingException { sb.append(escape(ORIGIN_PREFIX + e.getKey())).append('=') .append(escape(e.getValue())).append('\n'); } + // The other spellings of each hint, for a consumer that has to collapse + // them and cannot reach the catalog -- the simulator, which lives in the + // JavaSE port. Written only where there is more than one, so the common + // hint costs nothing. + for (String hint : hints.keySet()) { + Set spellings = spellingsOf(hint); + spellings.remove(hint); + if (spellings.isEmpty()) { + continue; + } + StringBuilder joined = new StringBuilder(); + for (String name : spellings) { + if (joined.length() > 0) { + joined.append(','); + } + joined.append(name); + } + sb.append(escape(ALIAS_PREFIX + hint)).append('=') + .append(escape(joined.toString())).append('\n'); + } try { return sb.toString().getBytes("ISO-8859-1"); } catch (UnsupportedEncodingException ex) { diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index b5176c910b7..6579eaa3d05 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2434,6 +2434,14 @@ static int nextMarker(String source, String marker, int from) { /// past it; otherwise `i`. private static int skipNonCode(String s, int i) { char c = s.charAt(i); + // A Kotlin raw string or a Java text block, which the ordinary rule reads + // as an empty string followed by a new one -- and then an embedded quote + // inside it opens a literal that swallows the annotation after it. No + // escapes apply inside either form; the literal ends at the next """. + if (c == '"' && s.startsWith("\"\"\"", i)) { + int close = s.indexOf("\"\"\"", i + 3); + return close < 0 ? s.length() : close + 3; + } if (c == '"') { for (int j = i + 1; j < s.length(); j++) { if (s.charAt(j) == '\\') { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index d79994e2431..3992c444a3b 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -380,4 +380,26 @@ public void theShortCaptureRecordSpellingIsAnAliasOfTheLongOne() { assertEquals("android.facebook_permissions", com.codename1.build.shared.BuildHints.canonicalName("and.facebook_permissions")); } + + /// A Kotlin raw string containing a quote was read as an empty literal + /// followed by a new one, and that new one then swallowed the annotation + /// after it -- so the hint read as unowned and Add wrote the duplicate. + @Test + public void aTripleQuotedStringDoesNotSwallowTheAnnotation() { + String src = "val doc = \"\"\"quoted \" text\"\"\"\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } + + /// And an annotation written INSIDE a raw string is still not ownership. + @Test + public void anAnnotationInsideATripleQuotedStringIsNotOwnership() { + String src = "val doc = \"\"\"@Ios(teamId = \"x\")\"\"\"\nclass MyApp\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertNull(out.get("ios.teamId")); + } } From 01e471ff4c682dc0c9eaea75b8b01260332b6b72 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:01:27 +0300 Subject: [PATCH 030/115] Take the published hints back out on a simulator reload A reload re-enters main() in the same JVM rather than starting a process, so everything published on the previous launch is still set. The rule that protects a -D value -- an existing value always wins -- then protected the PREVIOUS build's annotation value too: editing @Desktop(titleBar = ...) and reloading kept showing the old setting, and deleting the annotation altogether kept it forever, since the missing manifest takes an early return that touched nothing. What this method installed is now withdrawn at the top, before any early return, so a removed annotation and an unreadable manifest clear the value as surely as a changed one replaces it. Only what it installed. A -D was never a candidate, because a key that is already set is skipped rather than published, so it can never enter the withdraw set -- the command line keeps winning without a special case for it. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index c2ceefcfa7e..fca1228cc72 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -477,6 +477,16 @@ private List getExtraClasses() { * yet -- the loader is built from {@code files} further down.

*/ private static void publishAnnotationBuildHints(File projectDir, String classPathStr) { + // A reload re-enters main() in the SAME JVM, so anything published last + // time is still set. Withdraw it before deciding anything: otherwise the + // "existing value wins" rule that protects -D also protects the previous + // build's annotation value, and an edited @Desktop(titleBar = ...) -- or + // a deleted annotation, which takes an early return below -- keeps + // showing the old setting until the process is restarted. + // + // Only what THIS method installed is withdrawn. A -D was never installed + // here, because a key already set is skipped, so it is never a candidate. + withdrawPublishedHints(); if (projectDir == null) { return; } @@ -559,6 +569,7 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat } if (System.getProperty(key) == null) { System.setProperty(key, p.getProperty(key)); + PUBLISHED_HINTS.add(key); applied++; } } @@ -568,6 +579,28 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat } /** + * Keys this class installed into the system properties, so a reload can take + * them back out. + * + *

Static because a reload re-enters {@code main} in the same JVM rather + * than starting a process.

+ */ + private static final java.util.Set PUBLISHED_HINTS = + new java.util.HashSet(); + + /** Removes what a previous launch published, leaving anything else alone. */ + private static void withdrawPublishedHints() { + if (PUBLISHED_HINTS.isEmpty()) { + return; + } + for (String key : PUBLISHED_HINTS) { + System.clearProperty(key); + } + PUBLISHED_HINTS.clear(); + } + + /** + * The properties key that declares the same setting as {@code key} in the /** * The properties key that declares the same setting as {@code key} in the * settings file, or null when the file declares none of its spellings. * From cd00b3a4c7b71cd348710ab7b6a088c1fc80cfd9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:12:15 +0300 Subject: [PATCH 031/115] Read a text block the way its own language does Two from the same review. The triple-quote branch closed at the next """, which is wrong in BOTH languages and in opposite ways -- and getting it wrong over-consumes past a live annotation, so the hint reads as unowned and Settings writes the duplicate. Java escapes DO apply, so \""" is an escaped quote and two more, not a delimiter. Reading it as one made the REAL delimiter open a second text block that ran past whatever followed. Kotlin escapes do NOT apply, and a run of four or more quotes closes at its LAST three: """a"""" holds a" . So the scanner needs to know which language it is reading, and it can: the caller already picks the file by extension. The flag is threaded from there down to skipNonCode, with the two-argument entry point keeping Java rules for callers that have no file to name. Separately, the manifest was merged over the source result as a union. It cannot ADD ownership the source does not show: an attribute deleted from the main class and not yet rebuilt is precisely that, and the union kept Add and the editor hidden for a hint nothing owns any more, until the user happened to rebuild with nothing to suggest that was the fix. The source now decides WHICH hints are owned and the manifest only supplies their origins -- except when no source file could be read at all, which the scan now reports as null rather than as an empty result, because there the manifest is all there is. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 134 +++++++++++++----- .../settings/BuildHintCatalogTest.java | 43 +++++- 2 files changed, 137 insertions(+), 40 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 6579eaa3d05..041486ebcb7 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2106,10 +2106,16 @@ private java.util.Map loadAnnotationOwnedHints() { // the newly annotated hint looking unowned, and Add then wrote the // duplicate declaration the next build refuses. // - // The manifest is merged in on top for its origins; the union is the - // safe direction, since over-reporting ownership only withholds an - // editor, while under-reporting breaks the build. - out.putAll(annotationOwnedHintsFromSource()); + // The manifest supplies origins, but it cannot ADD ownership the + // source does not show. An attribute deleted from the main class and + // not yet rebuilt is exactly that case: the source is right, the + // manifest is a build old, and taking the union kept Add and the + // editor hidden for a hint nothing owns any more -- until the user + // happened to rebuild, with nothing to suggest that was the fix. + java.util.Map fromSource = annotationOwnedHintsFromSource(); + if (fromSource != null) { + out.putAll(fromSource); + } String url = ProjectIO.fsUrl(path); FileSystemStorage fs = FileSystemStorage.getInstance(); @@ -2127,8 +2133,14 @@ private java.util.Map loadAnnotationOwnedHints() { int eq = t.indexOf('='); if (eq > originPrefix.length()) { String hint = t.substring(originPrefix.length(), eq).trim(); - out.put(com.codename1.build.shared.BuildHints.canonicalName(hint), - t.substring(eq + 1).trim()); + String canonical = + com.codename1.build.shared.BuildHints.canonicalName(hint); + // Only for a hint the source still declares -- unless there + // was no source to read, where the manifest is all there is + // and is better than nothing. + if (fromSource == null || fromSource.containsKey(canonical)) { + out.put(canonical, t.substring(eq + 1).trim()); + } } } } catch (Exception ex) { @@ -2147,6 +2159,8 @@ private java.util.Map loadAnnotationOwnedHints() { /// annotation's parentheses and maps those to hint names through the catalog. /// Values are skipped wholesale, so a comma or bracket inside a string cannot /// confuse it. + /// The hints the main class's SOURCE declares, or null when no source file + /// for it could be read at all. private java.util.Map annotationOwnedHintsFromSource() { java.util.Map out = new java.util.HashMap<>(); String main = settings == null ? null : settings.get("codename1.mainName"); @@ -2162,11 +2176,14 @@ private java.util.Map annotationOwnedHintsFromSource() { if (text == null) { continue; } - collectAnnotationOwnedHints(text, out); + collectAnnotationOwnedHints(text, out, ext.equals(".kt")); return out; } } - return out; + // No source file found. Distinct from "found and declares nothing", and + // the caller has to tell them apart before letting the source overrule + // the manifest. + return null; } private String readIfPresent(String path) { @@ -2194,18 +2211,18 @@ private String readIfPresent(String path) { /// hint as unowned, so Settings offers it for Add, writes the properties /// line, and the next `process-annotations` fails on the duplicate the tool /// itself created. - static String kotlinImportAlias(String source, String simple) { + static String kotlinImportAlias(String source, String simple, boolean kotlin) { String needle = "com.codename1.annotations.buildhints." + simple; // Same comment-aware walk the marker search uses, and the occurrence has // to be a live `import` directive. A commented-out earlier alias -- // `// import ...Ios as Old` above the real `import ...Ios as BuildIos` -- // otherwise won, the live `@BuildIos` was never looked for, and the hint // read as unowned again: the exact bug the alias support was added for. - int at = nextMarker(source, needle, 0); + int at = nextMarker(source, needle, 0, kotlin); while (at >= 0) { int after = at + needle.length(); if (!precededByImport(source, at)) { - at = nextMarker(source, needle, after); + at = nextMarker(source, needle, after, kotlin); continue; } if (after >= source.length() || !continuesAName(source.charAt(after))) { @@ -2230,7 +2247,7 @@ static String kotlinImportAlias(String source, String simple) { } } } - at = nextMarker(source, needle, after); + at = nextMarker(source, needle, after, kotlin); } return null; } @@ -2255,7 +2272,13 @@ private static boolean precededByImport(String source, int at) { } /// Maps every `@Group(attr = ...)` on the main class to the hints it sets. + /// Java rules for the source text; see the three-argument form. static void collectAnnotationOwnedHints(String source, java.util.Map out) { + collectAnnotationOwnedHints(source, out, false); + } + + static void collectAnnotationOwnedHints(String source, java.util.Map out, + boolean kotlin) { for (com.codename1.build.shared.BuildHints.Hint h : com.codename1.build.shared.BuildHints.entries()) { if (!h.isAnnotated()) { continue; @@ -2265,7 +2288,7 @@ static void collectAnnotationOwnedHints(String source, java.util.Map= 0) { // "@Ios" must not match "@IosPrivacy": the next character has // to end the name. int after = at + markers[m].length(); if (after < source.length() && continuesAName(source.charAt(after))) { - at = nextMarker(source, markers[m], after); + at = nextMarker(source, markers[m], after, kotlin); continue; } // The annotation's OWN argument list, not the next one in @@ -2299,19 +2322,19 @@ static void collectAnnotationOwnedHints(String source, java.util.Map i) { i = skipped - 1; continue; @@ -2346,11 +2369,11 @@ private static String balancedArgs(String source, int open) { /// Whether `args` assigns `attr` at the top level, ignoring anything inside a /// nested value, a string, a character literal or a comment. - private static boolean declaresAttribute(String args, String attr) { + private static boolean declaresAttribute(String args, String attr, boolean kotlin) { int depth = 0; StringBuilder word = new StringBuilder(); for (int i = 0; i < args.length(); i++) { - int skipped = skipNonCode(args, i); + int skipped = skipNonCode(args, i, kotlin); if (skipped > i) { i = skipped - 1; continue; @@ -2386,7 +2409,7 @@ private static boolean continuesAName(char c) { /// The index of the next character that is neither whitespace nor part of a /// comment, starting at `from`; -1 when the source ends first. - static int nextLiveChar(String source, int from) { + static int nextLiveChar(String source, int from, boolean kotlin) { int i = from; while (i < source.length()) { char c = source.charAt(i); @@ -2395,7 +2418,7 @@ static int nextLiveChar(String source, int from) { continue; } if (c == '/') { - int skipped = skipNonCode(source, i); + int skipped = skipNonCode(source, i, kotlin); if (skipped > i) { i = skipped; continue; @@ -2411,12 +2434,12 @@ static int nextLiveChar(String source, int from) { /// Comments and string literals are stepped over with the same scanner the /// argument reader uses, so what counts as code is one answer rather than /// two that can disagree. - static int nextMarker(String source, String marker, int from) { + static int nextMarker(String source, String marker, int from, boolean kotlin) { int i = from; while (i < source.length()) { char c = source.charAt(i); if (c == '"' || c == '\'' || c == '/') { - int skipped = skipNonCode(source, i); + int skipped = skipNonCode(source, i, kotlin); if (skipped > i) { i = skipped; continue; @@ -2432,15 +2455,60 @@ static int nextMarker(String source, String marker, int from) { /// If a string, character literal or comment starts at `i`, the index just /// past it; otherwise `i`. - private static int skipNonCode(String s, int i) { + /// Index just past a Java text block opening at `i`. Escapes apply, so a + /// backslash consumes the character after it and cannot start a delimiter. + private static int endOfJavaTextBlock(String s, int i) { + int j = i + 3; + while (j < s.length()) { + char c = s.charAt(j); + if (c == '\\') { + j += 2; + continue; + } + if (c == '"' && s.startsWith("\"\"\"", j)) { + return j + 3; + } + j++; + } + return s.length(); + } + + /// Index just past a Kotlin raw string opening at `i`. No escapes, and a run + /// of quotes closes at its last three, so the extra ones belong to the value. + private static int endOfKotlinRawString(String s, int i) { + int j = i + 3; + while (j < s.length()) { + if (s.charAt(j) != '"') { + j++; + continue; + } + int run = j; + while (run < s.length() && s.charAt(run) == '"') { + run++; + } + if (run - j >= 3) { + return run; + } + j = run; + } + return s.length(); + } + + private static int skipNonCode(String s, int i, boolean kotlin) { char c = s.charAt(i); // A Kotlin raw string or a Java text block, which the ordinary rule reads // as an empty string followed by a new one -- and then an embedded quote - // inside it opens a literal that swallows the annotation after it. No - // escapes apply inside either form; the literal ends at the next """. + // inside it opens a literal that swallows the annotation after it. + // + // The two languages close it differently, and taking the shorter reading + // in either direction over-consumes past a live annotation: + // + // Java escape sequences DO apply, so \" is one quote and the run + // \""" is an escaped quote followed by two, not a delimiter. + // Kotlin escapes do NOT apply, and a run of four or more quotes ends + // the literal at its LAST three -- """a"""" holds a" . if (c == '"' && s.startsWith("\"\"\"", i)) { - int close = s.indexOf("\"\"\"", i + 3); - return close < 0 ? s.length() : close + 3; + return kotlin ? endOfKotlinRawString(s, i) : endOfJavaTextBlock(s, i); } if (c == '"') { for (int j = i + 1; j < s.length(); j++) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 3992c444a3b..86190f2541d 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -272,7 +272,7 @@ public void aKotlinAliasedImportIsRecognized() { + "@BuildIos(teamId = \"ABCDE12345\")\n" + "class MyApp\n"; java.util.Map out = new java.util.HashMap(); - CodenameOneSettings.collectAnnotationOwnedHints(src, out); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); assertEquals("@Ios(teamId)", out.get("ios.teamId")); } @@ -281,7 +281,7 @@ public void aKotlinAliasedImportIsRecognized() { public void aPlainImportIsNotReadAsAnAlias() { String src = "import com.codename1.annotations.buildhints.Ios\n" + "@Ios(teamId = \"ABCDE12345\")\n"; - assertNull(CodenameOneSettings.kotlinImportAlias(src, "Ios")); + assertNull(CodenameOneSettings.kotlinImportAlias(src, "Ios", true)); } /// A commented-out annotation is not an annotation. Reading it as one made @@ -332,10 +332,10 @@ public void aCommentedOutAliasDoesNotShadowTheLiveOne() { + "import com.codename1.annotations.buildhints.Ios as BuildIos\n" + "@BuildIos(teamId = \"ABCDE12345\")\n" + "class MyApp\n"; - assertEquals("BuildIos", CodenameOneSettings.kotlinImportAlias(src, "Ios")); + assertEquals("BuildIos", CodenameOneSettings.kotlinImportAlias(src, "Ios", true)); java.util.Map out = new java.util.HashMap(); - CodenameOneSettings.collectAnnotationOwnedHints(src, out); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); assertEquals("@Ios(teamId)", out.get("ios.teamId")); } @@ -343,7 +343,7 @@ public void aCommentedOutAliasDoesNotShadowTheLiveOne() { @Test public void aMentionThatIsNotAnImportIsNotAnAlias() { assertNull(CodenameOneSettings.kotlinImportAlias( - "val doc = com.codename1.annotations.buildhints.Ios as Whatever", "Ios")); + "val doc = com.codename1.annotations.buildhints.Ios as Whatever", "Ios", true)); } /// Parentheses are optional on an annotation, so searching forward for the @@ -390,7 +390,7 @@ public void aTripleQuotedStringDoesNotSwallowTheAnnotation() { + "@Ios(teamId = \"ABCDE12345\")\n" + "class MyApp\n"; java.util.Map out = new java.util.HashMap(); - CodenameOneSettings.collectAnnotationOwnedHints(src, out); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); assertEquals("@Ios(teamId)", out.get("ios.teamId")); } @@ -399,7 +399,36 @@ public void aTripleQuotedStringDoesNotSwallowTheAnnotation() { public void anAnnotationInsideATripleQuotedStringIsNotOwnership() { String src = "val doc = \"\"\"@Ios(teamId = \"x\")\"\"\"\nclass MyApp\n"; java.util.Map out = new java.util.HashMap(); - CodenameOneSettings.collectAnnotationOwnedHints(src, out); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); assertNull(out.get("ios.teamId")); } + + /// Java text blocks DO process escapes, so `\\"""` is an escaped quote and + /// two more -- not the closing delimiter. Reading it as one made the scanner + /// treat the REAL delimiter as a new text block and run past the annotation + /// after it. + @Test + public void anEscapedQuoteRunDoesNotCloseAJavaTextBlock() { + String src = "String doc = \"\"\"\n" + + " a \\\"\"\" b\n" + + " \"\"\";\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "public class MyApp {}\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, false); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } + + /// Kotlin does NOT process escapes in a raw string, and a run of four quotes + /// closes at the last three -- the extra one belongs to the value. Applying + /// Java's rule here would keep scanning and swallow the annotation. + @Test + public void aQuoteRunClosesAKotlinRawStringAtItsLastThree() { + String src = "val doc = \"\"\"a\"\"\"\"\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } } From b4659212693a44f9caefbbc459ea1a6d1f9e6cbd Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:22:32 +0300 Subject: [PATCH 032/115] Find the hints that live in a table Three from the same review. Ten real hints had no catalog row while the gate reported success. IPhoneBuilder's WALLET_INJECTION_HINTS holds them in a String[][] and reaches getArg as hintAndMarker[0], so no literal anywhere in the tree sits at a getArg call -- invisible to every literal search, this gate included, which is exactly the hole the computed-site accounting was added to close. A subscript now counts as a computed name alongside a built one; that adds precisely one site, and its ten expansions are catalogued and accounted for. An output directory keeps class files whose source is gone. Rename the main class, update codename1.mainName, skip the clean, and the old annotated .class still sits there -- so every incremental build failed with a placement error naming a class the developer had already deleted, and the orphan's hints were merged in besides. A class that is not the main one and has no source under the project is now ignored. Two limits on that, and the second only because the tests caught it: the main class is never dropped, because failing to find ITS source would silently apply none of a project's hints, which is worse than any placement message; and a project with no source tree at all is left alone entirely. Absence of a source tree is not evidence a class is orphaned, it means this is a layout the lookup does not know. The migration read byName(), which returns the ALIAS entry for a legacy spelling -- and an alias's own isAnnotated() is false even though the setting it names has an annotation. So cn1.androidTheme, cn1.nativeTheme and and.captureRecord, the spellings an existing project is most likely to be carrying, were reported as having no annotation and left behind. Resolved through the alias now. Co-Authored-By: Claude Opus 5 (1M context) --- .../_generated-build-hints.adoc | 60 ++++++++++++++++ .../codename1/build/shared/BuildHintsIos.java | 70 +++++++++++++++++++ .../maven/MigrateBuildHintsMojo.java | 7 +- .../BuildHintAnnotationProcessor.java | 60 +++++++++++++++- scripts/build-hint-computed-sites.txt | 14 +++- scripts/build_hint_miner.py | 10 ++- 6 files changed, 213 insertions(+), 8 deletions(-) diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc index a2bc407525c..be24352411d 100644 --- a/docs/developer-guide/_generated-build-hints.adoc +++ b/docs/developer-guide/_generated-build-hints.adoc @@ -2742,6 +2742,18 @@ |_(none)_ |Boolean true/false defaults to false. Generates an Apple Wallet issuer provisioning extension (the "From apps on your iPhone" flow in the Wallet app) and embeds it in the build. Requires `ios.wallet.appGroup` and `ios.wallet.issuerEndpoint`. See the Apple Wallet Extension chapter. +|ios.wallet.generateRequestInject +|string +|_(none)_ +|_(none)_ +|Swift injected at the generate-request marker of the non-UI Wallet extension. + +|ios.wallet.generateResponseInject +|string +|_(none)_ +|_(none)_ +|Swift injected at the generate-response marker of the non-UI Wallet extension. + |ios.wallet.includeUI |boolean |`false` @@ -2760,12 +2772,60 @@ |_(none)_ | +|ios.wallet.nonuiImportsInject +|string +|_(none)_ +|_(none)_ +|Extra `import` lines for the non-UI Wallet extension. + +|ios.wallet.passEntriesInject +|string +|_(none)_ +|_(none)_ +|Swift injected where the non-UI Wallet extension lists its pass entries. + +|ios.wallet.remotePassEntriesInject +|string +|_(none)_ +|_(none)_ +|Swift injected where the non-UI Wallet extension lists its remote pass entries. + +|ios.wallet.statusInject +|string +|_(none)_ +|_(none)_ +|Swift injected at the status marker of the non-UI Wallet extension. + +|ios.wallet.uiAuthRequestInject +|string +|_(none)_ +|_(none)_ +|Swift injected at the auth-request marker of the UI Wallet extension. + +|ios.wallet.uiAuthResponseInject +|string +|_(none)_ +|_(none)_ +|Swift injected at the auth-response marker of the UI Wallet extension. + |ios.wallet.uiExtensionName |string |`WalletUIExtension` |_(none)_ | +|ios.wallet.uiImportsInject +|string +|_(none)_ +|_(none)_ +|Extra `import` lines for the UI Wallet extension. + +|ios.wallet.uiViewDidLoadInject +|string +|_(none)_ +|_(none)_ +|Swift injected into `viewDidLoad` of the UI Wallet extension. + |ios.xcode_version |string |_(none)_ diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java index 08902bc6b30..86928b783d9 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java @@ -1207,6 +1207,76 @@ static void register(List h) { .platform("ios") .consumedBy("IPhoneBuilder")); + h.add(new Hint("ios.wallet.generateRequestInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Swift injected at the generate-request marker of the non-UI Wallet extension.")); + + h.add(new Hint("ios.wallet.generateResponseInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Swift injected at the generate-response marker of the non-UI Wallet extension.")); + + h.add(new Hint("ios.wallet.nonuiImportsInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Extra `import` lines for the non-UI Wallet extension.")); + + h.add(new Hint("ios.wallet.passEntriesInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Swift injected where the non-UI Wallet extension lists its pass entries.")); + + h.add(new Hint("ios.wallet.remotePassEntriesInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Swift injected where the non-UI Wallet extension lists its remote pass entries.")); + + h.add(new Hint("ios.wallet.statusInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Swift injected at the status marker of the non-UI Wallet extension.")); + + h.add(new Hint("ios.wallet.uiAuthRequestInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Swift injected at the auth-request marker of the UI Wallet extension.")); + + h.add(new Hint("ios.wallet.uiAuthResponseInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Swift injected at the auth-response marker of the UI Wallet extension.")); + + h.add(new Hint("ios.wallet.uiImportsInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Extra `import` lines for the UI Wallet extension.")); + + h.add(new Hint("ios.wallet.uiViewDidLoadInject") + .group(HintGroup.IOS) + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Swift injected into `viewDidLoad` of the UI Wallet extension.")); + h.add(new Hint("ios.zbar_flash") .group(HintGroup.IOS) .type(HintType.BOOLEAN) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index ebafa84240a..e3e0457c9e9 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -150,7 +150,12 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException skipped.add(name + " (kept by configuration)"); continue; } - BuildHints.Hint hint = BuildHints.byName(name); + // Through the alias, not at it. byName("cn1.androidTheme") returns the + // alias entry, whose own isAnnotated() is false even though the + // setting it names has an annotation -- so the legacy spellings, which + // are exactly the ones an existing project is most likely to be + // carrying, were reported as having no annotation and left behind. + BuildHints.Hint hint = BuildHints.resolve(BuildHints.byName(name)); if (hint == null || !hint.isAnnotated()) { skipped.add(name + " (no annotation for this hint yet)"); continue; diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index ba88da93aad..5abc926ce41 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -131,6 +131,21 @@ public void processClass(AnnotatedClass cls, ProcessorContext ctx) throws Proces if (!carriesAny) { return; } + // An output directory keeps class files whose source is gone. Rename the + // main class, update codename1.mainName and skip the clean, and the old + // annotated .class is still sitting there -- so every incremental build + // failed with a placement error naming a class the developer had already + // deleted, and the orphan's hints were merged in besides. + // + // Only a class that is NOT the main one is dropped this way. The main + // class is processed whatever its source layout, because failing to find + // its source would otherwise mean silently applying none of its hints, + // which is worse than any placement message. + if (!isMainClass(cls, ctx) && !hasBackingSource(cls, ctx)) { + ctx.getLog().debug("cn1: ignoring " + cls.getBinaryName() + + " -- annotated, but no source for it; stale output from an earlier build"); + return; + } annotated.add(cls); for (Map.Entry e : cls.getClassAnnotations().entrySet()) { @@ -189,7 +204,50 @@ public void finish(ProcessorContext ctx) throws ProcessingException { + annotated.get(0).getBinaryName()); } - /// Build hints configure the application, so they belong on the class the + private static boolean isMainClass(AnnotatedClass cls, ProcessorContext ctx) { + String main = ctx.getMainClassBinaryName(); + return main != null && main.equals(cls.getBinaryName()); + } + + /// Whether a source file for `cls` still exists under the project. + /// + /// Answered "yes" whenever the question cannot actually be put: no project + /// directory, or no source tree under it to search. Absence of a source tree + /// is not evidence that a class is orphaned -- it means this is a layout the + /// lookup does not know, and dropping annotations on that basis would apply + /// none of a project's hints while reporting nothing. + private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx) { + File dir = ctx.getProjectDir(); + if (dir == null) { + return true; + } + // The outermost class owns the file: Outer$Inner lives in Outer.java. + String binary = cls.getBinaryName(); + int nested = binary.indexOf('$'); + if (nested >= 0) { + binary = binary.substring(0, nested); + } + String rel = binary.replace('.', File.separatorChar); + String[] roots = {"src" + File.separator + "main" + File.separator + "java", + "src" + File.separator + "main" + File.separator + "kotlin", + "src"}; + String[] extensions = {".java", ".kt"}; + boolean sawARoot = false; + for (String root : roots) { + if (!new File(dir, root).isDirectory()) { + continue; + } + sawARoot = true; + for (String ext : extensions) { + if (new File(dir, root + File.separator + rel + ext).isFile()) { + return true; + } + } + } + return !sawARoot; + } + + /// Build hints configure the application, so they belong on the class the /// Build hints configure the application, so they belong on the class the /// project already names as its entry point. /// /// Accepting them anywhere would mean two classes could set the same hint diff --git a/scripts/build-hint-computed-sites.txt b/scripts/build-hint-computed-sites.txt index bd905dbff14..30f778273eb 100644 --- a/scripts/build-hint-computed-sites.txt +++ b/scripts/build-hint-computed-sites.txt @@ -17,12 +17,20 @@ # the new hint had no catalog row. The line number is deliberately not part of # the key, so moving code does not churn this file. # -# Only sites whose expression contains a string literal are reported. A helper -# that forwards a variable -- getArg(key, ...) inside a wrapper -- gets its -# literal from its caller, which the ordinary literal pass already mines. +# Reported when the expression BUILDS a name (contains a string literal) or reads +# one out of a table (contains a subscript). A helper that merely forwards a +# variable -- getArg(key, ...) inside a wrapper -- gets its literal from its +# caller, which the ordinary literal pass already mines, and is not reported. +# +# The table case is why the subscript counts: IPhoneBuilder's +# WALLET_INJECTION_HINTS holds ten real hints in a String[][] and reaches getArg +# as hintAndMarker[0], so no literal anywhere sits at a getArg call and every +# literal search -- including this gate -- reported success while all ten had no +# catalog row. maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MapsProviderInjector.java|platform + ".maps.provider"|android.maps.provider,ios.maps.provider maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/NativeVerifyOption.java|platformPrefix + "." + HINT|ios.nativeVerify,linux.nativeVerify,windows.nativeVerify maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MacNativeBuilder.java|"macNative.provisioningProfile." + (isAppStore ? "appStore" : "developerID")|macNative.provisioningProfile.appStore,macNative.provisioningProfile.developerID maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java|"ios.entitlements.com.apple.developer.healthkit." + suffix|ios.entitlements.com.apple.developer.healthkit.access maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/WatchNativeBuilder.java|"ios.entitlements.com.apple.developer.healthkit." + canonicalKey|ios.entitlements.com.apple.developer.healthkit.access +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java|hintAndMarker[0]|ios.wallet.nonuiImportsInject,ios.wallet.statusInject,ios.wallet.passEntriesInject,ios.wallet.remotePassEntriesInject,ios.wallet.generateRequestInject,ios.wallet.generateResponseInject,ios.wallet.uiImportsInject,ios.wallet.uiViewDidLoadInject,ios.wallet.uiAuthRequestInject,ios.wallet.uiAuthResponseInject diff --git a/scripts/build_hint_miner.py b/scripts/build_hint_miner.py index 935d7e04c91..d40cf3abfdc 100644 --- a/scripts/build_hint_miner.py +++ b/scripts/build_hint_miner.py @@ -156,9 +156,13 @@ def concat_of_literals(expr): # Fully resolved -- an ordinary hint read that merely spelled its # name with a constant. hits[resolved].append(("", rel, line)) - elif '"' in expr: - # The name is being BUILT here, so no literal anywhere names it and - # the literal pass cannot see it. Worth reporting. + elif '"' in expr or '[' in expr: + # The name is BUILT here, or read out of a table -- either way no + # literal at a getArg call names it, so the literal pass cannot + # see it. IPhoneBuilder's WALLET_INJECTION_HINTS is the second + # kind: ten real hints living in a String[][] and reaching getArg + # as hintAndMarker[0], invisible to every literal search until + # subscripts were reported too. computed.append({"expr": " ".join(expr.split()), "file": rel, "line": line}) # Anything else is a forwarding helper -- getArg(key, ...) inside a From a1fd2035a905c29c381ebaac79b64a6233103ca4 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:27:41 +0300 Subject: [PATCH 033/115] Render the guide's hint table instead of committing it 3,345 generated lines leave git. The table is rendered from maven/build-hint-catalog every time the developer guide is built, so it cannot drift from the catalog and a hand edit has nothing to survive in -- which is what a generated file living in the repository always eventually invites, and what the drift gate and the pr.yml path re-inclusion existed to compensate for. Both of those go with it. Two renderers, so the step is a script rather than a workflow line: developer-guide-docs.yml runs it before the Asciidoctor lint, which is ahead of everything else that reads the guide -- the HTML and PDF build, Vale -- and scripts/website/build.sh runs it before its own asciidoctor call. The generator grows a --table-only mode for them. A documentation build has no business rewriting CodenameOne/src and Ports/JavaSE on its way past, and the mode's output is byte-identical to what the full run wrote, so this is not a second implementation of the table. Forgetting the step cannot be quiet: asciidoctor reports "include file not found" and the lint fails on it. Verified in both directions -- the render fails without the table and succeeds after generating it. scripts/gen-build-hint-annotations.sh still writes the file for a local asciidoctor run, and it is gitignored, so that copy is never reviewed and never committed. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/developer-guide-docs.yml | 8 + .github/workflows/pr.yml | 12 - .gitignore | 6 + CLAUDE.md | 5 +- .../_generated-build-hints.adoc | 3345 ----------------- .../build/shared/BuildHintCodeGenerator.java | 9 + scripts/gen-build-hint-annotations.sh | 7 +- scripts/gen-build-hint-table.sh | 22 + scripts/website/build.sh | 5 + 9 files changed, 59 insertions(+), 3360 deletions(-) delete mode 100644 docs/developer-guide/_generated-build-hints.adoc create mode 100755 scripts/gen-build-hint-table.sh diff --git a/.github/workflows/developer-guide-docs.yml b/.github/workflows/developer-guide-docs.yml index 6fa6502001c..6527072ab2e 100644 --- a/.github/workflows/developer-guide-docs.yml +++ b/.github/workflows/developer-guide-docs.yml @@ -216,6 +216,14 @@ jobs: run: | gem install --no-document asciidoctor asciidoctor-pdf rouge + # The build hint table is rendered from maven/build-hint-catalog rather than + # checked in, so every step below that reads the guide -- the lint, the HTML + # and PDF build, Vale -- needs it on disk first. Generating it here is also + # what makes it impossible for the table to disagree with the catalog: there + # is no committed copy for an edit to survive in. + - name: Render the build hint table + run: scripts/gen-build-hint-table.sh + - name: Run Asciidoctor lint run: | set -euo pipefail diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index aaf8630d045..f46993920c3 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -38,12 +38,6 @@ on: - 'scripts/build-hint-computed-sites.txt' - 'scripts/gen-build-hint-annotations.sh' - '!docs/**' - # After the docs exclusion, because the last matching pattern decides. This - # table is generated from the build hint catalog and checked for drift by - # gen-build-hint-annotations.sh, which only this workflow runs -- so while - # it was excluded, a hand edit to it was reviewed by the documentation - # workflows and by nothing that would have put it back. - - 'docs/developer-guide/_generated-build-hints.adoc' - '!**/*.md' - '!.github/workflows/developer-guide-docs.yml' - '!.github/workflows/website-docs.yml' @@ -86,12 +80,6 @@ on: - 'scripts/build-hint-computed-sites.txt' - 'scripts/gen-build-hint-annotations.sh' - '!docs/**' - # After the docs exclusion, because the last matching pattern decides. This - # table is generated from the build hint catalog and checked for drift by - # gen-build-hint-annotations.sh, which only this workflow runs -- so while - # it was excluded, a hand edit to it was reviewed by the documentation - # workflows and by nothing that would have put it back. - - 'docs/developer-guide/_generated-build-hints.adoc' - '!**/*.md' - '!.github/workflows/developer-guide-docs.yml' - '!.github/workflows/website-docs.yml' diff --git a/.gitignore b/.gitignore index 29fe25c6425..ae80343e7db 100644 --- a/.gitignore +++ b/.gitignore @@ -141,3 +141,9 @@ scripts/fidelity-app/common/src/main/resources/*ThemeDev.res # build time (common/pom.xml copy-native-themes); never commit the duplicate. scripts/fidelity-app/common/src/main/resources/iOSModernTheme.res scripts/fidelity-app/common/src/main/resources/AndroidMaterialTheme.res + +# Rendered from maven/build-hint-catalog by scripts/gen-build-hint-table.sh every +# time the developer guide is built. Not checked in: a generated file living in +# git invites a hand edit that the next regeneration silently discards, and needs +# a drift gate to notice. Generating it on the fly removes both. +docs/developer-guide/_generated-build-hints.adoc diff --git a/CLAUDE.md b/CLAUDE.md index fef8739422a..d266d99490f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -229,7 +229,10 @@ everything else is generated from it: - the `com.codename1.annotations.buildhints` annotations in `CodenameOne/src` - `BuildHintAnnotationBinding`, which the annotation processor reads back -- the developer guide's build hint table (`docs/developer-guide/_generated-build-hints.adoc`) +- the developer guide's build hint table, rendered by + `scripts/gen-build-hint-table.sh` every time the guide is built and **not** + checked in (both renderers -- `developer-guide-docs.yml` and + `scripts/website/build.sh` -- call it first) - the simulator's Build Hint editor schema (`BuildHintCatalogDefaults`) - the agent reference's annotation table (`skill/references/build-hints.md`) diff --git a/docs/developer-guide/_generated-build-hints.adoc b/docs/developer-guide/_generated-build-hints.adoc deleted file mode 100644 index be24352411d..00000000000 --- a/docs/developer-guide/_generated-build-hints.adoc +++ /dev/null @@ -1,3345 +0,0 @@ -// Generated from com.codename1.build.shared.BuildHints by -// BuildHintCodeGenerator. Do not edit by hand -- edit the catalog and re-run -// scripts/gen-build-hint-annotations.sh. -// -// The Annotation column names the compiler-checked form where one exists; -// those hints can be written on the application's main class instead of in -// codenameone_settings.properties. - -[cols="2,1,1,2,4"] -|=== -|Name |Type |Default |Annotation |Description - -|and.captureRecord -|string -|_(none)_ -|_(none)_ -|Override alias of `android.captureRecord`, read after it and winning when set. - -|and.facebook_permissions -|string -|_(none)_ -|_(none)_ -|Override alias of `android.facebook_permissions`, read after it and winning when set. `IPhoneBuilder` also falls back to it when `ios.facebook_permissions` is unset. - -|and.themeMode -|`auto`, `modern`, `hololight`, `legacy` -|_(none)_ -|`@Android(themeMode)` -|`auto`, `modern` / `material`, `hololight` (default for existing apps), `legacy`. `auto` and `modern` / `material` opt in to the CSS-generated Android Material 3 theme from `native-themes/android-material/theme.css`. `hololight` is Android Holo Light (what the framework shipped on API 14+ before this refactor). `legacy` loads the pre-Holo Android theme. The legacy alias `cn1.androidTheme` is still accepted, and `and.hololight=true` still maps to `hololight`. The default stays on `hololight` for existing apps until you flip in a future release. - -|android.NotificationChannel.description -|string -|`Remote notifications` -|_(none)_ -| - -|android.NotificationChannel.enableLights -|boolean -|`true` -|_(none)_ -| - -|android.NotificationChannel.enableVibration -|boolean -|`false` -|_(none)_ -| - -|android.NotificationChannel.id -|string -|`cn1-channel` -|_(none)_ -| - -|android.NotificationChannel.importance -|int -|`2` -|_(none)_ -| - -|android.NotificationChannel.lightColor -|string -|_(none)_ -|_(none)_ -| - -|android.NotificationChannel.name -|string -|`Notifications` -|_(none)_ -| - -|android.NotificationChannel.vibrationPattern -|string -|_(none)_ -|_(none)_ -| - -|android.accessibilityGuard -|boolean -|`false` -|_(none)_ -| - -|android.accessibilityGuard.allow -|string -|_(none)_ -|_(none)_ -| - -|android.accessibilityGuard.mode -|string -|`exit` -|_(none)_ -| - -|android.activity.launchMode -|string -|`singleTop` -|`@Android(activityLaunchMode)` -|Allows explicitly setting the `android:launchMode` attribute of the main activity in android. Default is "singleTop," but for some applications you may need to change this behaviour. In particular, apps that are meant to open a file type will need to set this to "singleTask." See https://developer.android.com/guide/topics/manifest/activity-element.html[Android docs for the activity element] for more information about the `android:launchMode` attribute. - -|android.activityClassBody -|string -|_(none)_ -|_(none)_ -| - -|android.activityClassImports -|string -|_(none)_ -|_(none)_ -| - -|android.adaptiveIconBackground -|string -|`#ffffff` -|_(none)_ -|Background color to use for adaptive icons when `android.enableAdaptiveIcons=true` and no background image is supplied. Defaults to `#ffffff` and is written as `@color/ic_launcher_background`. - -|android.adaptiveIconBackgroundImage -|string -|_(none)_ -|_(none)_ -|Optional path (relative to the root of the native Android project) to an image file to use as the adaptive icon background when `android.enableAdaptiveIcons=true`. If this property is set, it overrides `android.adaptiveIconBackground`. - -|android.allowBackup -|boolean -|`true` -|_(none)_ -| - -|android.androidAuto.messaging -|boolean -|`false` -|_(none)_ -| - -|android.androidAuto.minCarApiLevel -|int -|`1` -|_(none)_ -| - -|android.androidAuto.navigation -|boolean -|`false` -|_(none)_ -| - -|android.androidAuto.poi -|boolean -|`false` -|_(none)_ -| - -|android.anyDensity -|boolean -|`true` -|_(none)_ -| - -|android.apacheLegacy -|boolean -|`false` -|_(none)_ -| - -|android.appBundle -|boolean -|_(none)_ -|`@Android(appBundle)` -|Produces an Android App Bundle (.aab) rather than an APK. Required for new Play Store submissions. - -|android.appReview.version -|version -|`2.0.1` -|_(none)_ -| - -|android.ar.required -|boolean -|`false` -|_(none)_ -| - -|android.arrcompile -|string -|_(none)_ -|_(none)_ -| - -|android.arrimplementation -|string -|_(none)_ -|_(none)_ -| - -|android.asyncPaint -|boolean -|`true` -|_(none)_ -|Boolean true/false defaults to true. Toggles the Android pipeline between the legacy pipeline (false) and new pipeline (true) - -|android.background_push_handling -|boolean -|`false` -|_(none)_ -| - -|android.billingclient.version -|version -|`4.0.0` -|_(none)_ -| - -|android.blockExternalStoragePermission -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Disables the external storage (SD card) permission - -|android.blockLabel -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Leaves `android:label` off the generated `` tag so a label set through `android.xapplication_attr` or a merged manifest is the one that survives. Honoured by the wear module's tag as well as the phone's. - -|android.blockReadMediaPermissions -|boolean -|_(none)_ -|_(none)_ -|Boolean true/false, defaults to the value of `android.blockExternalStoragePermission`. Suppresses the `READ_MEDIA_VIDEO` and `READ_MEDIA_AUDIO` permissions that playing a URI adds on API 33 and above - -|android.bluetooth.neverForLocation -|boolean -|`true` -|_(none)_ -| - -|android.bluetooth.required -|boolean -|`false` -|_(none)_ -| - -|android.buildToolsVersion -|version -|_(none)_ -|`@Android(buildToolsVersion)` -|Android build-tools version. It also selects the compile SDK, so there is no separate compile-SDK hint. - -|android.captureRecord -|string -|`enabled` -|`@Android(captureRecord)` -|Indicates whether the `RECORD_AUDIO` permission should be requested. Can be `enabled` or any other value to disable this option - -|android.carAppVersion -|version -|`1.4.0` -|_(none)_ -| - -|android.credentialsPlayServicesVersion -|string -|_(none)_ -|_(none)_ -| - -|android.credentialsVersion -|version -|`1.3.0` -|_(none)_ -| - -|android.cusom_layout -|string -|_(none)_ -|_(none)_ -| - -|android.cusom_layout* -|string -|_(none)_ -|_(properties file only)_ -|Numbered custom layout resources: android.cusom_layout1, android.cusom_layout2 and upward. The misspelling is load-bearing: it's the key the builder actually reads, so correcting it drops the layout with no warning. - -|android.cusom_layout1 -|string -|_(none)_ -|_(none)_ -|Applies to any number of layouts as long as they're in sequence (for example, android.cusom_layout2, android.cusom_layout3 etc.). Will write the content of the argument as a layout XML file and give it the name `cusom_layout1.xml` onwards. This can be used by native code to work with XML files - -|android.customActivity -|string -|`CodenameOneActivity` -|_(none)_ -| - -|android.customTabsVersion -|version -|`1.8.0` -|_(none)_ -| - -|android.debug -|boolean -|`false` -|`@Android(debug)` -|true/false defaults to true - indicates whether to include the debug version in the build. Defaults conditionally rather than to a fixed value: when android.release is on it defaults to false, and when release is off it defaults to true, so a build that selects neither still produces something installable (AndroidGradleBuilder.java:447-451). - -|android.decouplePlayServiceVersions -|string -|_(none)_ -|_(none)_ -| - -|android.delayPushCompletion -|boolean -|`false` -|_(none)_ -| - -|android.disableR8 -|boolean -|`false` -|`@Android(disableR8)` -|Turns off R8, falling back to the older shrinker. Note that hardening requires R8, so this conflicts with harden.level. - -|android.disableR8FullMode -|boolean -|`true` -|_(none)_ -| - -|android.disableScreenshots -|boolean -|`false` -|_(none)_ -| - -|android.enableAdaptiveIcons -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Enables Android adaptive icon generation in Android Gradle builds. When enabled, Codename One generates `mipmap` launcher resources (`ic_launcher`, `ic_launcher_foreground`, and adaptive XML in `mipmap-anydpi-v26`) and uses them in the application manifest (`android:icon` and `android:roundIcon`). - -|android.enableProguard -|boolean -|`true` -|`@Android(enableProguard)` -|Boolean true/false defaults to true. Allows disabling the proguard obfuscation even on release builds, notice that this isn't recommended - -|android.excludeBolts -|boolean -|`false` -|_(none)_ -| - -|android.extendAppCompatActivity -|boolean -|`false` -|_(none)_ -| - -|android.facebookSdkVersion -|version -|`16.2.0` -|_(none)_ -| - -|android.facebook_permissions -|string -// vale-skip: Microsoft.Quotes: this is a literal default value, not prose -- the quotes belong to the value. -|`"public_profile","email","user_friends"` -|_(none)_ -|Permissions for Facebook used in the Android build target, applicable only if Facebook native integration is used. - -|android.file_paths -|string -// vale-skip: Microsoft.Quotes: this is a literal default value, not prose -- the quotes belong to the value. -|` ` -|_(none)_ -| - -|android.firebaseAnalytics -|boolean -|`false` -|_(none)_ -| - -|android.firebaseAnalyticsVersion -|version -|`21.5.0` -|_(none)_ -| - -|android.firebaseCoreVersion -|string -|_(none)_ -|_(none)_ -| - -|android.firebaseMessagingVersion -|string -|_(none)_ -|_(none)_ -| - -|android.foldableSupport -|boolean -|`false` -|_(none)_ -| - -|android.forceJava8Builder -|boolean -|`false` -|_(none)_ -| - -|android.foregroundServiceType -|string -|`dataSync` -|_(none)_ -| - -|android.fridaDebugLogging -|boolean -|_(none)_ -|_(none)_ -|Boolean true/false defaults to false. If true, it will add verbose debug logs during frida detection to show which check if fails on. - -|android.fridaDetection -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Indicates whether the app should check for the presence of the https://www.frida.re/[Frida] dynamic instrumentation toolkit on the device. If Frida is detected, the app will exit. This uses the [frida-blocker](https://github.com/shannah/frida-blocker) library to perform the frida detection. - -|android.fridaVersion -|string -|_(none)_ -|_(none)_ -|x.y.z The version of [frida-blocker](https://github.com/shannah/frida-blocker) to use to perform frida detection. This is only relevant if `android.fridaDetection=true`. If omitted, it will use the latest tested version in the build server. - -|android.fullScreenIntent -|boolean -|`false` -|_(none)_ -| - -|android.googleAdUnitId -|string -|_(none)_ -|_(none)_ -|Allows integrating admob/google play ads, this is effectively identical to google.adUnitId but only applies to Android - -|android.googleAdUnitTestDevice -|string -|`C6783E2486F0931D9D09FABC65094FDF` -|_(none)_ -|Device key used to mark a specific Android device as a test device for Google Play ads defaults to C6783E2486F0931D9D09FABC65094FDF - -|android.gpsPermission -|boolean -|`false` -|_(none)_ -|Indicates whether the GPS permission should be requested, it's autodetected by default if you use the location API. But, some code might want to explicitly define it - -|android.gradle.androidx -|list (newline delimited) -|_(none)_ -|_(none)_ -| - -|android.gradleDep -|list (`;` delimited) -|_(none)_ -|`@Android(gradleDep)` -|Gradle dependency statements to add to the app module, such as implementation 'com.example:lib:1.0'. - -|android.gradlePlugin -|list (newline delimited) -|_(none)_ -|_(none)_ -| - -|android.hce -|boolean -|`false` -|_(none)_ -| - -|android.hceAids -|string -|`F0010203040506` -|_(none)_ -| - -|android.hceCategory -|string -|`other` -|_(none)_ -| - -|android.hceDescription -|string -|_(none)_ -|_(none)_ -| - -|android.hceRequireUnlock -|boolean -|`false` -|_(none)_ -| - -|android.headphoneCallback -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. When set to true it assumes the main class has two methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately as needed - -|android.health.background -|boolean -|`false` -|_(none)_ -| - -|android.health.connectVersion -|string -|`1.1.0-alpha07` -|_(none)_ -| - -|android.health.history -|boolean -|`false` -|_(none)_ -| - -|android.health.privacyPolicyUrl -|string -|_(none)_ -|_(none)_ -| - -|android.health.read -|string -|_(none)_ -|_(none)_ -| - -|android.health.write -|string -|_(none)_ -|_(none)_ -| - -|android.hideOverlayWindows -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Declares the `android.permission.HIDE_OVERLAY_WINDOWS` permission needed by `DeviceIntegrity.setHideOverlayWindows()` on Android 12+, for apps that call the runtime API without enabling `android.tapjackingGuard`. A normal install-time permission, so the user sees no prompt. - -|android.hideStatusBar -|boolean -|`false` -|`@Android(hideStatusBar)` -|Hides the Android status bar. - -|android.hms.pushVersion -|string -|`6.3.0.302` -|_(none)_ -| - -|android.home.playServicesVersion -|string -|`16.0.0-beta1` -|_(none)_ -| - -|android.includeGPlayServices -|boolean -|`true` -|_(none)_ -|*Deprecated, please android.playService.+++*+++!* Indicates whether Google Play Services should be included into the build, defaults to false but that might change based on the functionality of the application and other build hints. Adding Google Play Services support allows you to use a more refined location implementation and invoke some Google specific functionality from native code. - -|android.includeMavenCentral -|boolean -|`false` -|_(none)_ -| - -|android.installLocation -|`auto`, `internalOnly`, `preferExternal` -|`auto` -|`@Android(installLocation)` -|Maps to android:installLocation manifest entry defaults to auto. Can also be set to internalOnly or preferExternal. - -|android.java8 -|string -|_(none)_ -|_(none)_ -| - -|android.keyboardOpen -|boolean -|`true` -|_(none)_ -|Boolean true/false defaults to true. Toggles the new async keyboard mode that leaves the keyboard open while you move between text components - -|android.largeScreens -|boolean -|`true` -|_(none)_ -| - -|android.licenseKey -|string -|_(none)_ -|`@Android(licenseKey)` -|The license key for the Android app, this is required if you use in-app purchase on Android - -|android.locales -|string -|_(none)_ -|_(none)_ -| - -|android.manifest.queries -|string -|_(none)_ -|_(none)_ -|Embeds XML content into the section of the Android manifest file. This is https://developer.android.com/training/package-visibility[required in Android 11 for package visibility]. See https://developer.android.com/guide/topics/manifest/queries-element[queries element Android documentation]. - -|android.maps.provider -|string -|_(none)_ -|_(none)_ -|Android's own native map provider, overriding `maps.provider`. - -|android.messagingService -|string -|_(none)_ -|_(none)_ -| - -|android.migrateToAndroidX -|boolean -|`true` -|_(none)_ -| - -|android.min_sdk_version -|int -|`19` -|`@Android(minSdkVersion)` -|The least SDK required to run this app, the default value changes based on functionality but can be as low as 7. This corresponds to the XML attribute `android:minSdkVersion`. - -|android.mockLocation -|boolean -|`true` -|_(none)_ -|Boolean true/false defaults to true. Toggles the mock location permission which is on by default, this allows easier debugging of Android device location based services - -|android.mopubId -|string -|_(none)_ -|_(none)_ -| - -|android.multidex -|boolean -|`true` -|`@Android(multidex)` -|Boolean true/false defaults to false. Multidex allows Android binaries to reference more than 65536 methods. This slows builds a bit so you have it off by default but if you get a build error mentioning this limit you should turn this on. - -|android.newFirebaseMessaging -|boolean -|`true` -|`@Android(newFirebaseMessaging)` -|Uses the current Firebase Cloud Messaging integration. Requires AndroidX and Gradle 8.13 or newer. - -|android.nonconsumable -|string -|_(none)_ -|_(none)_ -|Comma delimited string of items that are non-consumable in the in-app purchase API - -|android.normalScreens -|boolean -|`true` -|_(none)_ -| - -|android.onCreate -|string -|_(none)_ -|_(none)_ -| - -|android.onDeviceDebug -|boolean -|`false` -|`@OnDeviceDebug(android)` -|Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable="true"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable="true"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it — release builds are unaffected. See the On-Device Debugging (Android) chapter for the full flow. - -|android.permission.* -|string -|_(none)_ -|_(properties file only)_ -|true/false. Whether to include a particular permission. Preferred over android.xpermissions because it avoids conflicts with libraries. See Android's Manifest.permission documentation for the full list. The optional .maxSdkVersion suffix becomes the maxSdkVersion attribute of the generated tag, and .required marks the permission required. - -|android.playIntegrity -|boolean -|`false` -|_(none)_ -| - -|android.playIntegrity.verifyUrl -|string -|_(none)_ -|_(none)_ -| - -|android.playIntegrityVersion -|version -|`1.4.0` -|_(none)_ -| - -|android.playService.* -|string -|_(none)_ -|_(properties file only)_ -|Opts a single Google Play service in or out. The sibling .minPlayServicesVersion pins its version. - -|android.playService.ads -|boolean -|`false` -|_(none)_ -| - -|android.playService.analytics -|string -|_(none)_ -|_(none)_ -| - -|android.playService.appInvite -|boolean -|`false` -|_(none)_ -| - -|android.playService.auth -|string -|_(none)_ -|_(none)_ -| - -|android.playService.base -|string -|_(none)_ -|_(none)_ -| - -|android.playService.cast -|boolean -|`false` -|_(none)_ -| - -|android.playService.drive -|boolean -|`false` -|_(none)_ -| - -|android.playService.fitness -|boolean -|`false` -|_(none)_ -| - -|android.playService.games -|boolean -|`false` -|_(none)_ -| - -|android.playService.gcm -|string -|_(none)_ -|_(none)_ -| - -|android.playService.identity -|boolean -|`false` -|_(none)_ -| - -|android.playService.indexing -|boolean -|`false` -|_(none)_ -| - -|android.playService.location -|string -|_(none)_ -|_(none)_ -| - -|android.playService.maps -|string -|_(none)_ -|_(none)_ -| - -|android.playService.nearby -|boolean -|`false` -|_(none)_ -| - -|android.playService.panorama -|boolean -|`false` -|_(none)_ -| - -|android.playService.plus -|boolean -|`false` -|_(none)_ -| - -|android.playService.safetynet -|boolean -|`false` -|_(none)_ -| - -|android.playService.vision -|boolean -|`false` -|_(none)_ -| - -|android.playService.wallet -|boolean -|`false` -|_(none)_ -| - -|android.playService.wearable -|boolean -|`false` -|_(none)_ -| - -|android.playServicesVersion -|string -|_(none)_ -|_(none)_ -|The version number of play services to build against. Experimental. **Use with caution** as building against versions other than the server default may introduce incompatibilities with some Codename One APIs. - -|android.proguardKeep -|list (newline delimited) -|_(none)_ -|`@Android(proguardKeep)` -|Arguments for the keep option in proguard allowing you to keep a pattern of files for example, `-keep class com.mypackage.ProblemClass { *; }` - -|android.proguardKeepOverride -|string -|`Exceptions, InnerClasses, Signature, Deprecated, SourceFile, LineNumberTable, *Annotation*, EnclosingMethod` -|_(none)_ -| - -|android.pushSound -|string -|_(none)_ -|_(none)_ -| - -|android.pushVibratePattern -|string -|_(none)_ -|_(none)_ -|Comma delimited long values to describe the push pattern of vibrate used for the `setVibrate` native method - -|android.release -|boolean -|`true` -|`@Android(release)` -|true/false defaults to true - indicates whether to include the release version in the build - -|android.removeBasePermissions -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Disables the built-in permissions specifically `INTERNET` permission (that is, no networking...) - -|android.repositories -|list (newline delimited) -|_(none)_ -|`@Android(repositories)` -|Extra Gradle repositories to resolve dependencies from. - -|android.requestReadMediaPermissions -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Declares `READ_MEDIA_IMAGES`, `READ_MEDIA_VIDEO` and `READ_MEDIA_AUDIO` on API 33 and above even when the build detected no media playback. `READ_MEDIA_IMAGES` is only ever added by this hint - -|android.rootCheck -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Indicates whether the app should check for root access on the device. If root access is detected, the app will exit. - -|android.rootbeerVersion -|version -|`0.1.0` -|_(none)_ -| - -|android.shareFilter -|string -|_(none)_ -|_(none)_ -| - -|android.sharedUserId -|string -|_(none)_ -|_(none)_ -|Allows adding a manifest attribute for the sharedUserId option - -|android.sharedUserLabel -|string -|_(none)_ -|_(none)_ -|Allows adding a manifest attribute for the sharedUserLabel option - -|android.shrinkResources -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Used only in conjunction with android.enableProguard. Strips out unused resources to reduce apk size. Since 7.0 - -|android.signingV1 -|boolean -|_(none)_ -|_(none)_ -|true/false Default true. See https://source.android.com/docs/security/features/apksigning - -|android.signingV2 -|boolean -|_(none)_ -|_(none)_ -|true/false Default true. See https://source.android.com/docs/security/features/apksigning - -|android.signingV3 -|boolean -|_(none)_ -|_(none)_ -|true/false Default true. See https://source.android.com/docs/security/features/apksigning - -|android.signingV4 -|boolean -|_(none)_ -|_(none)_ -|true/false Default true. See https://source.android.com/docs/security/features/apksigning - -|android.smallScreens -|boolean -|`true` -|_(none)_ -|Boolean true/false defaults to true. Corresponds to the `android:smallScreens` XML attribute and allows disabling the support for small phones - -|android.stack_size -|string -|_(none)_ -|_(none)_ -|Size in bytes for the Android stack thread - -|android.statusbar_hidden -|boolean -|`false` -|_(none)_ -|true/false defaults to false. When set to true hides the status bar on Android devices. - -|android.store_ids -|string -|_(none)_ -|_(none)_ -| - -|android.streamMode -|string -|_(none)_ -|_(none)_ -|The mode in which the volume key should behave, defaults to OS default. Allows setting it to `music` for music playback apps - -|android.stringsXml -|string -|_(none)_ -|_(none)_ -|Allows injecting more entries into the strings.xml file using a value that includes something like this `value1value2` - -|android.style -|string -|_(none)_ -|_(none)_ -|Allows injecting more data into the `styles.xml` file right before the closing resources tag - -|android.supportScreens -|string -|_(none)_ -|_(none)_ -| - -|android.supportV4 -|boolean -|_(none)_ -|_(none)_ -|Boolean true/false defaults to false but that can change based on usage (for example, push implicitly activates this). Indicates whether the android support v4 library should be included in the build - -|android.supportv4Dep -|list (newline delimited) -|_(none)_ -|_(none)_ -| - -|android.surfaces.complicationUpdateSeconds -|int -|`0` -|_(none)_ -|`UPDATE_PERIOD_SECONDS` on the generated complication service. Zero, the default, means the system never polls on a timer and the complication updates only when the app pushes new data. - -|android.surfaces.exactAlarms -|boolean -|`false` -|_(none)_ -| - -|android.tapjackingGuard -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Switches on tapjacking / screen-overlay protection at launch, so touches that arrive while another app's window covers this one are detected and dropped. See the security chapter. - -|android.tapjackingGuard.hideOverlays -|boolean -|`true` -|_(none)_ -|Boolean true/false defaults to true. Also asks Android 12+ to hide overlay windows drawn over the app, which is the only mitigation that covers native peer components, and declares the `HIDE_OVERLAY_WINDOWS` permission it requires. Only relevant if `android.tapjackingGuard=true`. - -|android.tapjackingGuard.mode -|string -|`block` -|_(none)_ -|`block` (default), `strict`, `report` or `off`. `block` drops gestures that start on a fully obscured window, `report` only observes, `strict` also drops touches where only part of the window is covered (which benign system UI can trigger). Only relevant if `android.tapjackingGuard=true`. - -|android.targetSDKVersion -|int -|_(none)_ -|`@Android(targetSDKVersion)` -|Indicates the Android SDK used to compile the Android build defaults to 21. Notice that not all targets will work since the source might have some limitations and not all SDK targets are installed on the build servers. - -|android.textureView -|boolean -|`false` -|_(none)_ -| - -|android.theme -|string -|`Light` -|_(none)_ -|Light or Dark defaults to Light. On Android 4+ the default Holo theme is used to render the native widgets sometimes and this indicates whether holo light or holo dark is used. This doesn't affect the Codename One theme but that might change in the future. - -|android.topDependency -|list (newline delimited) -|_(none)_ -|`@Android(topDependency)` -|Statements added to the top-level Gradle build file rather than the app module. - -|android.tv -|boolean -|`false` -|_(none)_ -|true/false (defaults to false). Marks the build as an Android TV / Google TV app. Adds the `LEANBACK_LAUNCHER` intent category to the launcher activity (so the app appears on the TV home screen), declares the `android.software.leanback` feature, makes `android.hardware.touchscreen` optional (so it installs on touchless TVs), and generates a 320×180 launcher banner (`@drawable/tv_banner`) from the app icon. The same APK still installs and runs on phones and tablets, and `CN.isTV()` returns true at runtime on a TV. - -|android.useAndroidX -|boolean -|_(none)_ -|`@Android(useAndroidX)` -|Use Android X instead of support libraries. This will also run a find/replace on all source files to replace support libraries and artifacts with AndroidX equivalents. - -|android.useGradle8 -|string -|_(none)_ -|_(none)_ -| - -|android.uses_feature.* -|string -|_(none)_ -|_(properties file only)_ -|Adds a element named by the suffix. - -|android.uses_permission.* -|string -|_(none)_ -|_(properties file only)_ -|Adds a element named by the suffix. - -|android.versionCode -|string -|_(none)_ -|_(none)_ -|Allows overriding the auto generated version number with a custom internal version number specifically used for the XML attribute `android:versionCode` - -|android.watchModule -|boolean -|`true` -|_(none)_ -|Boolean true/false defaults to true. Set to false to build the phone app alone in a companion build: the wearable link stays, no watch module is generated, and the phone output matches what it was before the watch app existed. - -|android.watchVersionCode -|int -|_(none)_ -|_(none)_ -|The wear module's version code, stated outright. Play requires it to be higher than the phone's, so a value other than a whole number above `android.versionCode` fails the build rather than being replaced without a word. Leave it unset to derive the value from `android.watchVersionCodeOffset`. - -|android.watchVersionCodeOffset -|int -|`100000000` -|_(none)_ -|How far above the phone's version code the wear module's sits when `android.watchVersionCode` is unset. The default leaves room for the phone app to keep incrementing without ever catching up. - -|android.wear -|boolean -|`false` -|_(none)_ -| - -|android.wear.complicationsVersion -|string -|`1.2.1` -|_(none)_ -|Version of `androidx.wear.watchface:watchface-complications-data-source` added to the wear module. Kept out of `android.gradleDependencies` because that hint feeds the phone module too, and these libraries declare minSdk 26. - -|android.wear.guavaVersion -|string -|`31.1-android` -|_(none)_ -|Version of `com.google.guava:guava` added to the wear module alongside the tiles and complications libraries, which need it at runtime. - -|android.wear.protoLayoutVersion -|string -|`1.2.1` -|_(none)_ -|Version of the `androidx.wear.protolayout` libraries the generated tile service builds its layout with. - -|android.wear.standalone -|string -|_(none)_ -|_(none)_ -| - -|android.wear.tilesVersion -|string -|`1.4.1` -|_(none)_ -|Version of `androidx.wear.tiles` added to the wear module when the app declares a tile. - -|android.web_loading_hidden -|boolean -|`false` -|_(none)_ -|true/false defaults to false - set to true to hide the progress indicator that appears when loading a web page on Android. - -|android.windowVersion -|version -|`1.3.0` -|_(none)_ -| - -|android.xactivity -|xml -|_(none)_ -|_(none)_ -|Allows injecting more attributes into the `activity` tag in the Android XML - -|android.xapplication -|xml -|_(none)_ -|`@Android(xapplication)` -|defaults to an empty string. Allows developers of native Android code to add text within the application block to define things such as widgets, services etc. - -|android.xapplication_attr -|xml -|_(none)_ -|_(none)_ -|Allows injecting more attributes into the `application`` tag in the Android XML - -|android.xgradle -|list (newline delimited) -|_(none)_ -|`@Android(xgradle)` -|Arbitrary text spliced into the generated app-module Gradle file. - -|android.xgradle_default_config -|list (newline delimited) -|_(none)_ -|_(none)_ -| - -|android.xintent_filter -|xml -|_(none)_ -|_(none)_ -|Allows adding an intent filter to the main android activity - -|android.xlargeScreens -|boolean -|`true` -|_(none)_ -| - -|android.xlayout_attr -|string -|_(none)_ -|_(none)_ -| - -|android.xmanifest -|xml -|_(none)_ -|_(none)_ -| - -|android.xpermissions -|xml -|_(none)_ -|`@Android(xpermissions)` -|more permissions for the Android manifest - -|desktop.adaptToRetina -|boolean -|`true` -|`@Desktop(adaptToRetina)` -|Boolean true/false defaults to true. When set to true some values will ve implicitly doubled to deal with retina displays and icons etc. Will use higher DPI's - -|desktop.fontSizes -|string -|_(none)_ -|_(none)_ -|Indicates the sizes in pixels for the system fonts as a comma delimited string containing 3 numbers for small,medium,large fonts. - -|desktop.fullscreen -|boolean -|`false` -|`@Desktop(fullscreen)` -|Starts the desktop build in full-screen mode. - -|desktop.height -|int -|`600` -|`@Desktop(height)` -|Height in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 600. - -|desktop.interactiveScrollbars -|boolean -|`true` -|`@Desktop(interactiveScrollbars)` -|Enables grab-able, click-to-page desktop scrollbars. - -|desktop.resizable -|boolean -|`true` -|`@Desktop(resizable)` -|Boolean true/false defaults to true. Indicates whether the UI in the desktop build is resizable - -|desktop.theme -|string -|_(none)_ -|_(none)_ -|Name of the theme res file (without the ".res" extension) to use as the "native" theme. By default this is native indicating iOS theme on Mac and Windows Metro on Windows. If its something else then the app will try to load the file /themeName.res (placed in native/Java SE directory). - -|desktop.themeMac -|string -|_(none)_ -|_(none)_ -|Same as `desktop.theme` but specific to macOS - -|desktop.themeWin -|string -|_(none)_ -|_(none)_ -|Same as `desktop.theme` but specific to Windows - -|desktop.title -|string -|_(none)_ -|_(none)_ -| - -|desktop.titleBar -|`native`, `custom`, `toolbar` -|`native` -|`@Desktop(titleBar)` -|How the desktop window is framed: native for the OS title bar and menu bar, custom for an undecorated window with a Codename One drawn title bar, or toolbar for the legacy in-app Toolbar. An unrecognized value falls back to native with a warning. - -|desktop.width -|int -|`800` -|`@Desktop(width)` -|Width in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 800. - -|desktop.win.cef -|boolean -|_(none)_ -|_(none)_ -|Whether to use CEF for media and BrowserComponent instead of JavaFX in windows desktop builds. true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a future version. - -|desktop.windowsOutput -|string -|_(none)_ -|_(none)_ -|Can be exe or msi depending on desired results - -|KeepScreenOn -|boolean -|`false` -|_(none)_ -| - -|androidx.appcompat.version -|string -|_(none)_ -|_(none)_ -| - -|block_server_registration -|boolean -|_(none)_ -|_(none)_ -|true/false flag defaults to false. By default Codename One applications register with the Codename One server. Setting this to true blocks them from sending information to the Codename One cloud, which is kept for statistical purposes and may be used to provide more installation stats in the future. - -|build.cn1Version -|string -|_(none)_ -|_(none)_ -|Pro/Enterprise only. Pins the cloud build to a specific released Codename One version using the Maven release scheme (for example `7.0.182`), or to `master` to build against the current development head. The build server fetches that version's framework artifacts. Pro accounts can target versions published within the last two months; Enterprise within the last six months. Requesting an older version, a version that was never published, or using this hint without a Pro/Enterprise subscription fails the build with an explanatory error. See Versioned builds. - -|build.incSources -|string -|_(none)_ -|_(none)_ -| - -|build.testReporter -|string -|_(none)_ -|_(none)_ -| - -|build.unitTest -|string -|_(none)_ -|_(none)_ -| - -|cn1.androidTheme -|string -|_(none)_ -|_(none)_ -|Deprecated alias for and.themeMode (AndroidGradleBuilder.java:4097). Both names configure one setting, so declaring this alongside @Android(themeMode) is a conflict. - -|cn1.buildKey -|string -|_(none)_ -|_(none)_ -| - -|cn1.entitled -|boolean -|`true` -|_(none)_ -| - -|cn1.harden.forceOff -|string -|_(none)_ -|_(none)_ -| - -|cn1.hardenLevel -|string -|`off` -|_(none)_ -| - -|cn1.hardened -|boolean -|`false` -|_(none)_ -| - -|cn1.hardening.libraryJars -|string -|_(none)_ -|_(none)_ -| - -|cn1.mappingId -|string -|_(none)_ -|_(none)_ -| - -|cn1.nativeTheme -|string -|_(none)_ -|_(none)_ -|Deprecated alias for nativeTheme (AndroidGradleBuilder.java:4099, IPhoneBuilder.java:947). Both names configure one setting, so declaring this alongside @Build(nativeTheme) is a conflict. - -|codename1.mac.appid -|string -|_(none)_ -|_(none)_ -|Mac Native cloud builds only. The Mac bundle identifier registered in App Store Connect / Apple Developer. Distinct from `codename1.ios.appid` because Apple treats the iOS and Mac App Store records as separate products. Required for cloud Mac builds. - -|codename1.mac.certificate -|string -|_(none)_ -|_(none)_ -|Mac Native cloud builds only. Path to the `.p12` file containing the Mac signing certificate(s) — _Mac App Distribution_ (3rd Party Mac Developer Application) for App Store builds, _Developer ID Application_ for Developer ID builds, or both bundled into the same P12 when `macNative.distribution=both`. Not interchangeable with the iOS distribution certificate. Required for cloud Mac builds. - -|codename1.mac.certificatePassword -|secret -|_(none)_ -|_(none)_ -|Mac Native cloud builds only. Password to unlock the P12 referenced by `codename1.mac.certificate`. Required for cloud Mac builds. - -|codename1.mac.provision -|string -|_(none)_ -|_(none)_ -|Mac Native cloud builds only. Path to the Mac provisioning profile (`.provisionprofile`). Apple issues distinct provisioning profiles for Mac App Store and Developer ID distribution — pass the one that matches the chosen channel. - -|db.legacy -|string -|_(none)_ -|_(none)_ -| - -|delayPushCompletion -|boolean -|`false` -|_(none)_ -| - -|facebook.appId -|string -|`706695982682332` -|`@Build(facebookAppId)` -|The application ID for an app that requires native Facebook login integration, this defaults to null which means native Facebook support shouldn't be in the app - -|facebook.clientToken -|secret -|_(none)_ -|_(none)_ -|The client token for an app that requires native Facebook login integration, this is required if the facebook.appId is set. - -|gcm.sender_id -|string -|_(none)_ -|`@Build(gcmSenderId)` -|The Android/chrome push identifier, see the push section for more details - -|google.adUnitId -|string -|_(none)_ -|_(none)_ -|Allows integrating Admob/Google Play ads into the application see link:https://www.codenameone.com/blog/adding-google-play-ads.html[this] - -|gradleDependencies -|list (newline delimited) -|_(none)_ -|_(none)_ -| - -|harden.* -|string -|_(none)_ -|_(properties file only)_ -|The whole hardening namespace is swept into the hardening engine's configuration, so a hint added there reaches it without a dedicated reader. - -|harden.*.enabled -|string -|_(none)_ -|_(properties file only)_ -|Enables or disables hardening for one platform slice. - -|harden.allowUnhardenedLocalBuild -|boolean -|`false` -|`@Hardening(allowUnhardenedLocalBuild)` -|Permits a local or source build to run with hardening requested but not applied. Without it such a build is refused, so a hardened app is never shipped from a target that can't actually harden it. - -|harden.controlFlow -|`off`, `on` -|_(none)_ -|`@Hardening(controlFlow)` -|Overrides control-flow obfuscation independently of harden.level. - -|harden.ios.enabled -|boolean -|`true` -|_(none)_ -| - -|harden.keep -|text_block -|_(none)_ -|`@Hardening(keep)` -|Keep rules in ProGuard syntax, one per line, for classes that are resolved by name at runtime and so can't be found by the automatic analysis. Same syntax as android.proguardKeep, so existing rules port directly. Rules are separated by newlines only, because a semicolon is legal inside a rule body such as { *; }. - -|harden.level -|`off`, `standard`, `aggressive`, `paranoid` -|`off` -|`@Hardening(level)` -|Master switch for app hardening: off, standard, aggressive or paranoid. An unrecognized value fails the build rather than being treated as off. - -|harden.mac.enabled -|boolean -|`true` -|_(none)_ -| - -|harden.rename -|boolean -|_(none)_ -|`@Hardening(rename)` -|Overrides symbol renaming independently of harden.level. - -|harden.strings -|`off`, `constants`, `all` -|_(none)_ -|`@Hardening(strings)` -|Overrides string obfuscation independently of harden.level: off, constants or all. - -|harden.tv.enabled -|boolean -|`true` -|_(none)_ -| - -|harden.watch.enabled -|boolean -|`true` -|_(none)_ -| - -|java.version -|int -|`8` -|_(none)_ -|Valid values include 5 or 8. Indicates the JVM version that should be used for server compilation, this is defined by default for newly created apps based on the Java 8 mode selection - -|mac.desktop-vm -|string -|_(none)_ -|_(none)_ -|The JVM the should be bundled with Mac desktop build. Mac desktop builds only. Supported values: zuluFx8, zulu11, zuluFx11 - -|maps.provider -|string -|_(none)_ -|_(none)_ -|Selects the native map provider. `android.maps.provider` and `ios.maps.provider` override it for one platform. - -|nativeTheme -|`modern`, `legacy`, `custom` -|_(none)_ -|`@Build(nativeTheme)` -|`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both `ios.themeMode` and `and.themeMode` together when those aren't set explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the framework native theme entirely. The legacy alias `cn1.nativeTheme` is still accepted. - -|nativeVerify -|string -|_(none)_ -|_(none)_ -|`strict` or `warn` turns on ParparVM's native signature check for this build; anything else leaves it off, which is the default. ParparVM encodes the whole Java signature in the C function name, so a native spelled even slightly differently never reaches the linker as an error: the correctly named symbol is simply absent, the dead-code pass reads that as unused, and the feature ships inert. `ios.nativeVerify`, `linux.nativeVerify` and `windows.nativeVerify` override it for one platform. - -|noExtraResources -|boolean -|`false` -|`@Build(noExtraResources)` -|true/false (defaults to false). Blocks codename one from injecting its own resources when set to true, the only effect this has is in slightly reducing archive size. This might have adverse effects on some features of Codename One so it isn't recommended. - -|requireKotlinStdlib -|string -|_(none)_ -|_(none)_ -| - -|tvMain -|string -|_(none)_ -|_(none)_ -| - -|var.* -|string -|_(none)_ -|_(properties file only)_ -|Defines a variable that any other hint can interpolate as ${var.name}, with ${var.name:default} for a fallback. - -|vserv.allowSkipping -|boolean -|`true` -|_(none)_ -| - -|vserv.category -|int -|`29` -|_(none)_ -| - -|vserv.countryCode -|string -|`null` -|_(none)_ -| - -|vserv.locale -|string -|`en_US` -|_(none)_ -| - -|vserv.networkCode -|string -|`null` -|_(none)_ -| - -|vserv.scaleMode -|boolean -|`false` -|_(none)_ -| - -|vserv.transition -|int -|`300000` -|_(none)_ -| - -|vserv.zone -|string -|_(none)_ -|_(none)_ -| - -|watchMain -|string -|_(none)_ -|_(none)_ -| - -|watchStandalone -|boolean -|`false` -|_(none)_ -| - -|xxx.minPlayServicesVersion -|string -|_(none)_ -|_(none)_ -|This is a special case build hint. You can use any prefix to the build hint and the convention is to use your cn1lib name. It's identical to `android.minPlayServicesVersion` with the exception that the "highest version wins." That way if your cn1lib requires play services 9+ and uses: `myLib.minPlayServicesVersion=9.0.0` and another library has `otherLib.minPlayServicesVersion=10.0.0` then play services will be 10.0.0 - -|ios.*.appext.* -|string -|_(none)_ -|_(properties file only)_ -|Per-app-extension signing. ios.debug.appext..* and ios.release.appext..* are collapsed to unqualified keys before the request is sent. - -|ios.NFCReaderUsageDescription -|string -|_(none)_ -|_(none)_ -| - -|ios.NS*UsageDescription -|string -|_(none)_ -|_(properties file only)_ -|Info.plist privacy strings. The commonly used keys are catalogued individually and exposed through @IosPrivacy; this entry covers the open tail that the builder sweeps by prefix. - -|ios.NSBonjourServices -|string -|_(none)_ -|_(none)_ -| - -|ios.NSCalendarsFullAccessUsageDescription -|string -|`This app uses your calendars to read and schedule events.` -|`@IosPrivacy(calendarsFullAccessUsageDescription)` -| - -|ios.NSCalendarsUsageDescription -|string -|_(none)_ -|`@IosPrivacy(calendarsUsageDescription)` -| - -|ios.NSCalendarsWriteOnlyAccessUsageDescription -|string -|`This app uses your calendar to schedule events.` -|`@IosPrivacy(calendarsWriteOnlyAccessUsageDescription)` -| - -|ios.NSCameraUsageDescription -|string -|_(none)_ -|`@IosPrivacy(cameraUsageDescription)` -| - -|ios.NSHealthShareUsageDescription -|string -|_(none)_ -|`@IosPrivacy(healthShareUsageDescription)` -| - -|ios.NSHealthUpdateUsageDescription -|string -|_(none)_ -|`@IosPrivacy(healthUpdateUsageDescription)` -| - -|ios.NSLocalNetworkUsageDescription -|string -|_(none)_ -|`@IosPrivacy(localNetworkUsageDescription)` -| - -|ios.NSLocationAlwaysAndWhenInUseUsageDescription -|string -|_(none)_ -|`@IosPrivacy(locationAlwaysAndWhenInUseUsageDescription)` -| - -|ios.NSLocationAlwaysUsageDescription -|string -|_(none)_ -|`@IosPrivacy(locationAlwaysUsageDescription)` -| - -|ios.NSLocationWhenInUseUsageDescription -|string -|_(none)_ -|`@IosPrivacy(locationWhenInUseUsageDescription)` -| - -|ios.NSMicrophoneUsageDescription -|string -|_(none)_ -|`@IosPrivacy(microphoneUsageDescription)` -| - -|ios.NSRemindersFullAccessUsageDescription -|string -|`This app uses your reminders to read and schedule tasks.` -|`@IosPrivacy(remindersFullAccessUsageDescription)` -| - -|ios.NSRemindersUsageDescription -|string -|_(none)_ -|`@IosPrivacy(remindersUsageDescription)` -| - -|ios.NSXXXUsageDescription -|string -|_(none)_ -|_(none)_ -|iOS privacy flags for using certain APIs. Starting with Xcode 8, you're required to add usage description strings for certain APIs. Find a full list of the available keys in https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html[Apple's docs]. Some relevant ones include `ios.NSCameraUsageDescription`, `ios.NSContactsUsageDescription`, `ios.NSLocationAlwaysUsageDescription`, `NSLocationUsageDescription`, `ios.NSMicrophoneUsageDescription`, `ios.NSPhotoLibraryAddUsageDescription`, `ios.NSSpeechRecognitionUsageDescription`, `ios.NSSiriUsageDescription` - -|ios.UIRequiredDeviceCapabilities -|string -|_(none)_ -|_(none)_ -| - -|ios.actionSheetStyle -|string -|_(none)_ -|_(none)_ -| - -|ios.add_libs -|list (`;` delimited) -|_(none)_ -|`@Ios(addLibs)` -|A semicolon separated list of libraries that should be linked to the app to build it - -|ios.afterFinishLaunching -|string -|_(none)_ -|_(none)_ -|Objective-C code that can be injected into the iOS app delegate at the bottom of the body of the didFinishLaunchingWithOptions callback method - -|ios.appAttest -|boolean -|`false` -|_(none)_ -| - -|ios.appAttest.environment -|string -|_(none)_ -|_(none)_ -| - -|ios.appUsesNonExemptEncryption -|string -|_(none)_ -|_(none)_ -| - -|ios.app_groups -|string -|_(none)_ -|_(none)_ -|Space-delimited list of app groups that this app belongs to as described in https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19[Apple's documentation]. These are added to the entitlements file with key `com.apple.security.application-groups`. - -|ios.appext.NAME.provisioningURL -|string -|_(none)_ -|_(none)_ -|Cloud device builds only. URL of the provisioning profile for a generic app extension dropped into `ios/app_extensions/NAME/` (or a generated extension such as `CN1Widgets`), used when the extension folder doesn't bundle a `.mobileprovision` itself. The profile is installed on the build machine and added to the export options per bundle id. Used for both debug and release builds unless a qualified variant (below) is set. An extension is signed against its own App ID, so a device build with no profile for it -- by any of the three carriers -- is refused unless the app's own profile is a wildcard that covers the extension's bundle id. - -|ios.applicationDidEnterBackground -|string -|_(none)_ -|_(none)_ -|Objective-C code that can be injected into the iOS callback method (message) `applicationDidEnterBackground`. - -|ios.applicationQueriesSchemes -|list (`,` delimited) -|_(none)_ -|`@Ios(applicationQueriesSchemes)` -|Comma separated list of url schemes that `canExecute` will respect on iOS. If the url scheme isn't mentioned here `canExecute` will return false starting with iOS 9. Notice that this collides with `ios.plistInject` when used with the `LSApplicationQueriesSchemes...` value so you should use one or the other. For example, to enable `canExecute` for a url like `myurl://xys` you can use: `myurl,myotherurl` - -|ios.application_exits -|boolean -|_(none)_ -|_(none)_ -|true/false (defaults to false). Indicates whether the application should exit on home button press. The default is to exit, leaving the application running is only tested at the moment. - -|ios.associatedDomains -|string -|_(none)_ -|_(none)_ -|Comma-delimited list of domains associated with this app. Each domain should be prefixed by a supported prefix. For example, "applinks:" or "webcredentials:." See https://developer.apple.com/documentation/security/password_autofill/setting_up_an_app_s_associated_domains?language=objc[Apple's documentation on Associated domains] for more information. - -|ios.backgroundProcessingIds -|string -|_(none)_ -|_(none)_ -| - -|ios.background_modes -|string -|_(none)_ -|_(none)_ -| - -|ios.beforeFinishLaunching -|text_block -|_(none)_ -|`@Ios(beforeFinishLaunching)` -|Objective-C code that can be injected into the iOS app delegate at the top of the body of the didFinishLaunchingWithOptions callback method - -|ios.bitcode -|boolean -|`false` -|_(none)_ -|true/false defaults to false. Enables bitcode support for the build. - -|ios.blockScreenshotsOnEnterBackground -|boolean -|`false` -|_(none)_ -|true/false (defaults to false). Indicates that app should prevent iOS from taking screenshots when app enters background. Described https://shannah.github.io/cn1-recipes/#_hiding_sensitive_data_when_entering_background[here]. - -|ios.bluetooth.background -|string -|_(none)_ -|_(none)_ -| - -|ios.buildType -|string -|`debug` -|_(none)_ -| - -|ios.bundleVersion -|version -|_(none)_ -|`@Ios(bundleVersion)` -|Indicates the version number of the bundle, this is useful if you want to create a minor version number change for the beta testing support - -|ios.carplay.audio -|boolean -|`false` -|_(none)_ -| - -|ios.carplay.messaging -|boolean -|`false` -|_(none)_ -| - -|ios.carplay.navigation -|boolean -|`false` -|_(none)_ -| - -|ios.carplay.poi -|boolean -|`false` -|_(none)_ -| - -|ios.convertSignalsToExceptions -|boolean -|`true` -|_(none)_ -| - -|ios.criticalAlerts -|boolean -|`false` -|_(none)_ -| - -|ios.crypto.gcm -|boolean -|`false` -|_(none)_ -| - -|ios.debug.archs -|string -|_(none)_ -|_(none)_ -|Can be set to "armv7" to force iOS debug builds to be 32 bit. By default, debug builds are 64 bit only. - -|ios.debug.distributionMethod -|string -|_(none)_ -|_(none)_ -|Specifies distribution type for debug iOS builds only. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). - -|ios.debug.teamId -|string -|_(none)_ -|_(none)_ -|Specifies the team ID associated with the iOS debug provisioning profile and certificate. - -|ios.delayPushCompletion -|boolean -|`false` -|_(none)_ -| - -|ios.dependencyManager -|`auto`, `cocoapods`, `spm`, `both`, `none` -|`auto` -|`@Ios(dependencyManager)` -|Which native dependency manager to use: auto picks one from whichever of ios.pods and ios.spm.packages is set, and cocoapods, spm or both require the matching hint to be set. An unrecognized value fails the build. - -|ios.deployment_target -|version -|_(none)_ -|`@Ios(deploymentTarget)` -|Minimum iOS version the build targets. Set it to the lowest iOS you actually support; a higher value excludes older devices from the App Store listing. - -|ios.detectJailbreak -|boolean -|`false` -|_(none)_ -|true/false (defaults to false). When true, the iOS app will exit on launch if it detects that it's running on a jailbroken device. - -|ios.devLocale -|string -|_(none)_ -|_(none)_ -| - -|ios.disableScreenshots -|boolean -|`false` -|_(none)_ -| - -|ios.distributionMethod -|string -|_(none)_ -|_(none)_ -|Specifies distribution type for debug iOS builds. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). - -|ios.enableAutoplayVideo -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Makes videos "autoplay" when loaded on iOS - -|ios.enableBadgeClear -|boolean -|`true` -|_(none)_ -|Boolean true/false defaults to true. Clears the badge value with every load of the app, this is useful if the app doesn't manually keep track of number values for the badge - -|ios.enableGalleryMultiselect -|boolean -|`false` -|_(none)_ -| - -|ios.enableStatusBar7 -|boolean -|`true` -|_(none)_ -| - -|ios.entitlements.* -|string -|_(none)_ -|_(properties file only)_ -|Adds an arbitrary entitlement key to the generated entitlements file. - -|ios.entitlements.com.apple.developer -|string -|_(none)_ -|_(none)_ -| - -|ios.entitlements.com.apple.developer.applesignin -|string -|_(none)_ -|_(none)_ -| - -|ios.entitlements.com.apple.developer.healthkit -|boolean -|`false` -|_(none)_ -| - -|ios.entitlements.com.apple.developer.homekit -|string -|_(none)_ -|_(none)_ -| - -|ios.entitlements.com.apple.developer.networking.HotspotConfiguration -|string -|_(none)_ -|_(none)_ -| - -|ios.entitlements.com.apple.developer.nfc.hce -|string -|_(none)_ -|_(none)_ -| - -|ios.entitlements.com.apple.developer.nfc.readersession.formats -|string -|_(none)_ -|_(none)_ -| - -|ios.entitlementsInject -|xml -|_(none)_ -|_(none)_ -|Content to inject into the iOS entitlements file. This should be in the Plist XML format. See https://developer.apple.com/documentation/bundleresources/entitlements?language=objc[Apple Entitlements Documentation]. - -|ios.facebook.usePods -|boolean -|`true` -|_(none)_ -| - -|ios.facebook.version -|string -|`~>5.6.0` -|_(none)_ -| - -|ios.facebook_permissions -|string -|_(none)_ -|_(none)_ -|Permissions for Facebook used in the Android build target, applicable only if Facebook native integration is used. - -|ios.failOnWarning -|boolean -|`false` -|_(none)_ -| - -|ios.fieldNullChecks -|boolean -|`false` -|_(none)_ -| - -|ios.fileSharingEnabled -|boolean -|`false` -|_(none)_ -| - -|ios.firebaseAnalytics -|boolean -|`false` -|_(none)_ -| - -|ios.firebaseAnalyticsVersion -|string -|_(none)_ -|_(none)_ -| - -|ios.force64 -|boolean -|`false` -|_(none)_ -| - -|ios.generateSplashScreens -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Enables legacy generation of splash screen images instead of the current launch storyboards. - -|ios.glAppDelegateBody -|string -|_(none)_ -|_(none)_ -|Objective-C code that can be injected into the iOS app delegate within the body of the file before the end. This only makes sence for methods that aren't already declared in the class - -|ios.glAppDelegateHeader -|text_block -|_(none)_ -|`@Ios(glAppDelegateHeader)` -|Objective-C code that can be injected into the iOS app delegate at the top of the file. For example, if you need to include headers or make special imports for other injected code - -|ios.googleAdUnitId -|string -|_(none)_ -|_(none)_ -|Allows integrating admob/google play ads, this is effectively identical to google.adUnitId but only applies to iOS - -|ios.googleAdUnitIdPadding -|string -|_(none)_ -|_(none)_ -|Indicates the amount of padding to pass to the Google Ads placed at the bottom of the screen with `google.adUnitId` - -|ios.googleAdUnitTestDevice -|string -|`97cfc76e5efbc6dfa7eb2e6857b613a0` -|_(none)_ -| - -|ios.gplus.clientId -|string -|_(none)_ -|_(none)_ -| - -|ios.hceAids -|string -|_(none)_ -|_(none)_ -| - -|ios.headphoneCallback -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. When set to true it assumes the main class has two methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately as needed - -|ios.health.backgroundDelivery -|boolean -|`false` -|_(none)_ -| - -|ios.health.recalibrateEstimates -|boolean -|`false` -|_(none)_ -| - -|ios.health.required -|boolean -|`false` -|_(none)_ -| - -|ios.home.appGroup -|string -|_(none)_ -|_(none)_ -| - -|ios.home.commissioning -|boolean -|`true` -|_(none)_ -| - -|ios.home.commissioning.buildSettings.* -|string -|_(none)_ -|_(properties file only)_ -|Overrides an Xcode build setting for the Matter commissioning extension. - -|ios.home.commissioning.displayName -|string -|_(none)_ -|_(none)_ -| - -|ios.home.commissioning.fabric -|string -|_(none)_ -|_(none)_ -| - -|ios.home.commissioning.vendorId -|string -|`0xFFF1` -|_(none)_ -| - -|ios.home.required -|boolean -|`false` -|_(none)_ -| - -|ios.includeNullChecks -|boolean -|`true` -|_(none)_ -| - -|ios.includePush -|boolean -|`false` -|`@Ios(includePush)` -|true/false (defaults to false). Whether to include the push capabilities in the iOS build. Notice that the IDE plugin has an "Include Push" check box you *should* use under the iOS section. - -|ios.intents.appIntents -|boolean -|`true` -|_(none)_ -| - -|ios.intents.minDeploymentTarget -|string -|_(none)_ -|_(none)_ -| - -|ios.interface_orientation -|string -|_(none)_ -|`@Ios(interfaceOrientation)` -|UIInterfaceOrientationPortrait by default. Indicates the orientation, one or more of (separated by colon :): `UIInterfaceOrientationPortrait`, `UIInterfaceOrientationPortraitUpsideDown`, `UIInterfaceOrientationLandscapeLeft`, `UIInterfaceOrientationLandscapeRight`. Notice that the IDE plugin has an "Interface Orientation" combo box you *should* use under the iOS section. - -|ios.keyboardOpen -|boolean -|`true` -|_(none)_ -|Flips between iOS keyboard open mode and autofold keyboard mode. Defaults to true which means the keyboard will remain open and not fold automatically when editing moves to another field. - -|ios.keychainAccessGroup -|string -|_(none)_ -|_(none)_ -|Space-delimited list of keychain access groups that this app has access to as described in https://developer.apple.com/library/content/documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html#//apple_ref/doc/uid/TP30000897-CH204-SW11[Apple's documentation]. These are added to the entitlements file with the key `keychain-access-groups`. - -|ios.launchPlaceholder -|boolean -|`true` -|_(none)_ -| - -|ios.launchStoryboardName -|string -|`LaunchScreen` -|_(none)_ -| - -|ios.locationUsageDescription -|string -|_(none)_ -|_(none)_ -|This flag is required for iOS 8 and newer if you're using the location API. It needs to include a description of the reason for which you need access to the users location - -|ios.lowMemCamera -|boolean -|`false` -|_(none)_ -| - -|ios.maps.provider -|string -|_(none)_ -|_(none)_ -|iOS's own native map provider, overriding `maps.provider`. - -|ios.metal -|boolean -|`true` -|_(none)_ -|Boolean true/false defaults to true. Selects the Metal rendering backend (`CAMetalLayer`) over the legacy OpenGL ES 2 path (`CAEAGLLayer`). Metal is the supported iOS graphics API; OpenGL ES is deprecated. Set to `false` to opt out if you hit a Metal-only rendering regression. See link:#_metal_renderer[Working with iOS / Metal renderer] for details. - -|ios.metal.colorSpace -|string -|`sRGB` -|_(none)_ -|Selects the `CAMetalLayer.colorspace` for the Metal renderer. Accepts `sRGB` (default), `displayP3`, `deviceRGB`, `linearSRGB`, `extendedSRGB`, `extendedLinearSRGB`, or `none`. Has no effect when `ios.metal=false`. See link:#_choosing_a_color_space_for_the_metal_renderer[Working with iOS / Choosing a color space] for the full table. - -|ios.minDeploymentTarget -|version -|`6.0` -|`@Ios(minDeploymentTarget)` -|The null and empty-string reads of this hint are presence checks; 6.0 is the substantive default (IPhoneBuilder.java:4671). - -|ios.mopubAdSize -|string -|`MOPUB_BANNER_SIZE` -|_(none)_ -| - -|ios.mopubId -|string -|_(none)_ -|_(none)_ -| - -|ios.mopubTabletAdSize -|string -|`MOPUB_LEADERBOARD_SIZE` -|_(none)_ -| - -|ios.mopubTabletId -|string -|_(none)_ -|_(none)_ -| - -|ios.multitasking -|boolean -|`true` -|_(none)_ -|Set to true to enable iOS multitasking and split-screen support. This only works if `ios.xcode_verson=9.2`. - -|ios.nativeVerify -|string -|_(none)_ -|_(none)_ -|`nativeVerify` for the iOS translation alone. - -|ios.newPipeline -|boolean -|_(none)_ -|_(none)_ -|Boolean true/false defaults to true. Allows toggling the OpenGL ES 2.0 drawing pipeline off to the older OGL ES 1.0 pipeline. - -|ios.newStorageLocation -|boolean -|`true` -|`@Ios(newStorageLocation)` -|true/false defaults to false but defined on new projects as true by default. This changes the storage directory on iOS from using caches to using the documents directory which is the recommended location but might break compatibility. This is described in https://github.com/codenameone/CodenameOne/issues/1480[this issue] - -|ios.noUIWebView -|boolean -|`true` -|_(none)_ -| - -|ios.no_strip -|boolean -|`false` -|_(none)_ -| - -|ios.notificationPermissionAtLaunch -|boolean -|`false` -|_(none)_ -|true/false (defaults to false). Backward-compatibility flag for the pre-issue-#4876 behavior. By default, the iOS notification permission prompt is deferred until the app calls `Push.register()` or schedules a `LocalNotification`, matching the Android flow and giving the developer a chance to display a rationale screen first. Set this hint to `true` to restore the legacy behavior in which the prompt fires automatically inside `application:didFinishLaunchingWithOptions:` as soon as the app launches. Existing apps relying on the prompt being shown at launch should set this to `true`; new apps should leave it disabled and trigger the prompt explicitly when they're ready to ask for permission. - -|ios.objC -|boolean -|`false` -|`@Ios(objC)` -|Added the `-ObjC` compile flag to the project files which some native libraries require - -|ios.onDeviceDebug -|boolean -|`false` -|`@OnDeviceDebug(ios)` -|Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits source-line and locals metadata so a desktop proxy can serve the running app to any JDWP-speaking debugger. Has no effect on release builds. See the On-Device Debugging (iOS) chapter for the full flow. - -|ios.onDeviceDebug.proxyHost -|string -|`127.0.0.1` -|`@OnDeviceDebug(iosProxyHost)` -|Hostname or IP address the device-side listener dials to reach the desktop proxy. Default `127.0.0.1` (correct for the native iOS simulator). For a physical device, set this to the developer laptop's LAN IP. Has no effect unless `ios.onDeviceDebug=true`. - -|ios.onDeviceDebug.proxyPort -|int -|`55333` -|`@OnDeviceDebug(iosProxyPort)` -|TCP port on `ios.onDeviceDebug.proxyHost` where the proxy is listening for the device. Default `55333`. Has no effect unless `ios.onDeviceDebug=true`. - -|ios.onDeviceDebug.waitForAttach -|boolean -|`false` -|`@OnDeviceDebug(iosWaitForAttach)` -|Boolean true/false defaults to false. When `true`, the app blocks at startup until the proxy connects and the IDE tells the VM to continue. Useful when the breakpoint to investigate fires during app boot. Has no effect unless `ios.onDeviceDebug=true`. - -|ios.openURLInject -|xml -|_(none)_ -|_(none)_ -| - -|ios.optimizer -|string -|`on` -|_(none)_ -| - -|ios.plistInject -|xml -|_(none)_ -|`@Ios(plistInject)` -|entries to inject into the iOS plist file during build. - -|ios.pods -|list (`,` delimited) -|_(none)_ -|`@Ios(pods)` -|A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be linked to the app to build it. For example, `AFNetworking ~> 2.6, ORStackView ~> 3.0, SwiftyJSON ~> 2.3` - -|ios.pods.build.* -|string -|_(none)_ -|_(properties file only)_ -|Overrides an Xcode build setting for the generated CocoaPods project. - -|ios.pods.build.CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES -|string -|_(none)_ -|_(none)_ -| - -|ios.pods.build.CLANG_ENABLE_MODULES -|string -|_(none)_ -|_(none)_ -| - -|ios.pods.platform -|version -|_(none)_ -|`@Ios(podsPlatform)` -|Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a minimum platform level. For example, `ios.pods.platform=7.0`. - -|ios.pods.sources -|list (`,` delimited) -|_(none)_ -|`@Ios(podsSources)` -|Extra CocoaPods spec repositories to search, in addition to the default trunk. - -|ios.pods.use_frameworks! -|boolean -|`false` -|_(none)_ -| - -|ios.prerendered_icon -|boolean -|`false` -|`@Ios(prerenderedIcon)` -|true/false defaults to false. The iOS build process adapts the submitted icon for iOS conventions (adding an overlay) that might not be appropriate on some icons. Setting this to true leaves the icon unchanged (only scaled). - -|ios.project_type -|`ios`, `ipad`, `iphone` -|`ios` -|`@Ios(projectType)` -|one of ios, ipad, iphone (defaults to ios). Indicates whether the resulting binary is targeted to the iphone only or ipad only. Notice that the IDE plugin has a "Project Type" combo box you *should* use under the iOS section. - -|ios.release.archs -|string -|_(none)_ -|_(none)_ -|Can be set to "arm64" to only build iOS release builds for 64 bit. By default, release builds are both 32 and 64 bit. - -|ios.release.distributionMethod -|string -|_(none)_ -|_(none)_ -|Specifies distribution type for release iOS builds only. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). - -|ios.release.teamId -|string -|_(none)_ -|_(none)_ -|Specifies the team ID associated with the iOS release provisioning profile and certificate. - -|ios.rpmalloc -|string -|_(none)_ -|_(none)_ -|`true`/`false` Use https://github.com/rampantpixels/rpmalloc[rpmalloc] instead of malloc/free for memory allocation in ParparVM. This will cause the deployment target to be changed to a minimum of iOS 8.0. - -|ios.shareAppGroup -|string -|_(none)_ -|_(none)_ -| - -|ios.spm.packages -|list (`;` delimited) -|_(none)_ -|`@Ios(spmPackages)` -|Swift Package Manager packages to link, one per entry, each written as identity\|url\|requirement. - -|ios.spm.products.* -|string -|_(none)_ -|_(properties file only)_ -|Selects which products of a Swift Package Manager package to link, keyed by package identity. - -|ios.statusBarFG -|string -|_(none)_ -|_(none)_ -| - -|ios.statusbar_hidden -|boolean -|_(none)_ -|_(none)_ -|true/false defaults to false. Hides the iOS status bar if set to true. - -|ios.superfastBuild -|boolean -|`false` -|_(none)_ -| - -|ios.surfaces.appGroup -|string -|_(none)_ -|_(none)_ -| - -|ios.surfaces.buildSettings.* -|string -|_(none)_ -|_(properties file only)_ -|Overrides an Xcode build setting for the external-surfaces extension. - -|ios.surfaces.deploymentTarget -|version -|`16.1` -|_(none)_ -| - -|ios.surfaces.extension -|boolean -|`true` -|_(none)_ -| - -|ios.surfaces.frequentUpdates -|boolean -|`false` -|_(none)_ -| - -|ios.swiftVersion -|version -|`5.0` -|_(none)_ -| - -|ios.teamId -|string -|_(none)_ -|`@Ios(teamId)` -|Specifies the team ID associated with the iOS provisioning profile and certificate. Use `ios.debug.teamId` and `ios.release.teamId` to specify different team IDs for debug and release builds respectively. - -|ios.testFlight -|boolean -|_(none)_ -|_(none)_ -|Boolean true/false defaults to false and works only for pro accounts. Enables the testflight support in the release binaries for easy beta testing. Notice that the IDE plugin has a "Test Flight" check box you *should* use under the iOS section. - -|ios.themeMode -|`auto`, `modern`, `ios7`, `legacy` -|_(none)_ -|`@Ios(themeMode)` -|`auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the existing iOS 7 flat theme so pre-refactor screenshot goldens and apps see no behavior change. `modern` / `liquid` opts in to the CSS-generated iOS Modern (liquid-glass) theme shipped from `native-themes/ios-modern/theme.css`. `ios7` / `flat` is the same as `auto` - pre-liquid iOS 7 flat theme; `legacy` / `iphone` loads the pre-iOS 7 iPhone theme. The `auto` -> modern flip is planned for a future release. - -|ios.timeSensitiveNotifications -|boolean -|`false` -|_(none)_ -| - -|ios.twoDigitVersion -|boolean -|`false` -|_(none)_ -| - -|ios.uiscene -|boolean -|`true` -|`@Ios(uiscene)` -|true/false (defaults to true). Enables iOS UIScene lifecycle support. UIScene lets iOS manage one or more app UI sessions independently, improving lifecycle handling in modern iOS versions. Apple has indicated UIScene will be required starting with iOS 27, so this is now on by default; set the flag to `false` only if you need to temporarily fall back to the legacy `UIApplicationDelegate` lifecycle. - -|ios.urlScheme -|string -|_(none)_ -|`@Ios(urlScheme)` -|Allows intercepting a URL call using the syntax `urlPrefix` - -|ios.urlSchemes -|string -|_(none)_ -|_(none)_ -| - -|ios.useAVKit -|boolean -|`true` -|_(none)_ -|Use AVKit for video components on iOS rather than `MPMoviePlayerController` on iOS versions 8 through 12. iOS 13 will always use AVKit, and iOS 7 and lower will always use `MPMoviePlayerController`. Default value `false` - -|ios.useJavascriptCore -|boolean -|`false` -|_(none)_ -| - -|ios.usePhotoKitForMultigallery -|boolean -|`false` -|_(none)_ -| - -|ios.usePrintf -|boolean -|`false` -|_(none)_ -| - -|ios.useWKWebView -|boolean -|`true` -|_(none)_ -| - -|ios.usesBackgroundProcessing -|boolean -|`false` -|_(none)_ -| - -|ios.viewDidLoad -|string -|_(none)_ -|_(none)_ -|Objective-C code that can be injected into the iOS callback method (message) `viewDidLoad` - -|ios.viewDidLoadInclude -|string -|_(none)_ -|_(none)_ -| - -|ios.wallet.appGroup -|string -|_(none)_ -|_(none)_ -|App Group id starting with `group.` shared by the app and the generated Wallet extensions. The app publishes pass entries into this group through `com.codename1.payment.WalletExtension` and the group is added to the app and extension entitlements automatically. Required when `ios.wallet.extension=true`. - -|ios.wallet.authEndpoint -|string -|_(none)_ -|_(none)_ -|HTTPS URL the generated login UI extension POSTs `{"username","password"}` to; the JSON response's `token` is stored in the App Group for the provisioning request. Required when `ios.wallet.includeUI=true`. - -|ios.wallet.extension -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Generates an Apple Wallet issuer provisioning extension (the "From apps on your iPhone" flow in the Wallet app) and embeds it in the build. Requires `ios.wallet.appGroup` and `ios.wallet.issuerEndpoint`. See the Apple Wallet Extension chapter. - -|ios.wallet.generateRequestInject -|string -|_(none)_ -|_(none)_ -|Swift injected at the generate-request marker of the non-UI Wallet extension. - -|ios.wallet.generateResponseInject -|string -|_(none)_ -|_(none)_ -|Swift injected at the generate-response marker of the non-UI Wallet extension. - -|ios.wallet.includeUI -|boolean -|`false` -|_(none)_ -|Boolean true/false defaults to false. Also generates the Wallet authorization UI extension - a login form shown inside the Wallet app when the app reports that authentication is required. Requires `ios.wallet.authEndpoint`. - -|ios.wallet.issuerEndpoint -|string -|_(none)_ -|_(none)_ -|HTTPS URL of the issuer backend endpoint that produces the encrypted provisioning payload. The generated extension POSTs Apple's certificates/nonce plus the card identifier and auth token there as JSON. Required when `ios.wallet.extension=true`. - -|ios.wallet.nonuiExtensionName -|string -|`WalletNonUIExtension` -|_(none)_ -| - -|ios.wallet.nonuiImportsInject -|string -|_(none)_ -|_(none)_ -|Extra `import` lines for the non-UI Wallet extension. - -|ios.wallet.passEntriesInject -|string -|_(none)_ -|_(none)_ -|Swift injected where the non-UI Wallet extension lists its pass entries. - -|ios.wallet.remotePassEntriesInject -|string -|_(none)_ -|_(none)_ -|Swift injected where the non-UI Wallet extension lists its remote pass entries. - -|ios.wallet.statusInject -|string -|_(none)_ -|_(none)_ -|Swift injected at the status marker of the non-UI Wallet extension. - -|ios.wallet.uiAuthRequestInject -|string -|_(none)_ -|_(none)_ -|Swift injected at the auth-request marker of the UI Wallet extension. - -|ios.wallet.uiAuthResponseInject -|string -|_(none)_ -|_(none)_ -|Swift injected at the auth-response marker of the UI Wallet extension. - -|ios.wallet.uiExtensionName -|string -|`WalletUIExtension` -|_(none)_ -| - -|ios.wallet.uiImportsInject -|string -|_(none)_ -|_(none)_ -|Extra `import` lines for the UI Wallet extension. - -|ios.wallet.uiViewDidLoadInject -|string -|_(none)_ -|_(none)_ -|Swift injected into `viewDidLoad` of the UI Wallet extension. - -|ios.xcode_version -|string -|_(none)_ -|_(none)_ -|The version of Xcode used on the server. Defaults to 4.5; accepts 5.0 as an option and nothing else. - -|ios.zbar_flash -|boolean -|`true` -|_(none)_ -| - -|javascript.includeVideoJS -|boolean -|`false` -|_(none)_ -| - -|javascript.inject.afterHead -|string -|_(none)_ -|_(none)_ -|Content to be injected into the index.html file at the end of the `` tag. - -|javascript.inject.beforeHead -|string -|_(none)_ -|_(none)_ -|Content to be injected into the index.html file at the beginning of the `` tag. - -|javascript.inject_proxy -|boolean -|`true` -|_(none)_ -|true/false (defaults to `true`). The ParparVM builder generates a same-origin proxy bundle and configures the app to use it. Setting this to `false` disables both proxy generation and proxy URL injection. - -|javascript.minifying -|boolean -|_(none)_ -|_(none)_ -|true/false (defaults to `true`). By default the JavaScript code is minified to reduce file size. You may optionally disable minification by setting `javascript.minifying` to `false`. - -|javascript.port -|string -|_(none)_ -|_(none)_ -|`parparvm` (default) or `teavm`. Selects the public JavaScript compiler for cloud builds. `teavm` retains the original builder as a compatibility fallback. - -|javascript.portSources -|string -|_(none)_ -|_(none)_ -| - -|javascript.proxy.allowedTargets -|string -|_(none)_ -|_(none)_ -|Comma-separated target origins, host names, or wildcard subdomains that a generated proxy may access, for example `https://api.example.com,*.services.example.org`. If omitted, the proxy accepts any HTTP or HTTPS target and the build emits a warning. - -|javascript.proxy.target -|string -|`jakarta-servlet` -|_(none)_ -|The generated ParparVM proxy deployment platform. Supported values are `jakarta-servlet` (default), `javax-servlet`, `node`, `php`, `aws-lambda`, `google-cloud-functions`, `cloudflare-workers`, and `none`. - -|javascript.proxy.url -|string -|_(none)_ -|_(none)_ -|The URL of an existing proxy to use for network requests. Setting it suppresses generated proxy packaging unless `javascript.proxy.target` is also set. If `javascript.inject_proxy` is `false`, this build hint is ignored. - -|javascript.sourceFilesCopied -|boolean -|_(none)_ -|_(none)_ -|true/false (defaults to `false`). Setting this flag to `true` will cause available java source files to be included in the resulting .zip and .war files. These may be used by Chrome during debugging. - -|javascript.stopOnErrors -|boolean -|_(none)_ -|_(none)_ -|true/false (defaults to `true`). Causes a TeaVM JavaScript build to fail when the compiler reports warnings. Setting this to `false` may allow the fallback builder to complete, but can turn compiler diagnostics into runtime failures that are more difficult to debug. - -|javascript.teavm.version -|string -|_(none)_ -|_(none)_ -|(Optional) The version of TeaVM to use for the build. *Use caution*, only use this property if you know what you're doing! - -|linux.arch -|string -|_(none)_ -|_(none)_ -| - -|linux.cc -|string -|_(none)_ -|_(none)_ -| - -|linux.debug -|boolean -|`false` -|_(none)_ -| - -|linux.libc -|string -|`glibc` -|_(none)_ -| - -|linux.musl -|boolean -|`false` -|_(none)_ -| - -|linux.muslNativeCc -|boolean -|`false` -|_(none)_ -| - -|linux.nativeVerify -|string -|_(none)_ -|_(none)_ -|`nativeVerify` for the native Linux translation alone. - -|linux.toolchain -|string -|_(none)_ -|_(none)_ -| - -|desktop.mac.cef -|boolean -|_(none)_ -|_(none)_ -|Whetherto use CEF for media or BrowserComponent instead of JavaFX in Mac desktop builds. true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a future version. - -|macNative.appCategory -|string -|`public.app-category.utilities` -|_(none)_ -|Mac Native builds only. `LSApplicationCategoryType` in the generated Info.plist. Default `public.app-category.utilities`. See https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype[Apple's category list]. - -|macNative.bundleId -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. Used only when `macNative.deriveBundleId=false`. Default: `.mac`. - -|macNative.copyright -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. `NSHumanReadableCopyright` in the Info.plist. Defaults to `Copyright (c) `. - -|macNative.deriveBundleId -|boolean -|`true` -|_(none)_ -|Mac Native builds only. `true` (default) maps to Xcode's `DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER=YES` (Xcode appends `.maccatalyst` to the iOS bundle ID). Set to `false` to take the bundle ID verbatim from `macNative.bundleId`. - -|macNative.distribution -|string -|`appStore` -|_(none)_ -|Mac Native builds only. `appStore` (default), `developerID`, or `both`. Selects which entitlements + ExportOptions plist + signing certificate to emit. `both` emits parallel `*-AppStore.entitlements` / `*-DeveloperID.entitlements` and matching `ExportOptions-*-Mac.plist` files so a single project can be archived to either channel. - -|macNative.enabled -|boolean -|`false` -|_(none)_ -| - -|macNative.entitlements.allowJit -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. `true` enables `com.apple.security.cs.allow-jit` for hardened runtime. ParparVM is AOT-compiled so this is `false` by default; flip when bundling a JIT-using cn1lib. - -|macNative.entitlements.appSandbox -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. `true` enables `com.apple.security.app-sandbox`. Default is `true` for the `appStore` channel (Mac App Store requires the sandbox), `false` for `developerID`. - -|macNative.entitlements.extra -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. Free-form XML inserted verbatim inside the `…` of the generated entitlements plist. Use for entitlements Codename One doesn't expose individually. - -|macNative.entitlements.files.userSelected -|string -|`readwrite` -|_(none)_ -|Mac Native builds only. `readwrite` (default), `readonly`, or `none`. Sets the matching `com.apple.security.files.user-selected.*` entitlement. - -|macNative.entitlements.hardenedRuntime -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. `true` enables hardened runtime restrictions. Default is `true` for `developerID` (notarization requires it), `false` for `appStore`. - -|macNative.entitlements.network.client -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. Toggles `com.apple.security.network.client`. Default `true`. - -|macNative.entitlements.network.server -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. Toggles `com.apple.security.network.server`. Default `false`. - -|macNative.fixedWindowSize -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. Opt-in. Format `x` — for example `1024x685`. When set, the Catalyst window's `UISceneSession.sizeRestrictions` minimum and maximum are pinned to the requested size so every launch produces a byte-identical window. Default unset, in which case the window is resizable. The CI screenshot pipeline turns this on to keep the strict-pixel golden comparison stable; production apps should leave it off. - -|macNative.iosMinDeploymentTarget -|version -|`13.1` -|_(none)_ -|Mac Native builds only. iOS deployment-target floor for the Catalyst slice (`IPHONEOS_DEPLOYMENT_TARGET`). Default `13.1`. The plugin coerces the iOS slice's minimum upward when set. - -|macNative.minDeploymentTarget -|version -|`10.15` -|_(none)_ -|Mac Native builds only. Minimum macOS version (`MACOSX_DEPLOYMENT_TARGET`). Default `10.15` — earlier versions don't support Mac Catalyst. - -|macNative.notarize -|boolean -|`false` -|_(none)_ -| - -|macNative.notarize.appleId -|string -|_(none)_ -|_(none)_ -| - -|macNative.notarize.keychainProfile -|string -|_(none)_ -|_(none)_ -| - -|macNative.notarize.password -|secret -|_(none)_ -|_(none)_ -| - -|macNative.notarize.teamId -|string -|_(none)_ -|_(none)_ -| - -|macNative.provisioningProfile.* -|string -|_(none)_ -|_(properties file only)_ -|Per-profile provisioning data for a native macOS build, keyed by profile name. - -|macNative.provisioningProfile.appStore -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. Provisioning profile name for App Store distribution — used only when `macNative.signing.style=manual`. - -|macNative.provisioningProfile.developerID -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. Provisioning profile name for Developer ID distribution — used only when `macNative.signing.style=manual`. - -|macNative.signing.style -|string -|`automatic` -|_(none)_ -|Mac Native builds only. `automatic` (default) lets Xcode pick the signing certificate; `manual` forces the certificate identity hints below to be respected verbatim. - -|macNative.signingIdentity.appStore -|string -|`Apple Distribution` -|_(none)_ -|Mac Native builds only. Signing certificate identity for the App Store channel. Default `Apple Distribution`. - -|macNative.signingIdentity.developerID -|string -|`Developer ID Application` -|_(none)_ -|Mac Native builds only. Signing certificate identity for the Developer ID channel. Default `Developer ID Application`. - -|macNative.teamId -|string -|_(none)_ -|_(none)_ -|Mac Native builds only. Apple Developer Team ID (alphanumeric). Falls back to `ios.release.teamId` → `ios.teamId` → `ios.debug.teamId` since most apps share a single Apple Developer Team for iOS and Mac. - -|tvNative.bundleId -|string -|_(none)_ -|_(none)_ -|Bundle identifier of the tvOS app. Defaults to `.tvos`. - -|tvNative.displayName -|string -|_(none)_ -|_(none)_ -|The tvOS app name shown on Apple TV. Defaults to the app's display name. - -|tvNative.enabled -|boolean -|`false` -|_(none)_ -|true/false (defaults to false). Adds an Apple TV (tvOS) application target to the iOS build. The tvOS app is a separate `appletvos` target built from the same Java/Kotlin sources through ParparVM (UIKit + Metal; tvOS has no OpenGL ES). Enabling it doesn't change the iOS app -- in particular it doesn't override the iOS app's `ios.metal` setting. Also turned on implicitly by `codename1.tvMain`. - -|tvNative.mainClass -|string -|_(none)_ -|_(none)_ -| - -|tvNative.minDeploymentTarget -|version -|`13.0` -|_(none)_ -|`TVOS_DEPLOYMENT_TARGET` for the tvOS target. Defaults to `13.0`. - -|tvNative.teamId -|string -|_(none)_ -|_(none)_ -|Apple Developer Team ID used to sign the tvOS target. Falls back to the iOS team id (`ios.release.teamId` / `ios.teamId` / `ios.debug.teamId`). - -|watchNative.enabled -|boolean -|`false` -|_(none)_ -| - -|watchNative.health -|string -|_(none)_ -|_(none)_ -| - -|watchNative.health.workoutProcessing -|boolean -|`false` -|_(none)_ -| - -|watchNative.mainClass -|string -|_(none)_ -|_(none)_ -| - -|watchNative.surfaces.deploymentTarget -|string -|`10.0` -|_(none)_ -|Deployment target of the WidgetKit extension that carries the watch complication. This is the WATCH APP's floor rather than the extension's own: WidgetKit reaches back to watchOS 9, but the extension is embedded in the watch app, so advertising a version the app itself refuses to install on claims support the user never gets. - -|win.desktop-vm -|string -|_(none)_ -|_(none)_ -|The JVM that should be bundled in the Windows desktop build. Windows desktop builds only. Supported values: zulu8, zuluFx8, zulu8-32bit, zuluFx8-32bit, zulu11, zuluFx11, zulu11-32bit, zuluFx11-32bit - -|win.installDirName -|string -|_(none)_ -|_(none)_ -|Windows desktop builds only. Overrides the default installation folder name suggested by the installer (under `Program Files`). Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly installation folder name (for example, `win.installDirName=My Application`). The application ID used by Windows for upgrade detection is unaffected, so existing installations continue to upgrade. - -|win.shortcutName -|string -|_(none)_ -|_(none)_ -|Windows desktop builds only. Overrides the name used for the Start Menu shortcut, the Desktop shortcut and (when `win.launchOnStart=true`) the autostart shortcut. Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly shortcut label (for example, `win.shortcutName=My Application`). - -|win.vm32bit -|boolean -|_(none)_ -|_(none)_ -|true/false (defaults to false). Forces windows desktop builds to use the Win32 JVM instead of the 64 bit VM making them compatible with older Windows Machines. This is off by default at the moment because of a bug in JDK 8 update 112 that might cause this to fail for some cases - -|windows.arch -|string -|_(none)_ -|_(none)_ -|Native Windows port only (the `windows-native` build target -- not the JVM `win.*` desktop hints above). Target CPU architecture for the standalone `.exe`: `x64` (the default) or `arm64`. Accepts the usual synonyms (`x86_64`/`amd64`, `aarch64`). clang-cl cross-compiles to the chosen architecture from either host. See the link:#_working_with_the_native_windows_port[Working with the native Windows port chapter]. - -|windows.calendar.restrictedCapability -|boolean -|`false` -|_(none)_ -| - -|windows.debug -|boolean -|`false` -|_(none)_ -|Native Windows port only. true/false (defaults to false). When `false` the `.exe` is built optimized and *stripped* -- no PDB, dead-stripped unreferenced code (`/OPT:REF`) and folded identical functions (`/OPT:ICF`) -- which is the shipping default. Set `true` to keep debug symbols (a `.pdb` next to the exe, via `RelWithDebInfo` / clang-cl `/Zi` + linker `/DEBUG`) so a native crash address can be symbolized during development. Optimizations stay on in both cases. - -|windows.extensions -|string -|_(none)_ -|_(none)_ -|Historical build hint for the discontinued UWP target. It's retained here only for legacy reference and isn't used by current supported build targets. - -|windows.msix -|boolean -|`false` -|_(none)_ -| - -|windows.msix.identityName -|string -|_(none)_ -|_(none)_ -| - -|windows.msix.password -|secret -|_(none)_ -|_(none)_ -| - -|windows.msix.pfx -|string -|_(none)_ -|_(none)_ -| - -|windows.msix.publisher -|string -|_(none)_ -|_(none)_ -| - -|windows.msix.version -|string -|_(none)_ -|_(none)_ -| - -|windows.nativeVerify -|string -|_(none)_ -|_(none)_ -|`nativeVerify` for the native Windows translation alone. - -|windows.sdkRoot -|string -|_(none)_ -|_(none)_ -|Native Windows port only; used when building on a *non-Windows* host (for example a Linux build server). Path to a Windows SDK laid out by https://github.com/Jake-Shadle/xwin[`xwin splat`] (a directory containing `crt/include` and `sdk/include/um`), used to cross-compile the `.exe` with clang-cl + lld-link instead of a Visual Studio environment. If unset, the `CN1_XWIN_SYSROOT` environment variable is used. Ignored on Windows hosts, which build through Visual Studio. The same SDK serves both `windows.arch` targets (its `x86_64` / `aarch64` lib subdirs). - -|windows.signing -|boolean -|`true` -|_(none)_ -|Native Windows port only. `true`/`false` (default `true`). Set `false` to force an unsigned build even when a certificate is available. - -|windows.signing.digest -|string -|`sha256` -|_(none)_ -|Native Windows port only. Signature digest algorithm. Default `sha256`. - -|windows.signing.name -|string -|_(none)_ -|_(none)_ -| - -|windows.signing.password -|secret -|_(none)_ -|_(none)_ -| - -|windows.signing.pkcs12 -|string -|_(none)_ -|_(none)_ -| - -|windows.signing.timestampUrl -|string -|`http://timestamp.digicert.com` -|_(none)_ -|Native Windows port only. RFC 3161 timestamp server used when signing, so the signature stays valid after the certificate expires. Default `http://timestamp.digicert.com`; set empty to disable timestamping. - -|windows.signing.url -|string -|_(none)_ -|_(none)_ -| - -|=== diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java index 626ec0c9867..220023ade9c 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintCodeGenerator.java @@ -101,6 +101,15 @@ private BuildHintCodeGenerator() { * file receives the developer guide's table */ public static void main(String[] args) throws IOException { + // The developer guide's table is not checked in: it is rendered from the + // catalog every time the guide is built, so it cannot drift and there is + // nothing for a hand edit to survive in. This mode writes that one file + // and nothing else, so a documentation build does not also rewrite source + // trees it has no business touching. + if (args.length == 2 && "--table-only".equals(args[0])) { + write(new File(args[1]), asciidocTable()); + return; + } if (args.length < 2) { System.err.println("usage: BuildHintCodeGenerator " + " [output...]"); diff --git a/scripts/gen-build-hint-annotations.sh b/scripts/gen-build-hint-annotations.sh index fd63b45b109..2f92a12542b 100755 --- a/scripts/gen-build-hint-annotations.sh +++ b/scripts/gen-build-hint-annotations.sh @@ -39,10 +39,13 @@ java -cp "$CLASSES" com.codename1.build.shared.BuildHintCodeGenerator \ "$ANN_ROOT" "$CATALOG_SRC" "$JAVASE_SRC" "$GUIDE_TABLE" if [ "$check" -eq 1 ]; then + # The developer guide's table is deliberately absent: it is not checked in, so + # there is nothing for it to drift FROM. scripts/gen-build-hint-table.sh + # renders it during the doc build instead, and this script still writes it for + # a local asciidoctor run -- gitignored, so that copy is never reviewed. targets=("CodenameOne/src/com/codename1/annotations/buildhints" "maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java" - "Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java" - "docs/developer-guide/_generated-build-hints.adoc") + "Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java") if ! git -C "$REPO_ROOT" diff --quiet -- "${targets[@]}" \ || [ -n "$(git -C "$REPO_ROOT" ls-files --others --exclude-standard -- "${targets[@]}")" ]; then echo "::error::Generated build hint annotations are out of date." >&2 diff --git a/scripts/gen-build-hint-table.sh b/scripts/gen-build-hint-table.sh new file mode 100755 index 00000000000..1cbab70f6a3 --- /dev/null +++ b/scripts/gen-build-hint-table.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Renders the developer guide's build hint table from the catalog. +# +# The table is NOT checked in. It is generated from maven/build-hint-catalog +# every time the guide is rendered, so it cannot drift from the catalog and a +# hand edit has nothing to survive in -- which is what a generated file that +# lives in git always eventually invites. +# +# Both renderers call this first: the developer-guide-docs workflow and +# scripts/website/build.sh. Asciidoctor resolves the include relative to the +# including document, so the file has to land beside it. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${1:-$REPO_ROOT/docs/developer-guide/_generated-build-hints.adoc}" + +echo "gen-build-hint-table: building the catalog" >&2 +(cd "$REPO_ROOT/maven" && mvn -q -B -pl build-hint-catalog package -DskipTests) + +CLASSES="$REPO_ROOT/maven/build-hint-catalog/target/classes" +java -cp "$CLASSES" com.codename1.build.shared.BuildHintCodeGenerator --table-only "$OUT" +echo "gen-build-hint-table: wrote $OUT" >&2 diff --git a/scripts/website/build.sh b/scripts/website/build.sh index 737a9f00fa9..e9646e8c501 100755 --- a/scripts/website/build.sh +++ b/scripts/website/build.sh @@ -321,6 +321,11 @@ build_developer_guide_for_site() { local build_date build_date="$(date +%Y-%m-%d)" + # The guide includes a build hint table that is rendered from + # maven/build-hint-catalog rather than checked in, so it has to exist before + # asciidoctor resolves the include. + "${REPO_ROOT}/scripts/gen-build-hint-table.sh" + ( cd "${REPO_ROOT}" asciidoctor \ From ffb67152e32cd99a2f9ef6ead8e7286efe111df9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:41:06 +0300 Subject: [PATCH 034/115] Migrate a legacy spelling to the name the build emits Four from the same review, two of them consequences of the alias fix I made last round. Verification searched the manifest for the key the FILE used. A legacy spelling is deleted under its own name and comes back under the canonical one, so migrating cn1.nativeTheme, cn1.androidTheme or and.captureRecord reported the hint missing and rolled a correct migration back. The two lists are separate now: migratedKeys is what gets deleted, verifiedKeys is what must come back. Declaring one setting under two spellings with different values was resolved by whichever Properties.stringPropertyNames happened to enumerate last -- and then both lines were deleted, so a migration could change what the app builds with and still report success, since the check asks whether the hint came back and not what it holds. There is no rule to apply: and.captureRecord is read after android.captureRecord and overrides it, while the theme aliases are each handed to Display.setProperty and resolved in the framework. So the ambiguous case is refused, naming both spellings, and the developer decides. Equal values are not ambiguous and migrate. Settings accepted a bare @Build or @Android whatever it meant. Those names are ordinary enough that another library's annotation with a matching attribute read as ownership, and the editor was withheld for a hint the processor never emits. The simple name now counts only when an import brings it in from com.codename1.annotations.buildhints -- by name or on demand -- while the fully qualified spelling still needs none. Nine existing tests failed on this, all because their snippets used a bare annotation with no import, which no real source does; they carry the import now and are better tests for it. Two identical computed expressions in one file collapsed to one key, so a second getArg(hintAndMarker[0], ...) over a different table would have been validated against the first table's expansions and its own hints never checked. The accounting line carries a call count -- `#2` -- and a mismatch is reported with the number to record, which forces the second call to be looked at. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 56 +++++++++++++-- scripts/build-hint-computed-sites.txt | 7 +- scripts/check-build-hint-catalog.py | 29 ++++++-- .../settings/CodenameOneSettings.java | 49 ++++++++++--- .../settings/BuildHintCatalogTest.java | 70 ++++++++++++++++--- 5 files changed, 178 insertions(+), 33 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index e3e0457c9e9..7803ac3c92c 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -139,7 +139,14 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException // annotation simple name -> attribute -> source literal Map> plan = new TreeMap>(); List migratedKeys = new ArrayList(); + /// Canonical keys to look for in the emitted manifest. Not the same list: + /// a legacy spelling is deleted under the name the file used and comes + /// back under the name the annotation carries. + List verifiedKeys = new ArrayList(); List skipped = new ArrayList(); + /// Canonical hint name to every properties key that names it. + Map> declaredAs = new TreeMap>(); + Map byHint = new TreeMap(); for (String key : new ArrayList(settings.stringPropertyNames())) { if (!key.startsWith(BuildHints.ARG_PREFIX)) { @@ -160,9 +167,45 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException skipped.add(name + " (no annotation for this hint yet)"); continue; } - String literal = toSourceLiteral(hint, settings.getProperty(key), kotlinTarget); + List spellings = declaredAs.get(hint.name()); + if (spellings == null) { + spellings = new ArrayList(); + declaredAs.put(hint.name(), spellings); + byHint.put(hint.name(), hint); + } + spellings.add(key); + } + + for (Map.Entry> e : declaredAs.entrySet()) { + BuildHints.Hint hint = byHint.get(e.getKey()); + List spellings = e.getValue(); + + // One setting, spelled more than one way, with the two disagreeing. + // Which one the build honours is decided per hint and not always by + // the builder: and.captureRecord is read after android.captureRecord + // and overrides it, while the theme aliases are each handed to + // Display.setProperty and resolved in the framework. So there is no + // rule to apply here, and picking either value would change what the + // app builds with while reporting a successful migration -- the + // verification cannot catch it, since it checks that the hint came + // back and not what it holds. The developer decides. + String value = settings.getProperty(spellings.get(0)); + boolean disagree = false; + for (String other : spellings) { + String v = settings.getProperty(other); + if (value == null ? v != null : !value.equals(v)) { + disagree = true; + } + } + if (disagree) { + skipped.add(e.getKey() + " (declared as " + spellings + " with different values; " + + "delete all but one and run this again)"); + continue; + } + + String literal = toSourceLiteral(hint, value, kotlinTarget); if (literal == null) { - skipped.add(name + " = '" + settings.getProperty(key) + skipped.add(e.getKey() + " = '" + value + "' (value is outside the hint's supported set)"); continue; } @@ -173,7 +216,12 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException plan.put(annotation, members); } members.put(hint.attr(), literal); - migratedKeys.add(key); + // Every spelling goes, because they were all naming this one setting. + migratedKeys.addAll(spellings); + // Verified under the CANONICAL key: that is what the annotation makes + // the processor emit. Checking the alias the file happened to use + // reported it missing and rolled a correct migration back. + verifiedKeys.add(BuildHints.ARG_PREFIX + hint.name()); } if (plan.isEmpty()) { @@ -258,7 +306,7 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException + restore(source, originalSource, settingsFile, originalSettings), ex); } - String missing = verifyAnnotationsAreProcessed(projectDir, migratedKeys); + String missing = verifyAnnotationsAreProcessed(projectDir, verifiedKeys); if (missing != null) { String restoreFailed = restore(source, originalSource, settingsFile, originalSettings); throw new MojoFailureException("The annotations were added but the build did not turn " diff --git a/scripts/build-hint-computed-sites.txt b/scripts/build-hint-computed-sites.txt index 30f778273eb..454cded0e92 100644 --- a/scripts/build-hint-computed-sites.txt +++ b/scripts/build-hint-computed-sites.txt @@ -9,7 +9,12 @@ # every expansion must be catalogued or match a dynamic pattern. A new computed # site therefore forces a catalog decision instead of disappearing. # -# Format: ||[,...] +# Format: ||[,...][#] +# +# The call count defaults to 1 and only matters when one file makes the same +# call twice: two `getArg(hintAndMarker[0], ...)` over different tables produce +# an identical key, so without it the second would be validated against the +# first one's expansions and its own hints never checked at all. # # Keyed by the expression, not just the file: a builder that already appears here # would otherwise absorb a SECOND computed hint for free -- adding diff --git a/scripts/check-build-hint-catalog.py b/scripts/check-build-hint-catalog.py index 6cb813f3b39..f430b6e76cf 100755 --- a/scripts/check-build-hint-catalog.py +++ b/scripts/check-build-hint-catalog.py @@ -10,7 +10,7 @@ Held against a baseline rather than failing outright: a large tail of hints predates the catalog. The point is that *new* code cannot add another one. """ -import fnmatch, os, re, sys +import collections, fnmatch, os, re, sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.join(ROOT, "scripts")) @@ -164,11 +164,15 @@ def main(): # cannot. path, _, rest = line.partition("|") expr, _, expansions = rest.rpartition("|") - declared[(path, " ".join(expr.split()))] = [ - e for e in expansions.split(",") if e] + names, _, count = expansions.partition("#") + declared[(path, " ".join(expr.split()))] = ( + [e for e in names.split(",") if e], + int(count) if count.strip() else 1) computed_bad = [] mined = miner.hits_computed() + seen_counts = collections.Counter( + (path, " ".join(expr.split())) for expr, path, _ in mined) for expr, path, line_no in mined: key = (path, " ".join(expr.split())) if key not in declared: @@ -177,14 +181,25 @@ def main(): computed_bad.append( f"{path}:{line_no} builds a hint name from `{expr}` and is not listed") continue - for name in declared[key]: + for name in declared[key][0]: if name in known or any(fnmatch.fnmatch(name, p) for p in patterns): continue computed_bad.append( f"{path}:{line_no} expands to {name}, which the catalog does not describe") - mined_keys = {(path, " ".join(expr.split())) for expr, path, _ in mined} - for path, expr in sorted(set(declared) - mined_keys): - computed_bad.append(f"{path} is listed for `{expr}`, which no longer builds a hint name") + # Two calls in one file can read the same expression -- a second + # getArg(hintAndMarker[0], ...) over a different table -- and the key alone + # cannot tell them apart, so the newcomer would be validated against the old + # table's expansions and its own hints would never be checked. The declared + # occurrence count is what notices; the accounting line has to be revisited + # for the second call, which is the point. + for key, expected in sorted(declared.items()): + actual = seen_counts.get(key, 0) + if actual != expected[1]: + path, expr = key + computed_bad.append( + f"{path} is listed once for `{expr}` covering {expected[1]} call(s), " + f"but {actual} were mined -- list the expansions of every one and " + f"record the count as `#{actual}`") if computed_bad: print("check-build-hint-catalog: computed hint names are unaccounted for:", file=sys.stderr) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 041486ebcb7..300bad3c3f0 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2204,7 +2204,28 @@ private String readIfPresent(String path) { } } - /// The name a Kotlin `import ... as Alias` gives an annotation, or null. + /// Whether a live import brings `simple` in from the build hints package. + /// + /// Either the type by name or the package on demand. Without this the bare + /// `@Build` of some other library counts as ours. + static boolean importsAnnotation(String source, String simple, boolean kotlin) { + String pkg = "com.codename1.annotations.buildhints."; + for (String needle : new String[] {pkg + simple, pkg + "*"}) { + int at = nextMarker(source, needle, 0, kotlin); + while (at >= 0) { + int after = at + needle.length(); + boolean whole = needle.endsWith("*") + || after >= source.length() || !continuesAName(source.charAt(after)); + if (whole && precededByImport(source, at)) { + return true; + } + at = nextMarker(source, needle, after, kotlin); + } + } + return false; + } + + /// The name a Kotlin `import ... as Alias` gives an annotation, or null. /// The name a Kotlin `import ... as Alias` gives an annotation, or null. /// /// Kotlin lets a file rename what it imports, and then the annotation never /// appears under its own name anywhere in the source. Missing that reads the @@ -2289,16 +2310,22 @@ static void collectAnnotationOwnedHints(String source, java.util.Map markerList = new java.util.ArrayList(); + if (imported) { + markerList.add("@" + simple); + } + markerList.add("@com.codename1.annotations.buildhints." + simple); + if (alias != null) { + markerList.add("@" + alias); + } + String[] markers = markerList.toArray(new String[markerList.size()]); boolean found = false; for (int m = 0; m < markers.length && !found; m++) { // Found by walking the source rather than by indexOf, so a diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 86190f2541d..b6ace82457e 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -186,7 +186,8 @@ public void annotationsAreFoundInSourceBeforeTheProjectIsBuilt() { */ @Test public void valuesContainingSeparatorsDoNotCreatePhantomOwnership() { - String src = "@Android(xpermissions = \"\")\n" + String src = "import com.codename1.annotations.buildhints.*;\n" + + "@Android(xpermissions = \"\")\n" + "public class MyApp {}\n"; java.util.Map owned = new java.util.HashMap<>(); CodenameOneSettings.collectAnnotationOwnedHints(src, owned); @@ -203,7 +204,8 @@ public void valuesContainingSeparatorsDoNotCreatePhantomOwnership() { */ @Test public void commentsInsideAnAnnotationDoNotBreakOwnership() { - String src = "@Ios(/* required for issue ( */ teamId = \"T\")\n" + String src = "import com.codename1.annotations.buildhints.*;\n" + + "@Ios(/* required for issue ( */ teamId = \"T\")\n" + "public class MyApp {}\n"; java.util.Map owned = new java.util.HashMap<>(); CodenameOneSettings.collectAnnotationOwnedHints(src, owned); @@ -212,7 +214,8 @@ public void commentsInsideAnAnnotationDoNotBreakOwnership() { @Test public void lineCommentsAndCharLiteralsDoNotBreakOwnership() { - String src = "@Ios(\n" + String src = "import com.codename1.annotations.buildhints.*;\n" + + "@Ios(\n" + " // a stray ) in a line comment\n" + " teamId = \"T\",\n" + " urlScheme = \"x\")\n" @@ -222,7 +225,8 @@ public void lineCommentsAndCharLiteralsDoNotBreakOwnership() { assertEquals("@Ios(teamId)", owned.get("ios.teamId")); assertEquals("@Ios(urlScheme)", owned.get("ios.urlScheme")); - String withChar = "@Android(xpermissions = \"a\") // ')'\npublic class MyApp {}\n"; + String withChar = "import com.codename1.annotations.buildhints.*;\n" + + "@Android(xpermissions = \"a\") // ')'\npublic class MyApp {}\n"; java.util.Map owned2 = new java.util.HashMap<>(); CodenameOneSettings.collectAnnotationOwnedHints(withChar, owned2); assertEquals("@Android(xpermissions)", owned2.get("android.xpermissions")); @@ -244,7 +248,8 @@ public void fullyQualifiedAnnotationsAreRecognized() { /** `@Ios` must not match `@IosPrivacy`, which is a different annotation. */ @Test public void aSimpleNameDoesNotMatchALongerAnnotation() { - String src = "@IosPrivacy(cameraUsageDescription = \"why\")\n" + String src = "import com.codename1.annotations.buildhints.*;\n" + + "@IosPrivacy(cameraUsageDescription = \"why\")\n" + "public class MyApp {}\n"; java.util.Map owned = new java.util.HashMap<>(); CodenameOneSettings.collectAnnotationOwnedHints(src, owned); @@ -315,7 +320,8 @@ public void aBlockCommentOrStringIsNotOwnership() { /// And the real one is still found when a commented-out copy precedes it. @Test public void aLiveAnnotationAfterACommentedOneIsStillFound() { - String src = "// @Ios(teamId = \"OLD\")\n" + String src = "import com.codename1.annotations.buildhints.*;\n" + + "// @Ios(teamId = \"OLD\")\n" + "@Ios(teamId = \"ABCDE12345\")\n" + "public class MyApp {}\n"; java.util.Map out = new java.util.HashMap(); @@ -363,7 +369,8 @@ public void aBareAnnotationDoesNotAdoptTheNextCall() { /// A comment between the name and its own argument list is still its own. @Test public void anAnnotationsOwnArgumentListIsStillFoundAcrossAComment() { - String src = "@Ios /* why */ (teamId = \"ABCDE12345\")\nclass MyApp\n"; + String src = "import com.codename1.annotations.buildhints.*;\n" + + "@Ios /* why */ (teamId = \"ABCDE12345\")\nclass MyApp\n"; java.util.Map out = new java.util.HashMap(); CodenameOneSettings.collectAnnotationOwnedHints(src, out); assertEquals("@Ios(teamId)", out.get("ios.teamId")); @@ -386,7 +393,8 @@ public void theShortCaptureRecordSpellingIsAnAliasOfTheLongOne() { /// after it -- so the hint read as unowned and Add wrote the duplicate. @Test public void aTripleQuotedStringDoesNotSwallowTheAnnotation() { - String src = "val doc = \"\"\"quoted \" text\"\"\"\n" + String src = "import com.codename1.annotations.buildhints.*\n" + + "val doc = \"\"\"quoted \" text\"\"\"\n" + "@Ios(teamId = \"ABCDE12345\")\n" + "class MyApp\n"; java.util.Map out = new java.util.HashMap(); @@ -409,7 +417,8 @@ public void anAnnotationInsideATripleQuotedStringIsNotOwnership() { /// after it. @Test public void anEscapedQuoteRunDoesNotCloseAJavaTextBlock() { - String src = "String doc = \"\"\"\n" + String src = "import com.codename1.annotations.buildhints.*;\n" + + "String doc = \"\"\"\n" + " a \\\"\"\" b\n" + " \"\"\";\n" + "@Ios(teamId = \"ABCDE12345\")\n" @@ -424,11 +433,52 @@ public void anEscapedQuoteRunDoesNotCloseAJavaTextBlock() { /// Java's rule here would keep scanning and swallow the annotation. @Test public void aQuoteRunClosesAKotlinRawStringAtItsLastThree() { - String src = "val doc = \"\"\"a\"\"\"\"\n" + String src = "import com.codename1.annotations.buildhints.*\n" + + "val doc = \"\"\"a\"\"\"\"\n" + "@Ios(teamId = \"ABCDE12345\")\n" + "class MyApp\n"; java.util.Map out = new java.util.HashMap(); CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); assertEquals("@Ios(teamId)", out.get("ios.teamId")); } + + /// `@Build` and `@Android` are ordinary enough names that another library's + /// annotation with a matching attribute would be read as ownership -- and + /// Settings would then hide the editor for a hint the processor never emits. + /// The simple name only counts when an import makes it ours. + @Test + public void anUnrelatedAnnotationOfTheSameNameIsNotOwnership() { + String src = "import com.example.other.Ios;\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "public class MyApp {}\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out); + assertNull(out.get("ios.teamId")); + } + + /// A wildcard import counts, and so does the fully qualified spelling, which + /// needs no import at all. + @Test + public void aWildcardImportAndTheQualifiedSpellingBothCount() { + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints( + "import com.codename1.annotations.buildhints.*;\n" + + "@Ios(teamId = \"T\")\npublic class MyApp {}\n", out); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + + out.clear(); + CodenameOneSettings.collectAnnotationOwnedHints( + "@com.codename1.annotations.buildhints.Ios(teamId = \"T\")\n" + + "public class MyApp {}\n", out); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } + + /// A commented-out import does not bring the name in. + @Test + public void aCommentedOutImportDoesNotCount() { + assertFalse(CodenameOneSettings.importsAnnotation( + "// import com.codename1.annotations.buildhints.Ios;\n", "Ios", false)); + assertTrue(CodenameOneSettings.importsAnnotation( + "import com.codename1.annotations.buildhints.Ios;\n", "Ios", false)); + } } From 7e8e7e23b2bea87c1f94e664650bbdf4fd023672 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:50:14 +0300 Subject: [PATCH 035/115] Run the docs checks when the table's inputs change Generating the build hint table instead of committing it took away a trigger nobody had to think about: while the table was checked in, editing the catalog produced a diff under docs/ and the documentation workflow ran on it for free. Now a change to maven/build-hint-catalog or to the renderer changes what the guide contains without touching a file under docs/ at all, so the Asciidoctor lint, Vale, and the HTML and PDF build never saw it -- and a malformed table would have merged and surfaced in the release documentation build. Both paths are added to the workflow trigger AND to the paths-filter the steps are gated on. Triggering alone is not enough: the HTML and PDF build and the steps beside it are conditional on that filter, so they would have started and skipped. The render step also moves ahead of everything that reads the guide rather than sitting just before the lint -- the image and snippet checks read it too. It is unconditional, because every one of those steps is not, and asciidoctor reports a missing include as an error, so a future step inserted above it fails loudly rather than rendering a guide with the table quietly absent. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/developer-guide-docs.yml | 30 ++++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/developer-guide-docs.yml b/.github/workflows/developer-guide-docs.yml index 6527072ab2e..34c2541398e 100644 --- a/.github/workflows/developer-guide-docs.yml +++ b/.github/workflows/developer-guide-docs.yml @@ -8,6 +8,14 @@ on: - 'docs/demos/**' - 'scripts/developer-guide/**' - '.github/workflows/developer-guide-docs.yml' + # The build hint table is rendered from the catalog rather than committed, + # so a change to either the catalog or the renderer changes what this guide + # contains without touching a single file under docs/. While the table was + # checked in that edit showed up as a docs diff and triggered this workflow + # for free; generating it on the fly took that away, and a malformed table + # would then have merged and surfaced in the release documentation build. + - 'maven/build-hint-catalog/**' + - 'scripts/gen-build-hint-table.sh' release: types: [published] workflow_dispatch: @@ -39,6 +47,11 @@ jobs: - 'scripts/developer-guide/migrate-inline-guide-snippets.py' - 'scripts/developer-guide/validate-guide-snippets.py' - '.github/workflows/developer-guide-docs.yml' + # Triggering the workflow is not enough on its own: the HTML and PDF + # build and the steps beside it are gated on this filter, so the + # inputs the table is rendered from have to satisfy it too. + - 'maven/build-hint-catalog/**' + - 'scripts/gen-build-hint-table.sh' workflow: - '.github/workflows/developer-guide-docs.yml' @@ -48,6 +61,15 @@ jobs: distribution: 'temurin' java-version: '17' + # Before anything that reads the guide. The build hint table is rendered + # from maven/build-hint-catalog rather than checked in, so the lint, the + # image and snippet checks, the HTML and PDF build and Vale all need it on + # disk -- and asciidoctor reports a missing include as an error, so getting + # this order wrong fails loudly rather than dropping the table quietly. + # Unconditional, because every one of those steps is not. + - name: Render the build hint table + run: scripts/gen-build-hint-table.sh + - name: Install local Codename One Maven artifacts if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true' run: | @@ -216,14 +238,6 @@ jobs: run: | gem install --no-document asciidoctor asciidoctor-pdf rouge - # The build hint table is rendered from maven/build-hint-catalog rather than - # checked in, so every step below that reads the guide -- the lint, the HTML - # and PDF build, Vale -- needs it on disk first. Generating it here is also - # what makes it impossible for the table to disagree with the catalog: there - # is no committed copy for an edit to survive in. - - name: Render the build hint table - run: scripts/gen-build-hint-table.sh - - name: Run Asciidoctor lint run: | set -euo pipefail From 186a75cfbbfdd767edd09cdf7ebde20a5c6bcc39 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:05:08 +0300 Subject: [PATCH 036/115] Accept what the runtime accepts, and ask Maven where the sources are Three from the same review. A closed domain rejected spellings the runtime honours. IOSImplementation.installNativeTheme compares against flat, liquid and iphone alongside the catalogued values, AndroidImplementation against material and holo, so Settings told a developer that a working configuration was invalid and then refused to save the edit -- and the migration refused those values as outside the domain, which is exactly what a project old enough to be carrying a legacy spelling would have hit. They are recorded as valueAliases, deliberately NOT as domain values: an alias must not become an enum constant, because two constants for one behaviour is an API that asks a question with no right answer. Validation and migration both go through canonicalValue, so `flat` saves and migrates to IosThemeMode.IOS7, while the picklist and the annotation still offer one spelling per concept. `import ...Ios as BuildIos` puts BuildIos in scope and NOT Ios, so counting it as a simple-name import attributed another library's @Ios to us -- the same misattribution the import check had just been added to prevent. An import with an `as` clause now contributes only its alias marker. The orphan check hard-coded src/main/java. A module may add generated-sources or replace the conventional root, and Kotlin does not require a file to be named after the class it declares, so a live class could be read as orphaned -- and then silently dropped, taking its hints with it and suppressing the placement error that would have explained it. It now asks Maven for the compile source roots, which is where generated and Kotlin roots already are, and looks for the file name the COMPILER recorded in the SourceFile attribute rather than one derived from the class name. That is the one reliable link back from a class to its source, and it is what makes the Kotlin case answerable at all. ClassScanner records it; AnnotatedClass exposes it. Unknown roots, or a class compiled without debug information, still answer "has a source", because the only thing this decides is whether to IGNORE a class. Co-Authored-By: Claude Opus 5 (1M context) --- .../codename1/build/shared/BuildHints.java | 57 ++++++++++++++++ .../build/shared/BuildHintsAndroid.java | 4 ++ .../codename1/build/shared/BuildHintsIos.java | 5 ++ .../maven/MigrateBuildHintsMojo.java | 16 +++-- .../maven/ProcessAnnotationsMojo.java | 7 +- .../maven/annotations/AnnotatedClass.java | 12 ++++ .../maven/annotations/ClassScanner.java | 14 +++- .../maven/annotations/ProcessorContext.java | 25 +++++++ .../BuildHintAnnotationProcessor.java | 67 ++++++++++++------- .../MigrateBuildHintsPropertyParsingTest.java | 14 ++++ .../settings/CodenameOneSettings.java | 28 +++++++- .../settings/BuildHintCatalogTest.java | 58 ++++++++++++++++ 12 files changed, 274 insertions(+), 33 deletions(-) diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHints.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHints.java index 980cca3bbcd..b2517111d71 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHints.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHints.java @@ -210,6 +210,7 @@ public static final class Hint { private HintType type = HintType.STRING; private String enumName; private final List values = new ArrayList(); + private final Map valueAliases = new LinkedHashMap(); private final List valueLabels = new ArrayList(); private String def; private String separator; @@ -259,6 +260,36 @@ public Hint type(HintType t) { return this; } + /** + * Extra wire values the runtime accepts for an already-declared domain, + * each mapped to the canonical value it means. + * + *

Deliberately separate from {@link #values}: these do NOT become enum + * constants, because two constants for one behaviour is an API that asks + * a question with no right answer. They exist so that validation accepts + * what the runtime accepts — {@code ios.themeMode=flat} and + * {@code and.themeMode=material} are real, documented spellings, and + * rejecting them told a developer their working configuration was + * invalid — and so a migration can render one as the canonical + * constant rather than refusing it.

+ * + * @param pairs alias then canonical, repeated + */ + public Hint valueAliases(String... pairs) { + if (pairs.length % 2 != 0) { + throw new IllegalArgumentException( + "Build hint " + name + ": valueAliases takes alias/canonical pairs"); + } + for (int i = 0; i < pairs.length; i += 2) { + if (!values.contains(pairs[i + 1])) { + throw new IllegalArgumentException("Build hint " + name + " aliases " + + pairs[i] + " to " + pairs[i + 1] + ", which is not in its domain"); + } + valueAliases.put(pairs[i], pairs[i + 1]); + } + return this; + } + /** * Declares a closed value domain. Values are in wire form, i.e. * exactly what the builder compares against, never the enum constant @@ -356,6 +387,32 @@ public Hint doc(String text) { public HintType type() { return type; } public String enumName() { return enumName; } public List values() { return Collections.unmodifiableList(values); } + + /** Accepted spellings that are not their own value, alias to canonical. */ + public Map valueAliases() { + return Collections.unmodifiableMap(valueAliases); + } + + /** + * The canonical form of {@code value}, or null when the domain does not + * accept it. Case-insensitive, matching every reader of these hints. + */ + public String canonicalValue(String value) { + if (value == null) { + return null; + } + for (String allowed : values) { + if (allowed.equalsIgnoreCase(value)) { + return allowed; + } + } + for (Map.Entry e : valueAliases.entrySet()) { + if (e.getKey().equalsIgnoreCase(value)) { + return e.getValue(); + } + } + return null; + } public List valueLabels() { return Collections.unmodifiableList(valueLabels); } public String def() { return def; } public String separator() { return separator; } diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java index 1e4c10d7916..0896b9cc1d0 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java @@ -75,6 +75,10 @@ static void register(List h) { h.add(new Hint("and.themeMode") .annotatedAs(HintGroup.ANDROID, "themeMode") .values("AndroidThemeMode", "auto", "modern", "hololight", "legacy") + // AndroidImplementation.installNativeTheme compares against these + // too; see the note on ios.themeMode about why they are not + // constants. + .valueAliases("material", "modern", "holo", "hololight") .platform("android") .consumedBy("AndroidGradleBuilder") .doc("`auto`, `modern` / `material`, `hololight` (default for existing apps), `legacy`. `auto` " diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java index 86928b783d9..925964b81a8 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java @@ -1037,6 +1037,11 @@ static void register(List h) { h.add(new Hint("ios.themeMode") .annotatedAs(HintGroup.IOS, "themeMode") .values("IosThemeMode", "auto", "modern", "ios7", "legacy") + // Spellings IOSImplementation.installNativeTheme accepts for the + // same two themes. Not enum constants: one behaviour, one + // constant, or the annotation asks a question with no right + // answer. + .valueAliases("flat", "ios7", "liquid", "modern", "iphone", "legacy") .platform("ios") .consumedBy("IPhoneBuilder") .doc("`auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the existing iOS 7 " diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 7803ac3c92c..2196771f035 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -589,13 +589,15 @@ String toSourceLiteral(BuildHints.Hint hint, String value, boolean kotlin) { } catch (NumberFormatException ex) { return null; } - case ENUM: - for (String allowed : hint.values()) { - if (allowed.equalsIgnoreCase(v.trim())) { - return hint.enumName() + "." + enumConstant(allowed); - } - } - return null; + case ENUM: { + // Canonicalised, so a documented spelling that is not its own + // constant migrates to the constant it means -- ios.themeMode=flat + // becomes IosThemeMode.IOS7 -- rather than being refused as + // outside the domain, which is what an existing project setting a + // legacy spelling would have hit. + String canonical = hint.canonicalValue(v.trim()); + return canonical == null ? null : hint.enumName() + "." + enumConstant(canonical); + } case STRING_LIST: { String sep = hint.separator(); if (sep == null || sep.length() == 0) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java index 6d2cdb575b9..fc211049bb3 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java @@ -106,7 +106,12 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException } ProcessorContext ctx = new ProcessorContext(outputDirectory, stubSourceDirectory, - index, getLog(), getCN1ProjectDir(), rawProjectSettings(), mainClassBinaryName()); + index, getLog(), getCN1ProjectDir(), rawProjectSettings(), mainClassBinaryName(), + // The roots Maven is actually compiling, so a processor asking + // whether a class still has a source is not guessing at the + // layout. Kotlin and generated-source roots are in here too, + // because build-helper and the Kotlin plugin add them. + project == null ? null : project.getCompileSourceRoots()); // start() for (Iterator it = processors.iterator(); it.hasNext(); ) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/AnnotatedClass.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/AnnotatedClass.java index acb914cb5cd..920be84baa6 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/AnnotatedClass.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/AnnotatedClass.java @@ -42,6 +42,7 @@ public final class AnnotatedClass { private final String internalName; + private String sourceFile; private final String superInternalName; private final List interfaceInternalNames; private final int access; @@ -106,6 +107,17 @@ private static Set collectAllDescriptors( } /// JVM internal name (`com/example/ProfileForm`). + /// The source file name the compiler recorded, or null when the class was + /// compiled without debug information. + /// + /// The name only, never a path. It is the one reliable link back from a + /// class to its source: Kotlin lets a file's name and directory differ from + /// the class it declares, so deriving the file from the class name is a + /// guess and this is not. + public String getSourceFile() { return sourceFile; } + + void setSourceFile(String sourceFile) { this.sourceFile = sourceFile; } + public String getInternalName() { return internalName; } /// Dotted binary name (`com.example.ProfileForm`). diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ClassScanner.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ClassScanner.java index 427b71fc4a2..3740790be33 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ClassScanner.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ClassScanner.java @@ -147,11 +147,23 @@ private static final class Collector extends ClassVisitor { } AnnotatedClass build() { - return new AnnotatedClass( + AnnotatedClass out = new AnnotatedClass( internalName, superInternalName, interfaces, access, classAnnotations, methods, fields, source); + out.setSourceFile(sourceFile); + return out; } + /// The SourceFile attribute, which is the only reliable link from a + /// compiled class back to the file that declared it -- Kotlin does not + /// require the two to share a name. + @Override + public void visitSource(String sourceFileName, String debug) { + this.sourceFile = sourceFileName; + } + + private String sourceFile; + @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfacesArr) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ProcessorContext.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ProcessorContext.java index 254e85743b1..e67bb168a65 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ProcessorContext.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ProcessorContext.java @@ -62,6 +62,7 @@ public final class ProcessorContext { private final File projectDir; private final Properties projectSettings; private final String mainClassBinaryName; + private final List compileSourceRoots; public ProcessorContext(File outputClassDir, File stubSourceDir, Map classIndex, Log log) { @@ -78,6 +79,20 @@ public ProcessorContext(File outputClassDir, File stubSourceDir, Map classIndex, Log log, File projectDir, Properties projectSettings, String mainClassBinaryName) { + this(outputClassDir, stubSourceDir, classIndex, log, projectDir, projectSettings, + mainClassBinaryName, null); + } + + /// Adds the module's configured compile source roots. + /// + /// Passed in rather than guessed at from the project directory: a module may + /// add `generated-sources`, or a Kotlin root, or replace the conventional one + /// altogether, and a processor that assumes `src/main/java` would decide a + /// perfectly live class has no source. + public ProcessorContext(File outputClassDir, File stubSourceDir, + Map classIndex, Log log, + File projectDir, Properties projectSettings, + String mainClassBinaryName, List compileSourceRoots) { this.outputClassDir = outputClassDir; this.stubSourceDir = stubSourceDir; this.classIndex = classIndex == null @@ -87,8 +102,18 @@ public ProcessorContext(File outputClassDir, File stubSourceDir, this.projectDir = projectDir; this.projectSettings = projectSettings; this.mainClassBinaryName = mainClassBinaryName; + this.compileSourceRoots = compileSourceRoots == null + ? Collections.emptyList() + : Collections.unmodifiableList(new ArrayList(compileSourceRoots)); } + /// The module's configured compile source roots, empty when unknown. + /// + /// Empty means "not told", never "there are none": a caller deciding whether + /// a class still has a source has to treat the two differently, or an + /// unfamiliar layout looks exactly like a deleted file. + public List getCompileSourceRoots() { return compileSourceRoots; } + /// The Codename One project directory -- the one holding /// `codenameone_settings.properties` -- or null when it could not be found. public File getProjectDir() { return projectDir; } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 5abc926ce41..5eaa7bb1834 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -209,42 +209,63 @@ private static boolean isMainClass(AnnotatedClass cls, ProcessorContext ctx) { return main != null && main.equals(cls.getBinaryName()); } - /// Whether a source file for `cls` still exists under the project. + /// Whether a source file for `cls` still exists under the module. /// - /// Answered "yes" whenever the question cannot actually be put: no project - /// directory, or no source tree under it to search. Absence of a source tree - /// is not evidence that a class is orphaned -- it means this is a layout the - /// lookup does not know, and dropping annotations on that basis would apply - /// none of a project's hints while reporting nothing. + /// Answered "yes" whenever the question cannot actually be put, because the + /// only thing this decides is whether to IGNORE an annotated class, and + /// ignoring a live one applies none of its hints and reports nothing. Only a + /// class this can positively show has no source is treated as an orphan. + /// + /// Searched by the file name the compiler recorded, anywhere under the + /// module's configured source roots, rather than by the package path. Both + /// halves matter: a module may add `generated-sources` or replace + /// `src/main/java` outright, and Kotlin lets a file's name and directory + /// differ from the class it declares, so a package-path lookup would call a + /// perfectly live class orphaned. private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx) { - File dir = ctx.getProjectDir(); - if (dir == null) { + List roots = ctx.getCompileSourceRoots(); + if (roots == null || roots.isEmpty()) { + return true; + } + String sourceFile = cls.getSourceFile(); + if (sourceFile == null || sourceFile.length() == 0) { + // Compiled without debug information; nothing to look for. return true; } - // The outermost class owns the file: Outer$Inner lives in Outer.java. - String binary = cls.getBinaryName(); - int nested = binary.indexOf('$'); - if (nested >= 0) { - binary = binary.substring(0, nested); - } - String rel = binary.replace('.', File.separatorChar); - String[] roots = {"src" + File.separator + "main" + File.separator + "java", - "src" + File.separator + "main" + File.separator + "kotlin", - "src"}; - String[] extensions = {".java", ".kt"}; boolean sawARoot = false; for (String root : roots) { - if (!new File(dir, root).isDirectory()) { + File dir = new File(root); + if (!dir.isDirectory()) { continue; } sawARoot = true; - for (String ext : extensions) { - if (new File(dir, root + File.separator + rel + ext).isFile()) { + if (containsFileNamed(dir, sourceFile, 0)) { + return true; + } + } + return !sawARoot; + } + + /// Whether `name` exists anywhere under `dir`. Depth-limited, because this + /// runs on every annotated class and a source tree is not a search index. + private static boolean containsFileNamed(File dir, String name, int depth) { + if (depth > 24) { + return false; + } + File[] children = dir.listFiles(); + if (children == null) { + return false; + } + for (File f : children) { + if (f.isFile()) { + if (f.getName().equals(name)) { return true; } + } else if (f.isDirectory() && containsFileNamed(f, name, depth + 1)) { + return true; } } - return !sawARoot; + return false; } /// Build hints configure the application, so they belong on the class the /// Build hints configure the application, so they belong on the class the diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 5ab19fdb443..89bb5321f4c 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -155,4 +155,18 @@ public void aUnicodeEscapeInAKeyIsDecoded() { public void aMalformedUnicodeEscapeIsNotDecoded() { assertEquals("a.uZZZZb", MigrateBuildHintsMojo.propertyKeyOf("a.\\uZZZZb=1")); } + + /// A documented spelling that is not its own constant migrates to the one it + /// means, rather than being refused as outside the domain -- which is what an + /// existing project setting a legacy spelling would have hit. + @Test + public void anAcceptedSpellingMigratesToTheConstantItMeans() { + MigrateBuildHintsMojo mojo = new MigrateBuildHintsMojo(); + com.codename1.build.shared.BuildHints.Hint ios = + com.codename1.build.shared.BuildHints.byName("ios.themeMode"); + assertEquals("IosThemeMode.IOS7", mojo.toSourceLiteral(ios, "flat", false)); + assertEquals("IosThemeMode.MODERN", mojo.toSourceLiteral(ios, "liquid", false)); + assertEquals("IosThemeMode.MODERN", mojo.toSourceLiteral(ios, "modern", false)); + assertNull(mojo.toSourceLiteral(ios, "nonsense", false)); + } } diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 300bad3c3f0..422be6495e1 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -819,7 +819,18 @@ private boolean isValidHintValue(BuildHintMetadata meta, String value) { // A closed value domain is the one case where a wrong value is certain to // be wrong: the builder compares against these strings and silently uses // its default when it recognises none of them. + // + // Through the catalog's own canonicalisation rather than the picklist, + // because a domain can accept spellings that are not offered as choices: + // ios.themeMode=flat and and.themeMode=material are what the runtime + // compares against, and rejecting them told a developer that a working + // configuration was invalid and then refused to save the edit. if (!meta.values().isEmpty()) { + com.codename1.build.shared.BuildHints.Hint hint = + com.codename1.build.shared.BuildHints.byName(meta.name()); + if (hint != null && !hint.values().isEmpty()) { + return hint.canonicalValue(v) != null; + } for (String allowed : meta.values()) { if (allowed.equalsIgnoreCase(v)) { return true; @@ -2216,7 +2227,11 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin) { int after = at + needle.length(); boolean whole = needle.endsWith("*") || after >= source.length() || !continuesAName(source.charAt(after)); - if (whole && precededByImport(source, at)) { + // `import ...Ios as BuildIos` does NOT put Ios in scope -- it puts + // BuildIos there, and the file is then free to import someone + // else's Ios. Counting the aliased import as a simple-name import + // attributed that other annotation to us. + if (whole && precededByImport(source, at) && !hasAsClause(source, after)) { return true; } at = nextMarker(source, needle, after, kotlin); @@ -2225,6 +2240,17 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin) { return false; } + /// Whether an `as` rename follows the import target at `after`. + private static boolean hasAsClause(String source, int after) { + int i = after; + while (i < source.length() && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { + i++; + } + return source.regionMatches(i, "as", 0, 2) + && i + 2 < source.length() + && !continuesAName(source.charAt(i + 2)); + } + /// The name a Kotlin `import ... as Alias` gives an annotation, or null. /// The name a Kotlin `import ... as Alias` gives an annotation, or null. /// /// Kotlin lets a file rename what it imports, and then the annotation never diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index b6ace82457e..c344253e04c 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -481,4 +481,62 @@ public void aCommentedOutImportDoesNotCount() { assertTrue(CodenameOneSettings.importsAnnotation( "import com.codename1.annotations.buildhints.Ios;\n", "Ios", false)); } + + /// The runtime accepts spellings the picklist does not offer: + /// IOSImplementation.installNativeTheme compares against `flat` and `liquid`, + /// AndroidImplementation against `material` and `holo`. Rejecting them told a + /// developer a working configuration was invalid and refused the edit. + @Test + public void documentedThemeSpellingsAreAccepted() { + com.codename1.build.shared.BuildHints.Hint ios = + com.codename1.build.shared.BuildHints.byName("ios.themeMode"); + assertEquals("ios7", ios.canonicalValue("flat")); + assertEquals("modern", ios.canonicalValue("liquid")); + assertEquals("legacy", ios.canonicalValue("iphone")); + assertEquals("modern", ios.canonicalValue("MODERN")); + assertNull(ios.canonicalValue("nonsense")); + + com.codename1.build.shared.BuildHints.Hint and = + com.codename1.build.shared.BuildHints.byName("and.themeMode"); + assertEquals("modern", and.canonicalValue("material")); + assertEquals("hololight", and.canonicalValue("holo")); + assertNull(and.canonicalValue("nonsense")); + } + + /// An alias does not become a picklist choice or an enum constant: one + /// behaviour, one constant, or the annotation asks a question with no right + /// answer. + @Test + public void anAcceptedSpellingIsNotOfferedAsAChoice() { + com.codename1.build.shared.BuildHints.Hint ios = + com.codename1.build.shared.BuildHints.byName("ios.themeMode"); + assertFalse(ios.values().contains("flat")); + assertFalse(ios.values().contains("liquid")); + } + + /// `import ...Ios as BuildIos` puts BuildIos in scope, NOT Ios. Counting it + /// as a simple-name import attributed another library's @Ios to us. + @Test + public void anAliasedImportDoesNotBringTheSimpleNameIntoScope() { + String src = "import com.codename1.annotations.buildhints.Ios as BuildIos\n" + + "import com.example.other.Ios\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + assertFalse(CodenameOneSettings.importsAnnotation(src, "Ios", true)); + + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); + assertNull(out.get("ios.teamId")); + } + + /// ...while the alias itself still is. + @Test + public void theAliasMarkerStillCounts() { + String src = "import com.codename1.annotations.buildhints.Ios as BuildIos\n" + + "@BuildIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } } From a83dbff176d5f355403c2fb9f92c8113dc10edb5 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:22:04 +0300 Subject: [PATCH 037/115] Identify a class by what it declares, not where it sits Three from the same review. Three more real hints had no catalog row. MacNativeBuilder reads them through parseEntitlementBool(request, hint, def), so every caller passes a literal, none of the calls is an accessor, and a literal search of accessor calls walks past all of them. The miner now recognises a helper that forwards one of its own parameters to getArg/arg/booleanArg and mines that helper's CALLERS instead -- which found exactly the three named, by looking rather than by being told. Same-file only, deliberately: a private helper's name is not unique across the tree, and mining calls to a same-named method of an unrelated class would invent hints rather than find them. The orphan check matched a SourceFile name anywhere under any source root, so moving a class to another package without a clean left an orphan that the NEW file answered for -- App.java satisfying the lookup for the old package's App.class -- and the stale class stayed, failing the placement check on every incremental build. The package is part of the match now, read from the file rather than inferred from its directory, since Kotlin does not require the two to agree. Settings had the same conventional-roots assumption on the other side, and falling through to null there let the caller trust a stale manifest again -- the bug the source scan exists to prevent, reappearing for anyone whose layout is merely unusual. It searches the project for a file that DECLARES the class now: package statement plus a class or object declaration, so a configured root or a Kotlin file named after something else is found anyway. Bounded in depth, in queue length and in how many files it will open, and target/ and build/ are skipped so a compiled copy of the same source cannot answer for it. Co-Authored-By: Claude Opus 5 (1M context) --- .../build/shared/BuildHintsApple.java | 30 +++++ .../BuildHintAnnotationProcessor.java | 68 ++++++++++- scripts/build_hint_miner.py | 57 +++++++++ .../settings/CodenameOneSettings.java | 110 +++++++++++++++++- .../settings/BuildHintCatalogTest.java | 35 ++++++ 5 files changed, 293 insertions(+), 7 deletions(-) diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java index be49840b404..a5cfc130d1b 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsApple.java @@ -99,6 +99,36 @@ static void register(List h) { .platform("mac") .consumedBy("CN1BuildMojo", "IPhoneBuilder", "MacNativeBuilder")); + h.add(new Hint("macNative.entitlements.device.camera") + .group(HintGroup.MAC_NATIVE) + .type(HintType.BOOLEAN) + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Sandboxed Mac Native builds only. Toggles " + + "`com.apple.security.device.camera`. Defaults to whether the app sets " + + "`ios.NSCameraUsageDescription`, so an app that asks for the camera gets " + + "the entitlement without naming it twice.")); + + h.add(new Hint("macNative.entitlements.device.microphone") + .group(HintGroup.MAC_NATIVE) + .type(HintType.BOOLEAN) + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Sandboxed Mac Native builds only. Toggles " + + "`com.apple.security.device.microphone`. Defaults to whether the app sets " + + "`ios.NSMicrophoneUsageDescription`.")); + + h.add(new Hint("macNative.entitlements.personalInformation.calendars") + .group(HintGroup.MAC_NATIVE) + .type(HintType.BOOLEAN) + .platform("mac") + .consumedBy("MacNativeBuilder") + .doc("Sandboxed Mac Native builds only. Toggles " + + "`com.apple.security.personal-information.calendars`, which gates all " + + "EventKit access. Defaults to whether the app sets any calendar or " + + "reminder usage description, including the write-only and reminders-only " + + "ones.")); + h.add(new Hint("macNative.entitlements.extra") .group(HintGroup.MAC_NATIVE) .type(HintType.STRING) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 5eaa7bb1834..7fb7b43df05 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -232,6 +232,7 @@ private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx // Compiled without debug information; nothing to look for. return true; } + String pkg = packageOf(cls.getBinaryName()); boolean sawARoot = false; for (String root : roots) { File dir = new File(root); @@ -239,16 +240,30 @@ private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx continue; } sawARoot = true; - if (containsFileNamed(dir, sourceFile, 0)) { + if (declaresPackage(dir, sourceFile, pkg, 0)) { return true; } } return !sawARoot; } - /// Whether `name` exists anywhere under `dir`. Depth-limited, because this - /// runs on every annotated class and a source tree is not a search index. - private static boolean containsFileNamed(File dir, String name, int depth) { + private static String packageOf(String binaryName) { + int dot = binaryName.lastIndexOf('.'); + return dot < 0 ? "" : binaryName.substring(0, dot); + } + + /// Whether a file called `name` declaring package `pkg` exists under `dir`. + /// + /// The package matters as well as the name: moving a class to another package + /// without a clean leaves an orphan whose SourceFile is, say, App.java, and + /// the NEW App.java would otherwise answer for it -- so the stale class stays + /// and fails the placement check on every incremental build, which is the bug + /// this whole guard exists to prevent. + /// + /// The package is read from the file rather than inferred from its directory, + /// because Kotlin does not require the two to agree. Depth-limited: this runs + /// per annotated class and a source tree is not a search index. + private static boolean declaresPackage(File dir, String name, String pkg, int depth) { if (depth > 24) { return false; } @@ -258,16 +273,57 @@ private static boolean containsFileNamed(File dir, String name, int depth) { } for (File f : children) { if (f.isFile()) { - if (f.getName().equals(name)) { + if (f.getName().equals(name) && pkg.equals(declaredPackage(f))) { return true; } - } else if (f.isDirectory() && containsFileNamed(f, name, depth + 1)) { + } else if (f.isDirectory() && declaresPackage(f, name, pkg, depth + 1)) { return true; } } return false; } + /// The package a source file declares, or "" for the default package. + /// + /// Read from the head of the file only -- a package declaration cannot appear + /// after the first type -- and "" on any read failure, which errs towards + /// treating the file as a match and so towards keeping a class. + private static String declaredPackage(File f) { + BufferedReader r = null; + try { + r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); + String line; + int read = 0; + while ((line = r.readLine()) != null && read++ < 200) { + String t = line.trim(); + if (!t.startsWith("package")) { + continue; + } + String rest = t.substring("package".length()); + if (rest.length() == 0 || Character.isJavaIdentifierPart(rest.charAt(0))) { + continue; + } + rest = rest.trim(); + int end = rest.indexOf(';'); + if (end >= 0) { + rest = rest.substring(0, end); + } + return rest.trim(); + } + return ""; + } catch (IOException ex) { + return ""; + } finally { + if (r != null) { + try { + r.close(); + } catch (IOException ignored) { + // read-only stream + } + } + } + } + /// Build hints configure the application, so they belong on the class the /// Build hints configure the application, so they belong on the class the /// project already names as its entry point. /// diff --git a/scripts/build_hint_miner.py b/scripts/build_hint_miner.py index d40cf3abfdc..0240fed4cfd 100644 --- a/scripts/build_hint_miner.py +++ b/scripts/build_hint_miner.py @@ -47,6 +47,49 @@ CONST_DECL = re.compile( r'\bstatic\s+final\s+String\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*"((?:[^"\\]|\\.)*)"\s*;') +# A method declaration, so a helper that forwards one of its own parameters to an +# accessor can be recognised and its CALLERS mined instead. MacNativeBuilder's +# parseEntitlementBool(request, hint, def) is the shape: every caller passes a +# literal, none of them is a getArg, and the hints were invisible to a literal +# search of accessor calls alone. +METHOD_DECL = re.compile( + r'\b(?:public|private|protected|static|final|synchronized|\s)+' + r'[A-Za-z_][A-Za-z0-9_<>\[\], .?]*\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*' + r'(?:throws [A-Za-z0-9_., ]+)?\{') + +FORWARDS = re.compile(r'\b(?:getArg|booleanArg)\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,' + r'|(?= 2 and arg.startswith('"') and arg.endswith('"')): + continue + key, _ = read_literal(arg, 1) + if key is None or not re.fullmatch(r'[A-Za-z][A-Za-z0-9_.!]*', key): + continue + line = text.count("\n", 0, m.start()) + 1 + hits[key].append(("", rel, line)) for pat, prefixed in OPENERS: for m in pat.finditer(text): # position of the char just after the opening quote of arg 1 diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 422be6495e1..50acc69f756 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2191,12 +2191,120 @@ private java.util.Map annotationOwnedHintsFromSource() { return out; } } - // No source file found. Distinct from "found and declares nothing", and + // Those three roots are a convention, not the truth: a module can add a + // source root, and Kotlin does not require a file to be named after the + // class it declares. Falling straight through to null here let the caller + // trust a stale manifest -- which is the bug the source scan was added to + // fix, reappearing for anyone whose layout is merely unusual. So look + // properly before giving up. + String found = findMainClassSource(binding.projectDir(), main, pkg); + if (found != null) { + collectAnnotationOwnedHints(found, out, lastSourceWasKotlin); + return out; + } + // Genuinely no source. Distinct from "found and declares nothing", and // the caller has to tell them apart before letting the source overrule // the manifest. return null; } + /// Set by findMainClassSource, since it decides the language by what it finds. + private boolean lastSourceWasKotlin; + + /// The text of the file declaring `main` in `pkg`, found by searching, or null. + /// + /// Walks the project for a `.java` or `.kt` file that declares the class, so + /// a configured source root or a Kotlin file whose name differs from its + /// class is found anyway. Bounded in depth and in how many files it will + /// open, because this runs when Settings opens a project and a source tree is + /// not a search index. + private String findMainClassSource(String projectDir, String main, String pkg) { + if (projectDir == null || main == null || main.isEmpty()) { + return null; + } + java.util.List queue = new java.util.ArrayList<>(); + queue.add(projectDir); + int opened = 0; + for (int i = 0; i < queue.size() && i < 4000; i++) { + String dir = queue.get(i); + String[] children; + try { + children = FileSystemStorage.getInstance().listFiles(ProjectIO.fsUrl(dir)); + } catch (Exception ex) { + continue; + } + if (children == null) { + continue; + } + for (String child : children) { + String name = child.endsWith("/") ? child.substring(0, child.length() - 1) : child; + String path = dir + "/" + name; + if (FileSystemStorage.getInstance().isDirectory(ProjectIO.fsUrl(path))) { + // target/ holds compiled copies of these same sources; walking + // it would find a generated stub and call it the main class. + if (!"target".equals(name) && !"build".equals(name) && !name.startsWith(".")) { + queue.add(path); + } + continue; + } + boolean kotlin = name.endsWith(".kt"); + if (!kotlin && !name.endsWith(".java")) { + continue; + } + // Cheap filter first: a file declaring the class almost always + // carries its name. + if (!name.equals(main + (kotlin ? ".kt" : ".java")) && opened > 200) { + continue; + } + String text = readIfPresent(path); + opened++; + if (text == null || !declaresClass(text, main, pkg)) { + continue; + } + lastSourceWasKotlin = kotlin; + return text; + } + } + return null; + } + + /// Whether `text` declares `main` in `pkg`, judged by the package statement + /// and a `class`/`object` declaration rather than by where the file sits. + static boolean declaresClass(String text, String main, String pkg) { + String declaredPkg = ""; + for (String line : com.codename1.util.StringUtil.tokenize(text, "\n")) { + String t = line.trim(); + if (t.startsWith("package") && t.length() > 7 + && !continuesAName(t.charAt(7))) { + declaredPkg = t.substring(7).trim(); + int semi = declaredPkg.indexOf(';'); + if (semi >= 0) { + declaredPkg = declaredPkg.substring(0, semi); + } + declaredPkg = declaredPkg.trim(); + break; + } + } + if (!(pkg == null || pkg.isEmpty() ? "" : pkg).equals(declaredPkg)) { + return false; + } + for (String keyword : new String[]{"class ", "object "}) { + int at = text.indexOf(keyword); + while (at >= 0) { + int start = at + keyword.length(); + int end = start; + while (end < text.length() && continuesAName(text.charAt(end))) { + end++; + } + if (text.substring(start, end).equals(main)) { + return true; + } + at = text.indexOf(keyword, start); + } + } + return false; + } + private String readIfPresent(String path) { InputStream in = null; try { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index c344253e04c..b22e156cbe8 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -539,4 +539,39 @@ public void theAliasMarkerStillCounts() { CodenameOneSettings.collectAnnotationOwnedHints(src, out, true); assertEquals("@Ios(teamId)", out.get("ios.teamId")); } + + /// Kotlin does not require a file to be named after the class it declares, so + /// the declaration is what identifies the main class -- not the filename and + /// not the directory. + @Test + public void aClassIsIdentifiedByItsDeclarationNotItsFile() { + String kt = "package com.example\n\nclass Helper\n\nclass MyApp\n"; + assertTrue(CodenameOneSettings.declaresClass(kt, "MyApp", "com.example")); + assertTrue(CodenameOneSettings.declaresClass(kt, "Helper", "com.example")); + assertFalse(CodenameOneSettings.declaresClass(kt, "Other", "com.example")); + } + + /// A same-named class in another package is a different class. Accepting it + /// is how a moved class makes its own orphan look alive. + @Test + public void thePackageIsPartOfTheIdentity() { + String src = "package com.example.moved;\npublic class MyApp {}\n"; + assertFalse(CodenameOneSettings.declaresClass(src, "MyApp", "com.example")); + assertTrue(CodenameOneSettings.declaresClass(src, "MyApp", "com.example.moved")); + } + + /// A Kotlin `object` declares a type too. + @Test + public void anObjectDeclarationCounts() { + assertTrue(CodenameOneSettings.declaresClass( + "package com.example\nobject MyApp\n", "MyApp", "com.example")); + } + + /// The default package is "" on both sides rather than null on one. + @Test + public void theDefaultPackageMatches() { + assertTrue(CodenameOneSettings.declaresClass("public class MyApp {}\n", "MyApp", null)); + assertTrue(CodenameOneSettings.declaresClass("public class MyApp {}\n", "MyApp", "")); + assertFalse(CodenameOneSettings.declaresClass("public class MyApp {}\n", "MyApp", "com.x")); + } } From 7c6d94999b224636732efa473651b26eb5f2768b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:36:30 +0300 Subject: [PATCH 038/115] Describe the plist keys we inject, individually Two from the same review. IPhoneBuilder builds a hint name into a local -- `"ios." + privacyKey` -- and passes the local to getArg, so the accessor's argument is a bare variable and read as forwarding while the name is assembled two lines up. That is the fourth route by which a literal reaches an accessor without sitting at one. The miner recognises a local assembled from a literal now and reports it, which surfaced this site and no other. The finding underneath it is sharper than "uncatalogued", and worth stating exactly: ios.NSBluetoothAlwaysUsageDescription, its Peripheral twin and ios.NSSpeechRecognitionUsageDescription were matched by the dynamic family ios.NS*UsageDescription, so the gate was right that they were "described" and they still had no annotation, no documentation row and no editor entry. That family exists so an app can set an ARBITRARY Apple key. A key the platform feature catalog injects is a known one, and known keys get described individually -- they are now, as @IosPrivacy attributes like the other fourteen. So the new cross-check requires a CONCRETE row for every injected plist key and deliberately does not accept the dynamic pattern. My first version did accept it and was therefore inert; the negative test passing is what showed that, after I went looking for why an injected unknown key did not fail the gate. Second finding: matching a source file by name and package alone kept a stale class when a Kotlin type is renamed in place without renaming its file, or when one type is deleted from a file holding several -- the survivor answered for it, the orphan stayed, and the placement check failed every incremental build. The file must declare the type now: class, interface, enum, object or record. Unreadable still answers yes, as everywhere else in this guard, because what it decides is whether to IGNORE an annotated class. Co-Authored-By: Claude Opus 5 (1M context) --- .../annotations/buildhints/IosPrivacy.java | 13 +++ .../impl/javase/BuildHintCatalogDefaults.java | 15 +++ .../shared/BuildHintAnnotationBinding.java | 3 + .../codename1/build/shared/BuildHintsIos.java | 25 ++++ .../BuildHintAnnotationProcessor.java | 107 +++++++++++++----- scripts/build-hint-computed-sites.txt | 1 + scripts/build_hint_miner.py | 19 ++++ scripts/check-build-hint-catalog.py | 33 ++++++ 8 files changed, 190 insertions(+), 26 deletions(-) diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/IosPrivacy.java b/CodenameOne/src/com/codename1/annotations/buildhints/IosPrivacy.java index 0375ac2116a..d74f61a1c1e 100644 --- a/CodenameOne/src/com/codename1/annotations/buildhints/IosPrivacy.java +++ b/CodenameOne/src/com/codename1/annotations/buildhints/IosPrivacy.java @@ -43,6 +43,15 @@ @Target(ElementType.TYPE) public @interface IosPrivacy { + /// Why the app uses Bluetooth. Supplied automatically when the app references + /// `com.codename1.bluetooth`; set it to say something more specific than the + /// default. + String bluetoothAlwaysUsageDescription() default ""; + + /// The pre-iOS 13 spelling of the Bluetooth usage description, supplied and + /// overridable on the same terms. + String bluetoothPeripheralUsageDescription() default ""; + /// The text iOS shows when the app first asks for the calendars full access. It /// becomes the `NSCalendarsFullAccessUsageDescription` key in `Info.plist`. The /// App Store rejects an app that touches this resource without one. @@ -109,4 +118,8 @@ /// `NSRemindersUsageDescription` key in `Info.plist`. The App Store rejects an /// app that touches this resource without one. String remindersUsageDescription() default ""; + + /// Why the app sends speech for recognition. Supplied automatically when the + /// app references the speech APIs; set it to say something more specific. + String speechRecognitionUsageDescription() default ""; } diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java index c1f57e69aef..ce475f99c2b 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java @@ -45,6 +45,16 @@ static void register() { java.util.Set handWritten = BuildHintSchemaDefaults.declaredHints(); set("{{@IosPrivacy}}.label", "iOS Privacy Strings"); + if (!handWritten.contains("ios.NSBluetoothAlwaysUsageDescription")) { + set("{{#IosPrivacy#ios.NSBluetoothAlwaysUsageDescription}}.label", "Bluetooth always usage description"); + set("{{#IosPrivacy#ios.NSBluetoothAlwaysUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSBluetoothAlwaysUsageDescription}}.description", "Why the app uses Bluetooth. Supplied automatically when the app references `com.codename1.bluetooth`; set it to say something more specific than the default."); + } + if (!handWritten.contains("ios.NSBluetoothPeripheralUsageDescription")) { + set("{{#IosPrivacy#ios.NSBluetoothPeripheralUsageDescription}}.label", "Bluetooth peripheral usage description"); + set("{{#IosPrivacy#ios.NSBluetoothPeripheralUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSBluetoothPeripheralUsageDescription}}.description", "The pre-iOS 13 spelling of the Bluetooth usage description, supplied and overridable on the same terms."); + } if (!handWritten.contains("ios.NSCalendarsFullAccessUsageDescription")) { set("{{#IosPrivacy#ios.NSCalendarsFullAccessUsageDescription}}.label", "Calendars full access usage description"); set("{{#IosPrivacy#ios.NSCalendarsFullAccessUsageDescription}}.type", "TextField"); @@ -97,6 +107,11 @@ static void register() { set("{{#IosPrivacy#ios.NSRemindersUsageDescription}}.label", "Reminders usage description"); set("{{#IosPrivacy#ios.NSRemindersUsageDescription}}.type", "TextField"); } + if (!handWritten.contains("ios.NSSpeechRecognitionUsageDescription")) { + set("{{#IosPrivacy#ios.NSSpeechRecognitionUsageDescription}}.label", "Speech recognition usage description"); + set("{{#IosPrivacy#ios.NSSpeechRecognitionUsageDescription}}.type", "TextField"); + set("{{#IosPrivacy#ios.NSSpeechRecognitionUsageDescription}}.description", "Why the app sends speech for recognition. Supplied automatically when the app references the speech APIs; set it to say something more specific."); + } set("{{@Ios}}.label", "iOS"); if (!handWritten.contains("ios.add_libs")) { diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java index 73e3e6081ed..d0589f0b62c 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintAnnotationBinding.java @@ -51,6 +51,8 @@ public final class BuildHintAnnotationBinding { static { DESCRIPTORS.put("IosPrivacy", "Lcom/codename1/annotations/buildhints/IosPrivacy;"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#bluetoothAlwaysUsageDescription", "ios.NSBluetoothAlwaysUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#bluetoothPeripheralUsageDescription", "ios.NSBluetoothPeripheralUsageDescription"); HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#calendarsFullAccessUsageDescription", "ios.NSCalendarsFullAccessUsageDescription"); HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#calendarsUsageDescription", "ios.NSCalendarsUsageDescription"); HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#calendarsWriteOnlyAccessUsageDescription", "ios.NSCalendarsWriteOnlyAccessUsageDescription"); @@ -64,6 +66,7 @@ public final class BuildHintAnnotationBinding { HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#microphoneUsageDescription", "ios.NSMicrophoneUsageDescription"); HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#remindersFullAccessUsageDescription", "ios.NSRemindersFullAccessUsageDescription"); HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#remindersUsageDescription", "ios.NSRemindersUsageDescription"); + HINTS.put("Lcom/codename1/annotations/buildhints/IosPrivacy;#speechRecognitionUsageDescription", "ios.NSSpeechRecognitionUsageDescription"); DESCRIPTORS.put("Ios", "Lcom/codename1/annotations/buildhints/Ios;"); HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#addLibs", "ios.add_libs"); HINTS.put("Lcom/codename1/annotations/buildhints/Ios;#applicationQueriesSchemes", "ios.applicationQueriesSchemes"); diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java index 925964b81a8..6071b99dcf1 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsIos.java @@ -76,12 +76,37 @@ static void register(List h) { .platform("ios") .consumedBy("IPhoneBuilder", "MacNativeBuilder")); + h.add(new Hint("ios.NSBluetoothAlwaysUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "bluetoothAlwaysUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Why the app uses Bluetooth. Supplied automatically when the app references " + + "`com.codename1.bluetooth`; set it to say something more specific than " + + "the default.")); + + h.add(new Hint("ios.NSBluetoothPeripheralUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "bluetoothPeripheralUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("The pre-iOS 13 spelling of the Bluetooth usage description, supplied and " + + "overridable on the same terms.")); + h.add(new Hint("ios.NSCameraUsageDescription") .annotatedAs(HintGroup.IOS_PRIVACY, "cameraUsageDescription") .type(HintType.STRING) .platform("ios") .consumedBy("MacNativeBuilder")); + h.add(new Hint("ios.NSSpeechRecognitionUsageDescription") + .annotatedAs(HintGroup.IOS_PRIVACY, "speechRecognitionUsageDescription") + .type(HintType.STRING) + .platform("ios") + .consumedBy("IPhoneBuilder") + .doc("Why the app sends speech for recognition. Supplied automatically when the " + + "app references the speech APIs; set it to say something more specific.")); + h.add(new Hint("ios.NSHealthShareUsageDescription") .annotatedAs(HintGroup.IOS_PRIVACY, "healthShareUsageDescription") .type(HintType.STRING) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 7fb7b43df05..b99f51fb2d8 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -233,6 +233,7 @@ private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx return true; } String pkg = packageOf(cls.getBinaryName()); + String simpleName = simpleNameOf(cls.getBinaryName()); boolean sawARoot = false; for (String root : roots) { File dir = new File(root); @@ -240,7 +241,7 @@ private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx continue; } sawARoot = true; - if (declaresPackage(dir, sourceFile, pkg, 0)) { + if (declaresPackage(dir, sourceFile, pkg, simpleName, 0)) { return true; } } @@ -252,6 +253,11 @@ private static String packageOf(String binaryName) { return dot < 0 ? "" : binaryName.substring(0, dot); } + private static String simpleNameOf(String binaryName) { + int dot = binaryName.lastIndexOf('.'); + return dot < 0 ? binaryName : binaryName.substring(dot + 1); + } + /// Whether a file called `name` declaring package `pkg` exists under `dir`. /// /// The package matters as well as the name: moving a class to another package @@ -263,7 +269,8 @@ private static String packageOf(String binaryName) { /// The package is read from the file rather than inferred from its directory, /// because Kotlin does not require the two to agree. Depth-limited: this runs /// per annotated class and a source tree is not a search index. - private static boolean declaresPackage(File dir, String name, String pkg, int depth) { + private static boolean declaresPackage(File dir, String name, String pkg, String simple, + int depth) { if (depth > 24) { return false; } @@ -273,46 +280,73 @@ private static boolean declaresPackage(File dir, String name, String pkg, int de } for (File f : children) { if (f.isFile()) { - if (f.getName().equals(name) && pkg.equals(declaredPackage(f))) { + if (f.getName().equals(name) && matches(f, pkg, simple)) { return true; } - } else if (f.isDirectory() && declaresPackage(f, name, pkg, depth + 1)) { + } else if (f.isDirectory() && declaresPackage(f, name, pkg, simple, depth + 1)) { return true; } } return false; } - /// The package a source file declares, or "" for the default package. + /// Whether `f` declares type `simple` in package `pkg`. /// - /// Read from the head of the file only -- a package declaration cannot appear - /// after the first type -- and "" on any read failure, which errs towards - /// treating the file as a match and so towards keeping a class. - private static String declaredPackage(File f) { + /// The declaration is checked, not just the file's name and package. Kotlin + /// lets a class be renamed without renaming its file, and one file can hold + /// several types, so the surviving source would otherwise answer for the + /// class that used to be in it -- keeping a stale annotation owner and + /// failing the placement check on every incremental build, which is the very + /// thing this guard was added to stop. + private static boolean matches(File f, String pkg, String simple) { + String text = readHead(f); + if (text == null) { + // Unreadable: answer yes, as everywhere else here, because the only + // thing this decides is whether to IGNORE an annotated class. + return true; + } + return pkg.equals(declaredPackageIn(text)) && declaresType(text, simple); + } + + /// Whether `text` declares a type called `simple`. + static boolean declaresType(String text, String simple) { + String[] keywords = {"class ", "interface ", "enum ", "object ", "record "}; + for (String keyword : keywords) { + int at = text.indexOf(keyword); + while (at >= 0) { + int start = at + keyword.length(); + int end = start; + while (end < text.length() + && Character.isJavaIdentifierPart(text.charAt(end))) { + end++; + } + if (text.substring(start, end).equals(simple) + && (at == 0 || !Character.isJavaIdentifierPart(text.charAt(at - 1)))) { + return true; + } + at = text.indexOf(keyword, start); + } + } + return false; + } + + /// The first 400 lines of `f`, or null when it cannot be read. + /// + /// Bounded because this runs per annotated class, and both the package + /// statement and the type declarations of interest are at the top. + private static String readHead(File f) { BufferedReader r = null; try { r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); + StringBuilder sb = new StringBuilder(); String line; int read = 0; - while ((line = r.readLine()) != null && read++ < 200) { - String t = line.trim(); - if (!t.startsWith("package")) { - continue; - } - String rest = t.substring("package".length()); - if (rest.length() == 0 || Character.isJavaIdentifierPart(rest.charAt(0))) { - continue; - } - rest = rest.trim(); - int end = rest.indexOf(';'); - if (end >= 0) { - rest = rest.substring(0, end); - } - return rest.trim(); + while ((line = r.readLine()) != null && read++ < 400) { + sb.append(line).append('\n'); } - return ""; + return sb.toString(); } catch (IOException ex) { - return ""; + return null; } finally { if (r != null) { try { @@ -324,6 +358,27 @@ private static String declaredPackage(File f) { } } + /// The package `text` declares, or "" for the default package. + static String declaredPackageIn(String text) { + for (String line : text.split("\n")) { + String t = line.trim(); + if (!t.startsWith("package")) { + continue; + } + String rest = t.substring("package".length()); + if (rest.length() == 0 || Character.isJavaIdentifierPart(rest.charAt(0))) { + continue; + } + rest = rest.trim(); + int end = rest.indexOf(';'); + if (end >= 0) { + rest = rest.substring(0, end); + } + return rest.trim(); + } + return ""; + } + /// Build hints configure the application, so they belong on the class the /// Build hints configure the application, so they belong on the class the /// project already names as its entry point. /// diff --git a/scripts/build-hint-computed-sites.txt b/scripts/build-hint-computed-sites.txt index 454cded0e92..8b8ba7c5d77 100644 --- a/scripts/build-hint-computed-sites.txt +++ b/scripts/build-hint-computed-sites.txt @@ -39,3 +39,4 @@ maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/MacNativeBui maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java|"ios.entitlements.com.apple.developer.healthkit." + suffix|ios.entitlements.com.apple.developer.healthkit.access maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/WatchNativeBuilder.java|"ios.entitlements.com.apple.developer.healthkit." + canonicalKey|ios.entitlements.com.apple.developer.healthkit.access maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java|hintAndMarker[0]|ios.wallet.nonuiImportsInject,ios.wallet.statusInject,ios.wallet.passEntriesInject,ios.wallet.remotePassEntriesInject,ios.wallet.generateRequestInject,ios.wallet.generateResponseInject,ios.wallet.uiImportsInject,ios.wallet.uiViewDidLoadInject,ios.wallet.uiAuthRequestInject,ios.wallet.uiAuthResponseInject +maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java|"ios." + privacyKey|ios.NSBluetoothAlwaysUsageDescription,ios.NSBluetoothPeripheralUsageDescription,ios.NSCameraUsageDescription,ios.NSMicrophoneUsageDescription,ios.NSSpeechRecognitionUsageDescription diff --git a/scripts/build_hint_miner.py b/scripts/build_hint_miner.py index 0240fed4cfd..3c770991212 100644 --- a/scripts/build_hint_miner.py +++ b/scripts/build_hint_miner.py @@ -57,6 +57,14 @@ r'[A-Za-z_][A-Za-z0-9_<>\[\], .?]*\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*' r'(?:throws [A-Za-z0-9_., ]+)?\{') +# A local that is ASSEMBLED from a literal and then passed to an accessor: +# String requestKey = "ios." + privacyKey; +# request.getArg(requestKey, null) +# The accessor's argument is a bare variable, so it reads as a forwarder, but the +# name is built two lines up and no literal ever sits at a getArg call. +LOCAL_ASSEMBLY = re.compile( + r'\b(?:String\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([^;\n]*"[^;\n]*)\s*;') + FORWARDS = re.compile(r'\b(?:getArg|booleanArg)\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,' r'|(?", rel, line)) + elif re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', expr) and expr in assembled: + # A bare variable, but one built from a literal in this file. + computed.append({"expr": " ".join(assembled[expr].split()), + "file": rel, "line": line}) elif '"' in expr or '[' in expr: # The name is BUILT here, or read out of a table -- either way no # literal at a getArg call names it, so the literal pass cannot diff --git a/scripts/check-build-hint-catalog.py b/scripts/check-build-hint-catalog.py index f430b6e76cf..a45d4dc8c38 100755 --- a/scripts/check-build-hint-catalog.py +++ b/scripts/check-build-hint-catalog.py @@ -211,6 +211,39 @@ def main(): file=sys.stderr) return 1 + # The plist keys the platform feature catalog injects become ios. build + # hints that an app may override. They reach getArg through a locally built + # name, so the computed-site accounting covers them -- but that accounting + # lists today's expansions, and a NEW entry there would change neither the + # expression nor the call count. This reads the other catalog directly, so + # adding a plist entry without a build hint row fails here instead. + # + # A CONCRETE row, not a dynamic pattern. ios.NS*UsageDescription exists so an + # app can set an arbitrary Apple key, and it was quietly absorbing these -- + # which is why the gate reported success while ios.NSBluetoothAlwaysUsage- + # Description had no annotation, no doc row and no editor entry. A key WE + # inject is a known one, and known keys get described individually. + plist_src = os.path.join( + ROOT, "maven/platform-feature-catalog/src/main/java/com/codename1/build/shared", + "PlatformFeatureCatalog.java") + plist_bad = [] + if os.path.exists(plist_src): + with open(plist_src, encoding="utf-8") as fh: + for m in re.finditer(r'\.iosPlist\(\s*"([^"\\]+)"', fh.read()): + name = "ios." + m.group(1) + if name in known: + continue + plist_bad.append(name) + if plist_bad: + print("check-build-hint-catalog: the platform feature catalog injects plist entries the " + "build hint catalog does not describe:", file=sys.stderr) + for name in sorted(set(plist_bad)): + print(f" {name} injected by PlatformFeatureCatalog.iosPlist", file=sys.stderr) + print("\nAn app can override any of these with a build hint of that name, so each one " + "needs a catalog row like the other ios.NS*UsageDescription entries.", + file=sys.stderr) + return 1 + print(f"check-build-hint-catalog: {len(miner.hits)} hints read, all described by the " f"catalog; {len(declared)} computed site(s) accounted for" + (f" ({len(baseline)} baselined)" if baseline else "")) From d29969e4c65fdc3e48f2f9769f4f9c7169f3584b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:47:30 +0300 Subject: [PATCH 039/115] Ask about the main class, not about every class file Three from the same review, all fallout from the orphan filter. A nested type's binary name is Main$Wrong and no source declares a type spelled that way, so the search found nothing, the class was read as an orphan and dropped, and the placement check that would have said "annotations belong on the main class" never ran. The build then succeeded with the requested hints silently absent -- the exact failure this feature exists to remove, reintroduced by the guard meant to protect it. The lookup uses the outermost simple name now, which is the type a source file actually declares. There is a test putting an annotation on a nested class and asserting the placement error. CN1BuildMojo's guard rescanned every class file without that filter, so a stale annotated .class left by a rename without a clean failed every build with "no manifest was produced", naming a class already deleted. Rather than duplicate the orphan test there, the guard now asks about the MAIN class alone: the processor honours no other class, so no other class is evidence that annotations went unprocessed. That is both narrower and immune to orphans by construction. It still scans everything when the project names no main class, since then there is nothing more specific to ask. The Settings search decided as it walked, so its budget could be spent on unrelated files before reaching the one Kotlin source whose name differs from its class -- the only layout that search exists for, and so exactly the case it dropped. Files are collected first and examined in two passes: every file named after the class, then Kotlin files that are not. Java is excluded from the second pass because a public Java type must be named after its file, so a differently named .java cannot declare an application's main class. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/CN1BuildMojo.java | 56 ++++++++++++++++++- .../BuildHintAnnotationProcessor.java | 13 ++++- .../BuildHintAnnotationProcessorTest.java | 20 +++++++ .../settings/CodenameOneSettings.java | 50 +++++++++++------ 4 files changed, 119 insertions(+), 20 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index 023aad8a7b7..533fdf13618 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -2746,7 +2746,7 @@ private void mergeAnnotationBuildHints(Properties target, List classpath // does not add an execution to a project's POM, so an app that adopts the // annotations without binding process-annotations compiles cleanly and // ships with every annotated hint missing. Refuse rather than build that. - String annotated = classCarryingBuildHintAnnotations(classpathElements); + String annotated = classCarryingBuildHintAnnotations(classpathElements, expectedMain); if (annotated != null) { throw new MojoFailureException(annotated + " carries build hint annotations, but " + (stale == null @@ -2869,14 +2869,36 @@ private com.codename1.maven.annotations.AnnotatedClass readClass(File element, S } /** - * The first application class found carrying a build hint annotation, or null. + * The application class carrying a build hint annotation, or null. * *

Read straight out of the class file's annotation table rather than from * source, so it sees exactly what the compiler emitted.

+ * + *

The MAIN class alone when the project names one. The processor honours + * no other class, so no other class is evidence that annotations went + * unprocessed — and scanning them all meant a stale annotated + * {@code .class}, left behind by a rename without a clean, failed every build + * with "no manifest was produced" for a class the developer had already + * deleted. The processor ignores that orphan; so does this. Only when the + * project names no main class at all does this fall back to scanning + * everything, since then there is nothing more specific to ask about.

*/ - private String classCarryingBuildHintAnnotations(List classpathElements) { + private String classCarryingBuildHintAnnotations(List classpathElements, + String expectedMain) { java.util.Collection descriptors = com.codename1.build.shared.BuildHintAnnotationBinding.descriptors(); + if (expectedMain != null) { + for (String element : classpathElements) { + try { + if (mainClassCarriesAnnotation(new File(element), expectedMain, descriptors)) { + return expectedMain; + } + } catch (IOException | RuntimeException ex) { + getLog().debug("cn1: could not read " + expectedMain + " from " + element, ex); + } + } + return null; + } for (String element : classpathElements) { File f = new File(element); if (f.isDirectory()) { @@ -2899,6 +2921,34 @@ private String classCarryingBuildHintAnnotations(List classpathElements) return null; } + /** Whether the named class, read from this classpath element, is annotated. */ + private boolean mainClassCarriesAnnotation(File element, String binaryName, + java.util.Collection descriptors) + throws IOException { + String path = binaryName.replace('.', '/') + ".class"; + if (element.isDirectory()) { + File f = new File(element, path.replace('/', File.separatorChar)); + if (!f.isFile()) { + return false; + } + try (InputStream in = new FileInputStream(f)) { + return carriesBuildHintAnnotation(in, descriptors); + } + } + if (element.isFile() && element.getName().endsWith(".jar")) { + try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(element)) { + java.util.zip.ZipEntry entry = zip.getEntry(path); + if (entry == null) { + return false; + } + try (InputStream in = zip.getInputStream(entry)) { + return carriesBuildHintAnnotation(in, descriptors); + } + } + } + return false; + } + private String findAnnotatedClassInJar(File jar, java.util.Collection descriptors) { try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(jar)) { java.util.Enumeration entries = zip.entries(); diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index b99f51fb2d8..6d2de936f36 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -253,9 +253,20 @@ private static String packageOf(String binaryName) { return dot < 0 ? "" : binaryName.substring(0, dot); } + /// The OUTERMOST simple name, so a nested type is looked for by the type its + /// file actually declares. + /// + /// A nested class's binary name is Main$Wrong, and no source declares a type + /// spelled that way -- searching for it found nothing, the class was read as + /// an orphan and dropped, and the placement check that would have said + /// "annotations belong on the main class" never ran. The build then succeeded + /// with the requested hints silently absent, which is the failure this whole + /// feature exists to remove. private static String simpleNameOf(String binaryName) { int dot = binaryName.lastIndexOf('.'); - return dot < 0 ? binaryName : binaryName.substring(dot + 1); + String simple = dot < 0 ? binaryName : binaryName.substring(dot + 1); + int nested = simple.indexOf('$'); + return nested < 0 ? simple : simple.substring(0, nested); } /// Whether a file called `name` declaring package `pkg` exists under `dir`. diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 1c9dc66d71b..8816797f23c 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -294,6 +294,26 @@ public void theShortSpellingOfAnAliasedHintStillConflicts() throws Exception { assertErrorContaining(ctx, "and.captureRecord"); } + /// A nested type's binary name is Main$Wrong and no source declares a type + /// spelled that way, so looking for it found nothing and the class was + /// dropped as an orphan -- taking the placement error with it and letting the + /// build succeed with the requested hints silently absent. + @Test + public void anAnnotationOnANestedTypeIsStillReported() throws Exception { + File classes = tmp.newFolder(); + JavaSourceCompiler.compile( + JavaSourceCompiler.singleSource(MAIN, + "package com.example;\n" + + "import com.codename1.annotations.buildhints.*;\n" + + "public class MyApp {\n" + + " @Ios(teamId = \"ABCDE12345\")\n" + + " public static class Wrong {}\n" + + "}\n"), + classes, Arrays.asList(testClassesDir(), coreJar())); + ProcessorContext ctx = run(classes, settings(), MAIN, false); + assertErrorContaining(ctx, "belong on the application's main class"); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 50acc69f756..c071979c537 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2222,9 +2222,15 @@ private String findMainClassSource(String projectDir, String main, String pkg) { if (projectDir == null || main == null || main.isEmpty()) { return null; } + // Collected first, then examined in two passes. Deciding as we walk made + // the answer depend on directory order: the budget could be spent on + // unrelated files before reaching the one Kotlin source whose name + // differs from its class -- which is the only layout this fallback exists + // for, so exactly the case it would drop. + java.util.List named = new java.util.ArrayList<>(); + java.util.List others = new java.util.ArrayList<>(); java.util.List queue = new java.util.ArrayList<>(); queue.add(projectDir); - int opened = 0; for (int i = 0; i < queue.size() && i < 4000; i++) { String dir = queue.get(i); String[] children; @@ -2247,24 +2253,36 @@ private String findMainClassSource(String projectDir, String main, String pkg) { } continue; } - boolean kotlin = name.endsWith(".kt"); - if (!kotlin && !name.endsWith(".java")) { - continue; - } - // Cheap filter first: a file declaring the class almost always - // carries its name. - if (!name.equals(main + (kotlin ? ".kt" : ".java")) && opened > 200) { - continue; - } - String text = readIfPresent(path); - opened++; - if (text == null || !declaresClass(text, main, pkg)) { - continue; + if (name.equals(main + ".java") || name.equals(main + ".kt")) { + named.add(path); + } else if (name.endsWith(".kt")) { + // Only Kotlin: Java requires a public type to be named after + // its file, so a differently named .java cannot declare the + // main class of an application. + others.add(path); } - lastSourceWasKotlin = kotlin; - return text; } } + String hit = firstDeclaring(named, main, pkg, named.size()); + return hit != null ? hit : firstDeclaring(others, main, pkg, 400); + } + + /// The text of the first of `paths` that declares `main` in `pkg`, opening at + /// most `budget` of them. + private String firstDeclaring(java.util.List paths, String main, String pkg, + int budget) { + int opened = 0; + for (String path : paths) { + if (opened++ >= budget) { + return null; + } + String text = readIfPresent(path); + if (text == null || !declaresClass(text, main, pkg)) { + continue; + } + lastSourceWasKotlin = path.endsWith(".kt"); + return text; + } return null; } From 3235077b2578381a98ab669ceed889f72114377e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 20:02:53 +0300 Subject: [PATCH 040/115] Refuse the value we cannot read, and read declarations as code Three from the same review. A scalar with surrounding whitespace is no longer migrated. It looks like it means what it says and it does not: AndroidGradleBuilder compares android.hideStatusBar with .equals("true"), so `=true ` is false today, while other builders trim or ignore case. Trimming it into an annotation `true` would change what the app builds with and still report success, since the verification asks whether the hint came back and not what it holds. Which reading is right differs per builder, so this refuses and says why rather than picking one. A string keeps its whitespace and migrates as before -- there the space is the value. Reducing Main$Wrong to Main so its file could be found let the LIVE outer class vouch for a nested type that had been deleted, so the orphan stayed and failed the placement check on every incremental build: one silent failure swapped for a loud permanent one. The nested name has to be declared too. An unnamed segment -- Main$1, an anonymous class -- asks nothing, since no source declares one and none can carry an annotation. Both declaration checks looked for `class X` with indexOf, so a commented-out `// class Wrong` left by the edit that deleted the type vouched for its own orphan, and on the Settings side an unrelated file mentioning the main class in a comment or a string answered for it -- ownership then reads as empty and Settings offers Add for a hint the real main class annotates. The processor blanks comments and string literals before looking; Settings reuses the comment-aware walk it already had, for the package statement as well as the declaration. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 27 +++-- .../BuildHintAnnotationProcessor.java | 106 +++++++++++++++++- .../settings/CodenameOneSettings.java | 64 +++++++---- .../settings/BuildHintCatalogTest.java | 26 +++++ 4 files changed, 190 insertions(+), 33 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 2196771f035..c656a22d84a 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -205,8 +205,13 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException String literal = toSourceLiteral(hint, value, kotlinTarget); if (literal == null) { - skipped.add(e.getKey() + " = '" + value - + "' (value is outside the hint's supported set)"); + boolean padded = value != null && !value.equals(value.trim()); + skipped.add(e.getKey() + " = '" + value + "' (" + + (padded + ? "has surrounding whitespace, which builders read differently; " + + "remove it and run this again" + : "value is outside the hint's supported set") + + ")"); continue; } String annotation = hint.group().annotationSimpleName(); @@ -571,12 +576,18 @@ String toSourceLiteral(BuildHints.Hint hint, String value, boolean kotlin) { if (value == null) { return null; } - // Trimmed only where the surrounding space cannot be part of the value: - // "true ", " 24" and " modern" all mean what they say. A string does not - // get that treatment -- an ios.glAppDelegateHeader ending in a newline - // after a // comment needs that newline, and losing it comments out - // whatever the builder generates next. The verification build would not - // notice, since it checks that the key came back and not what it holds. + // A scalar with surrounding whitespace is NOT migrated at all. It looks + // like it means what it says, and it does not: AndroidGradleBuilder + // compares android.hideStatusBar with .equals("true"), so `=true ` is + // false today, while other builders trim or ignore case. Trimming it into + // an annotation `true` would change what the app builds with and report a + // successful migration, since the verification asks whether the hint came + // back and not what it holds. Which reading is right differs per builder, + // so this refuses rather than picks. + if (hint.type() != HintType.STRING && hint.type() != HintType.STRING_LIST + && !value.equals(value.trim())) { + return null; + } String v = value; switch (hint.type()) { case BOOLEAN: diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 6d2de936f36..ed4d67a4d4f 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -234,6 +234,7 @@ private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx } String pkg = packageOf(cls.getBinaryName()); String simpleName = simpleNameOf(cls.getBinaryName()); + String nestedName = nestedNameOf(cls.getBinaryName()); boolean sawARoot = false; for (String root : roots) { File dir = new File(root); @@ -241,7 +242,7 @@ private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx continue; } sawARoot = true; - if (declaresPackage(dir, sourceFile, pkg, simpleName, 0)) { + if (declaresPackage(dir, sourceFile, pkg, simpleName, nestedName, 0)) { return true; } } @@ -281,7 +282,7 @@ private static String simpleNameOf(String binaryName) { /// because Kotlin does not require the two to agree. Depth-limited: this runs /// per annotated class and a source tree is not a search index. private static boolean declaresPackage(File dir, String name, String pkg, String simple, - int depth) { + String nested, int depth) { if (depth > 24) { return false; } @@ -291,10 +292,10 @@ private static boolean declaresPackage(File dir, String name, String pkg, String } for (File f : children) { if (f.isFile()) { - if (f.getName().equals(name) && matches(f, pkg, simple)) { + if (f.getName().equals(name) && matches(f, pkg, simple, nested)) { return true; } - } else if (f.isDirectory() && declaresPackage(f, name, pkg, simple, depth + 1)) { + } else if (f.isDirectory() && declaresPackage(f, name, pkg, simple, nested, depth + 1)) { return true; } } @@ -309,18 +310,51 @@ private static boolean declaresPackage(File dir, String name, String pkg, String /// class that used to be in it -- keeping a stale annotation owner and /// failing the placement check on every incremental build, which is the very /// thing this guard was added to stop. - private static boolean matches(File f, String pkg, String simple) { + private static boolean matches(File f, String pkg, String simple, String nested) { String text = readHead(f); if (text == null) { // Unreadable: answer yes, as everywhere else here, because the only // thing this decides is whether to IGNORE an annotated class. return true; } - return pkg.equals(declaredPackageIn(text)) && declaresType(text, simple); + if (!pkg.equals(declaredPackageIn(text)) || !declaresType(text, simple)) { + return false; + } + // The nested type has to be there too. Reducing Main$Wrong to Main so the + // file can be found let the LIVE outer class vouch for a nested type that + // had been deleted, and the orphan then failed the placement check on + // every incremental build -- swapping one silent failure for a loud + // permanent one. + return nested == null || declaresType(text, nested); + } + + /// The innermost named segment of a nested binary name, or null. + /// + /// Null for an unnamed segment -- Main$1 is an anonymous class, which no + /// source declares and which cannot carry an annotation in the first place, + /// so there is nothing to look for and nothing to conclude from not finding + /// it. + private static String nestedNameOf(String binaryName) { + int nested = binaryName.lastIndexOf('$'); + if (nested < 0 || nested + 1 >= binaryName.length()) { + return null; + } + String tail = binaryName.substring(nested + 1); + for (int i = 0; i < tail.length(); i++) { + if (!Character.isDigit(tail.charAt(i))) { + return tail; + } + } + return null; } /// Whether `text` declares a type called `simple`. + /// + /// Comments and string literals are blanked first: a commented-out + /// `// class Wrong` left behind by the very edit that deleted the type would + /// otherwise vouch for its own orphan. static boolean declaresType(String text, String simple) { + text = blankNonCode(text); String[] keywords = {"class ", "interface ", "enum ", "object ", "record "}; for (String keyword : keywords) { int at = text.indexOf(keyword); @@ -341,6 +375,65 @@ static boolean declaresType(String text, String simple) { return false; } + /// `text` with every comment and string literal replaced by spaces, so a + /// declaration can be looked for without a quoted or commented mention of one + /// answering for it. Lengths and line breaks are preserved. + static String blankNonCode(String text) { + char[] out = text.toCharArray(); + int i = 0; + while (i < out.length) { + char c = out[i]; + if (c == '/' && i + 1 < out.length && out[i + 1] == '/') { + while (i < out.length && out[i] != '\n') { + out[i++] = ' '; + } + } else if (c == '/' && i + 1 < out.length && out[i + 1] == '*') { + out[i++] = ' '; + out[i++] = ' '; + while (i < out.length && !(out[i] == '*' && i + 1 < out.length + && out[i + 1] == '/')) { + if (out[i] != '\n') { + out[i] = ' '; + } + i++; + } + if (i < out.length) { + out[i++] = ' '; + } + if (i < out.length) { + out[i++] = ' '; + } + } else if (c == '"') { + boolean triple = i + 2 < out.length && out[i + 1] == '"' && out[i + 2] == '"'; + int quotes = triple ? 3 : 1; + for (int q = 0; q < quotes && i < out.length; q++) { + out[i++] = ' '; + } + while (i < out.length) { + if (!triple && out[i] == '\\' && i + 1 < out.length) { + out[i++] = ' '; + out[i++] = ' '; + continue; + } + if (out[i] == '"' && (!triple + || (i + 2 < out.length && out[i + 1] == '"' && out[i + 2] == '"'))) { + for (int q = 0; q < quotes && i < out.length; q++) { + out[i++] = ' '; + } + break; + } + if (out[i] != '\n') { + out[i] = ' '; + } + i++; + } + } else { + i++; + } + } + return new String(out); + } + /// The first 400 lines of `f`, or null when it cannot be read. /// /// Bounded because this runs per annotated class, and both the package @@ -371,6 +464,7 @@ private static String readHead(File f) { /// The package `text` declares, or "" for the default package. static String declaredPackageIn(String text) { + text = blankNonCode(text); for (String line : text.split("\n")) { String t = line.trim(); if (!t.startsWith("package")) { diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index c071979c537..df1e0eba33d 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2277,7 +2277,7 @@ private String firstDeclaring(java.util.List paths, String main, String return null; } String text = readIfPresent(path); - if (text == null || !declaresClass(text, main, pkg)) { + if (text == null || !declaresClass(text, main, pkg, path.endsWith(".kt"))) { continue; } lastSourceWasKotlin = path.endsWith(".kt"); @@ -2289,35 +2289,61 @@ private String firstDeclaring(java.util.List paths, String main, String /// Whether `text` declares `main` in `pkg`, judged by the package statement /// and a `class`/`object` declaration rather than by where the file sits. static boolean declaresClass(String text, String main, String pkg) { + return declaresClass(text, main, pkg, true); + } + + /// Whether `text` declares `main` in `pkg`, judged by the package statement + /// and a `class`/`object` declaration rather than by where the file sits. + /// + /// Found in CODE: a `// class Main` left over from an edit, or those words + /// inside a string, would otherwise make an unrelated file answer for the + /// main class -- ownership then reads as empty and Settings offers Add for a + /// hint the real main class already annotates. + static boolean declaresClass(String text, String main, String pkg, boolean kotlin) { String declaredPkg = ""; - for (String line : com.codename1.util.StringUtil.tokenize(text, "\n")) { - String t = line.trim(); - if (t.startsWith("package") && t.length() > 7 - && !continuesAName(t.charAt(7))) { - declaredPkg = t.substring(7).trim(); - int semi = declaredPkg.indexOf(';'); - if (semi >= 0) { - declaredPkg = declaredPkg.substring(0, semi); + int pkgAt = nextMarker(text, "package", 0, kotlin); + while (pkgAt >= 0) { + int after = pkgAt + "package".length(); + if (after < text.length() && !continuesAName(text.charAt(after)) + && (pkgAt == 0 || !continuesAName(text.charAt(pkgAt - 1)))) { + String rest = text.substring(after).trim(); + int cut = rest.length(); + for (int i = 0; i < rest.length(); i++) { + char c = rest.charAt(i); + if (c == ';' || c == '\n' || c == '\r') { + cut = i; + break; + } } - declaredPkg = declaredPkg.trim(); + declaredPkg = rest.substring(0, cut).trim(); break; } + pkgAt = nextMarker(text, "package", after, kotlin); } if (!(pkg == null || pkg.isEmpty() ? "" : pkg).equals(declaredPkg)) { return false; } - for (String keyword : new String[]{"class ", "object "}) { - int at = text.indexOf(keyword); + for (String keyword : new String[]{"class", "object"}) { + int at = nextMarker(text, keyword, 0, kotlin); while (at >= 0) { int start = at + keyword.length(); - int end = start; - while (end < text.length() && continuesAName(text.charAt(end))) { - end++; - } - if (text.substring(start, end).equals(main)) { - return true; + boolean wholeWord = (at == 0 || !continuesAName(text.charAt(at - 1))) + && start < text.length() && !continuesAName(text.charAt(start)); + if (wholeWord) { + int i = start; + while (i < text.length() + && (text.charAt(i) == ' ' || text.charAt(i) == '\t')) { + i++; + } + int end = i; + while (end < text.length() && continuesAName(text.charAt(end))) { + end++; + } + if (text.substring(i, end).equals(main)) { + return true; + } } - at = text.indexOf(keyword, start); + at = nextMarker(text, keyword, start, kotlin); } } return false; diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index b22e156cbe8..4105e3ca0a9 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -574,4 +574,30 @@ public void theDefaultPackageMatches() { assertTrue(CodenameOneSettings.declaresClass("public class MyApp {}\n", "MyApp", "")); assertFalse(CodenameOneSettings.declaresClass("public class MyApp {}\n", "MyApp", "com.x")); } + + /// A commented-out or quoted mention of a declaration is not a declaration. + /// An unrelated file answering for the main class reads ownership as empty, + /// and Settings then offers Add for a hint the real main class annotates. + @Test + public void aMentionOfADeclarationIsNotADeclaration() { + assertFalse(CodenameOneSettings.declaresClass( + "package com.example\n// class MyApp\n", "MyApp", "com.example", true)); + assertFalse(CodenameOneSettings.declaresClass( + "package com.example\n/* class MyApp */\n", "MyApp", "com.example", true)); + assertFalse(CodenameOneSettings.declaresClass( + "package com.example\nval s = \"class MyApp\"\n", "MyApp", "com.example", true)); + assertTrue(CodenameOneSettings.declaresClass( + "package com.example\n// class MyApp\nclass MyApp\n", "MyApp", + "com.example", true)); + } + + /// The same for a commented-out package statement, which would otherwise make + /// a default-package file claim to be in one. + @Test + public void aCommentedPackageStatementIsNotThePackage() { + assertTrue(CodenameOneSettings.declaresClass( + "// package com.example\nclass MyApp\n", "MyApp", "", true)); + assertFalse(CodenameOneSettings.declaresClass( + "// package com.example\nclass MyApp\n", "MyApp", "com.example", true)); + } } From 80e6c2a9b52bcb09f7702aa74b2febcb00f77d91 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 20:13:59 +0300 Subject: [PATCH 041/115] Find the main class by what declares it, everywhere Two from the same review, and the last two places still asking the wrong question. Settings returned from the conventional path on the file merely EXISTING. Move a Kotlin main class into a differently named file and leave the old Main.kt holding something else, and it returned that unrelated file and never reached the declaration search added for exactly this -- reporting the annotated hints as unowned, which is the state that lets Add write the duplicate. The conventional file now has to declare the class before it is accepted. cn1:migrate-build-hints had the same hard-coded three roots and filename, and aborted with "Could not find the source" on a project Maven compiles perfectly well: a module may add src/app/java, and Kotlin does not require a file to be named after its class. It asks the owning MavenProject for its compile source roots and looks for the file that DECLARES the class. The declaration test is the annotation processor's, made public and called, rather than a third copy. Two copies have already drifted apart in this change and a third would drift again; what counts as a declaration should have one answer, and it now has tests of its own -- comment, block comment, string, a commented-out declaration followed by the real one, a longer name that must not match, and that blanking preserves offsets. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 68 ++++++++++++++++++- .../BuildHintAnnotationProcessor.java | 6 +- .../BuildHintAnnotationProcessorTest.java | 35 ++++++++++ .../settings/CodenameOneSettings.java | 8 ++- 4 files changed, 112 insertions(+), 5 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index c656a22d84a..041b2014c0c 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -714,6 +714,8 @@ private String findMainClassSource(File projectDir, Properties settings) { } String path = (pkg == null ? "" : pkg.trim().replace('.', File.separatorChar) + File.separator) + main.trim(); + String simple = main.trim(); + String expectedPkg = pkg == null ? "" : pkg.trim(); String[] roots = {"src" + File.separator + "main" + File.separator + "java", "src" + File.separator + "main" + File.separator + "kotlin", "src"}; @@ -721,14 +723,78 @@ private String findMainClassSource(File projectDir, Properties settings) { for (String root : roots) { for (String ext : extensions) { File f = new File(projectDir, root + File.separator + path + ext); - if (f.isFile()) { + // Declaring the class, not merely sitting at the conventional + // path: a Kotlin main class moved into a differently named file + // can leave the old one behind holding something else. + if (f.isFile() && declares(f, expectedPkg, simple)) { return f.getAbsolutePath(); } } } + // Those three are a convention. Maven is the authority on where this + // module's sources are -- a module may add src/app/java or a Kotlin root + // -- and Kotlin does not require a file to be named after its class, so + // the file is identified by what it DECLARES. Without this the goal + // aborted with "Could not find the source" on a project Maven compiles + // perfectly well. + org.apache.maven.project.MavenProject owner = moduleAt(projectDir); + if (owner == null || owner.getCompileSourceRoots() == null) { + return null; + } + for (String root : owner.getCompileSourceRoots()) { + File dir = new File(root); + if (!dir.isDirectory()) { + continue; + } + File hit = findDeclaringFile(dir, expectedPkg, simple, 0); + if (hit != null) { + return hit.getAbsolutePath(); + } + } + return null; + } + + /// The first .java or .kt under `dir` declaring `simple` in `pkg`, or null. + private File findDeclaringFile(File dir, String pkg, String simple, int depth) { + if (depth > 24) { + return null; + } + File[] children = dir.listFiles(); + if (children == null) { + return null; + } + for (File f : children) { + if (f.isDirectory()) { + File hit = findDeclaringFile(f, pkg, simple, depth + 1); + if (hit != null) { + return hit; + } + } else if ((f.getName().endsWith(".java") || f.getName().endsWith(".kt")) + && declares(f, pkg, simple)) { + return f; + } + } return null; } + /// Whether `f` declares type `simple` in package `pkg`. + /// + /// Through the annotation processor's helpers rather than a second copy: the + /// two have already drifted apart once in this change, and "what counts as a + /// declaration" should have one answer. + private boolean declares(File f, String pkg, String simple) { + String text; + try { + text = read(f); + } catch (IOException ex) { + return false; + } + return pkg.equals(com.codename1.maven.processors.BuildHintAnnotationProcessor + .declaredPackageIn(text)) + && com.codename1.maven.processors.BuildHintAnnotationProcessor + .declaresType(text, simple); + } + /** * Splices the annotations in above the class declaration, with the import. * diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index ed4d67a4d4f..79f93a1473d 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -353,7 +353,7 @@ private static String nestedNameOf(String binaryName) { /// Comments and string literals are blanked first: a commented-out /// `// class Wrong` left behind by the very edit that deleted the type would /// otherwise vouch for its own orphan. - static boolean declaresType(String text, String simple) { + public static boolean declaresType(String text, String simple) { text = blankNonCode(text); String[] keywords = {"class ", "interface ", "enum ", "object ", "record "}; for (String keyword : keywords) { @@ -378,7 +378,7 @@ static boolean declaresType(String text, String simple) { /// `text` with every comment and string literal replaced by spaces, so a /// declaration can be looked for without a quoted or commented mention of one /// answering for it. Lengths and line breaks are preserved. - static String blankNonCode(String text) { + public static String blankNonCode(String text) { char[] out = text.toCharArray(); int i = 0; while (i < out.length) { @@ -463,7 +463,7 @@ private static String readHead(File f) { } /// The package `text` declares, or "" for the default package. - static String declaredPackageIn(String text) { + public static String declaredPackageIn(String text) { text = blankNonCode(text); for (String line : text.split("\n")) { String t = line.trim(); diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 8816797f23c..f3339030bc2 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -314,6 +314,41 @@ public void anAnnotationOnANestedTypeIsStillReported() throws Exception { assertErrorContaining(ctx, "belong on the application's main class"); } + /// What counts as a declaration has one answer, shared by the processor's + /// orphan check and the migration goal's source lookup. These pin it. + @Test + public void aDeclarationIsFoundOnlyInCode() { + assertTrue(BuildHintAnnotationProcessor.declaresType("public class MyApp {}", "MyApp")); + assertTrue(BuildHintAnnotationProcessor.declaresType("object MyApp", "MyApp")); + assertFalse(BuildHintAnnotationProcessor.declaresType("// class MyApp", "MyApp")); + assertFalse(BuildHintAnnotationProcessor.declaresType("/* class MyApp */", "MyApp")); + assertFalse(BuildHintAnnotationProcessor.declaresType("String s = \"class MyApp\";", + "MyApp")); + assertTrue(BuildHintAnnotationProcessor.declaresType( + "// class MyApp\nclass MyApp {}", "MyApp")); + assertFalse(BuildHintAnnotationProcessor.declaresType("class MyApplication {}", "MyApp")); + } + + @Test + public void thePackageIsReadFromCodeToo() { + assertEquals("com.example", + BuildHintAnnotationProcessor.declaredPackageIn("package com.example;\n")); + assertEquals("com.example", + BuildHintAnnotationProcessor.declaredPackageIn("package com.example\n")); + assertEquals("", BuildHintAnnotationProcessor.declaredPackageIn("// package com.example;")); + assertEquals("", BuildHintAnnotationProcessor.declaredPackageIn("class MyApp {}")); + } + + /// Blanking preserves length and line breaks, so an offset into the blanked + /// text still means the same place in the original. + @Test + public void blankingKeepsThePositionsIntact() { + String src = "a // b\nc /* d */ e\n"; + String blanked = BuildHintAnnotationProcessor.blankNonCode(src); + assertEquals(src.length(), blanked.length()); + assertEquals(2, blanked.split("\n", -1).length - 1); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index df1e0eba33d..4a4d7e47dfc 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2184,7 +2184,13 @@ private java.util.Map annotationOwnedHintsFromSource() { for (String root : new String[]{"/src/main/java/", "/src/main/kotlin/", "/src/"}) { String path = binding.projectDir() + root + rel + ext; String text = readIfPresent(path); - if (text == null) { + // The conventional path has to DECLARE the class, not merely + // exist. Moving a Kotlin main class into a differently named file + // can leave the old Main.kt behind holding something else, and + // returning on its mere existence skipped the search below -- + // reporting the annotated hints as unowned, which is the state + // that lets Add write the duplicate. + if (text == null || !declaresClass(text, main, pkg, ext.equals(".kt"))) { continue; } collectAnnotationOwnedHints(text, out, ext.equals(".kt")); From dc257e19281161fb555f58d769c43014610c9916 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 20:26:17 +0300 Subject: [PATCH 042/115] Migrate only a value already spelled the way the build reads it Three from the same review. Case was still being normalised. AndroidGradleBuilder compares android.hideStatusBar with .equals("true"), so `=TRUE` is false today, and installNativeTheme compares themes with .equals too, so `MODERN` is not `modern` to the runtime. Migrating either changed what the app builds with while reporting success, since the verification asks whether the hint came back and not what it holds. Which hints are read leniently differs per hint and this cannot know, so a scalar or enum value that is not already spelled canonically is refused, exactly as a whitespace-padded one now is. An int must round-trip too: 007 and +5 parse, and a builder comparing the raw string would not see the 7 or 5 this would otherwise write. Nested identity is the whole path now, each segment declared DIRECTLY in the one before it. Matching only the innermost name let an unrelated Main.B.Wrong vouch for a deleted Main.A.Wrong. Writing the test first caught that my own fix was still too loose in the other direction: searching Main's body at any depth let that same Main.B.Wrong answer for Main.Wrong, which is precisely when Main$Wrong.class IS an orphan. Braces are counted on the blanked text, so one inside a comment or a string cannot move the nesting. `class\nMain` and `class /* why */ Main` are legal in both languages, and Settings skipped only spaces and tabs -- reading the declaration as unnamed, so the file did not declare the main class, ownership came back empty, and Add wrote the duplicate. It consumes any legal separator, comments included, through the scanner it already had. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 50 ++++-- .../BuildHintAnnotationProcessor.java | 147 ++++++++++++++++-- .../MigrateBuildHintsPropertyParsingTest.java | 31 ++++ .../BuildHintAnnotationProcessorTest.java | 30 ++++ .../settings/CodenameOneSettings.java | 12 +- .../settings/BuildHintCatalogTest.java | 14 ++ 6 files changed, 253 insertions(+), 31 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 041b2014c0c..74863f95570 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -564,6 +564,19 @@ private boolean carriesBuildHintAnnotations(org.apache.maven.project.MavenProjec return false; } + /// Whether `value` is spelled exactly as the domain declares it, alias or not. + /// + /// canonicalValue ignores case because a reader might; the migration cannot + /// afford to, because a reader might not. + private static boolean exactlySpelled(BuildHints.Hint hint, String value) { + for (String allowed : hint.values()) { + if (allowed.equals(value)) { + return true; + } + } + return hint.valueAliases().containsKey(value); + } + /** * Renders a value as the Java literal for its attribute. * @@ -591,23 +604,40 @@ String toSourceLiteral(BuildHints.Hint hint, String value, boolean kotlin) { String v = value; switch (hint.type()) { case BOOLEAN: - if ("true".equalsIgnoreCase(v.trim())) return "true"; - if ("false".equalsIgnoreCase(v.trim())) return "false"; + // Exactly, not ignoring case. AndroidGradleBuilder compares + // android.hideStatusBar with .equals("true"), so `=TRUE` is false + // today, while other hints are read with equalsIgnoreCase. Which + // applies is per hint and this cannot know, so a value that is not + // already canonical is refused rather than normalised into one + // that may mean the opposite. + if ("true".equals(v)) return "true"; + if ("false".equals(v)) return "false"; return null; case INT: try { - return String.valueOf(Integer.parseInt(v.trim())); + // Round-tripped for the same reason: 007 and +5 parse, and a + // builder comparing the raw string would not see the 7 or 5 + // this would otherwise write. + String canonical = String.valueOf(Integer.parseInt(v)); + return canonical.equals(v) ? canonical : null; } catch (NumberFormatException ex) { return null; } case ENUM: { - // Canonicalised, so a documented spelling that is not its own - // constant migrates to the constant it means -- ios.themeMode=flat - // becomes IosThemeMode.IOS7 -- rather than being refused as - // outside the domain, which is what an existing project setting a - // legacy spelling would have hit. - String canonical = hint.canonicalValue(v.trim()); - return canonical == null ? null : hint.enumName() + "." + enumConstant(canonical); + // A documented spelling that is not its own constant migrates to + // the constant it means -- ios.themeMode=flat becomes + // IosThemeMode.IOS7 -- rather than being refused as outside the + // domain, which is what an existing project setting a legacy + // spelling would have hit. + // + // Case-sensitively, though: installNativeTheme compares with + // .equals, so `MODERN` is not `modern` to the runtime and + // migrating it would change the theme rather than preserve it. + String canonical = hint.canonicalValue(v); + if (canonical == null || !exactlySpelled(hint, v)) { + return null; + } + return hint.enumName() + "." + enumConstant(canonical); } case STRING_LIST: { String sep = hint.separator(); diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 79f93a1473d..921e1afb06c 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -234,7 +234,7 @@ private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx } String pkg = packageOf(cls.getBinaryName()); String simpleName = simpleNameOf(cls.getBinaryName()); - String nestedName = nestedNameOf(cls.getBinaryName()); + String[] nestedName = nestedNameOf(cls.getBinaryName()); boolean sawARoot = false; for (String root : roots) { File dir = new File(root); @@ -282,7 +282,7 @@ private static String simpleNameOf(String binaryName) { /// because Kotlin does not require the two to agree. Depth-limited: this runs /// per annotated class and a source tree is not a search index. private static boolean declaresPackage(File dir, String name, String pkg, String simple, - String nested, int depth) { + String[] nested, int depth) { if (depth > 24) { return false; } @@ -310,7 +310,7 @@ private static boolean declaresPackage(File dir, String name, String pkg, String /// class that used to be in it -- keeping a stale annotation owner and /// failing the placement check on every incremental build, which is the very /// thing this guard was added to stop. - private static boolean matches(File f, String pkg, String simple, String nested) { + private static boolean matches(File f, String pkg, String simple, String[] nested) { String text = readHead(f); if (text == null) { // Unreadable: answer yes, as everywhere else here, because the only @@ -320,12 +320,111 @@ private static boolean matches(File f, String pkg, String simple, String nested) if (!pkg.equals(declaredPackageIn(text)) || !declaresType(text, simple)) { return false; } - // The nested type has to be there too. Reducing Main$Wrong to Main so the - // file can be found let the LIVE outer class vouch for a nested type that - // had been deleted, and the orphan then failed the placement check on - // every incremental build -- swapping one silent failure for a loud - // permanent one. - return nested == null || declaresType(text, nested); + // The whole nesting PATH has to be there, in order. Checking only the + // innermost name let an unrelated Main.B.Wrong vouch for a deleted + // Main.A.Wrong, so the orphan stayed and failed the placement check on + // every incremental build. Checking only the outer class was the same bug + // one level out. + return nested == null || declaresNestedPath(text, nested); + } + + /// Whether `text` declares the chain `path` -- {"Main", "A", "Wrong"} -- each + /// inside the body of the one before it. + /// + /// Braces are counted on the blanked text, where every comment and string + /// literal is already spaces, so a brace inside either cannot throw the + /// nesting off. + static boolean declaresNestedPath(String text, String[] path) { + String code = blankNonCode(text); + int from = 0; + int end = code.length(); + for (String segment : path) { + int at = declarationOf(code, segment, from, end); + if (at < 0) { + return false; + } + int open = code.indexOf('{', at); + if (open < 0 || open >= end) { + // A body-less declaration -- Kotlin's `class Foo` -- can only be + // the last segment, and if it is not, nothing is nested in it. + return segment.equals(path[path.length - 1]); + } + from = open + 1; + end = matchingBrace(code, open); + } + return true; + } + + /// The offset of a `class`/`interface`/`enum`/`object`/`record` declaration of + /// `simple` declared DIRECTLY between `from` and `end`, or -1. + /// + /// Directly: at brace depth zero within that range. A match at any depth + /// would let Main.B.Wrong answer for Main.Wrong, which is the same + /// wrong-identity bug one level along -- and Main.Wrong not existing is + /// exactly when its .class is an orphan. + private static int declarationOf(String code, String simple, int from, int end) { + int depth = 0; + int i = from; + while (i < end && i < code.length()) { + char c = code.charAt(i); + if (c == '{') { + depth++; + i++; + continue; + } + if (c == '}') { + depth--; + i++; + continue; + } + if (depth != 0 || !Character.isJavaIdentifierStart(c) + || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { + i++; + continue; + } + int wordEnd = i; + while (wordEnd < code.length() && Character.isJavaIdentifierPart(code.charAt(wordEnd))) { + wordEnd++; + } + String word = code.substring(i, wordEnd); + if (isTypeKeyword(word)) { + int n = wordEnd; + while (n < end && Character.isWhitespace(code.charAt(n))) { + n++; + } + int stop = n; + while (stop < end && Character.isJavaIdentifierPart(code.charAt(stop))) { + stop++; + } + if (code.substring(n, stop).equals(simple)) { + return i; + } + } + i = wordEnd; + } + return -1; + } + + private static boolean isTypeKeyword(String word) { + return "class".equals(word) || "interface".equals(word) || "enum".equals(word) + || "object".equals(word) || "record".equals(word); + } + + /// The index just past the brace closing the one at `open`. + private static int matchingBrace(String code, int open) { + int depth = 0; + for (int i = open; i < code.length(); i++) { + char c = code.charAt(i); + if (c == '{') { + depth++; + } else if (c == '}') { + depth--; + if (depth == 0) { + return i; + } + } + } + return code.length(); } /// The innermost named segment of a nested binary name, or null. @@ -334,18 +433,32 @@ private static boolean matches(File f, String pkg, String simple, String nested) /// source declares and which cannot carry an annotation in the first place, /// so there is nothing to look for and nothing to conclude from not finding /// it. - private static String nestedNameOf(String binaryName) { - int nested = binaryName.lastIndexOf('$'); - if (nested < 0 || nested + 1 >= binaryName.length()) { + private static String[] nestedNameOf(String binaryName) { + int dot = binaryName.lastIndexOf('.'); + String simple = dot < 0 ? binaryName : binaryName.substring(dot + 1); + if (simple.indexOf('$') < 0) { return null; } - String tail = binaryName.substring(nested + 1); - for (int i = 0; i < tail.length(); i++) { - if (!Character.isDigit(tail.charAt(i))) { - return tail; + String[] path = simple.split("\\$"); + for (String segment : path) { + if (segment.length() == 0) { + return null; + } + boolean digits = true; + for (int i = 0; i < segment.length(); i++) { + if (!Character.isDigit(segment.charAt(i))) { + digits = false; + break; + } + } + // An anonymous or synthetic segment. No source declares one and none + // can carry a TYPE annotation, so there is nothing to look for and + // nothing to conclude from not finding it. + if (digits) { + return null; } } - return null; + return path; } /// Whether `text` declares a type called `simple`. diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 89bb5321f4c..1894f5b6ced 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -169,4 +169,35 @@ public void anAcceptedSpellingMigratesToTheConstantItMeans() { assertEquals("IosThemeMode.MODERN", mojo.toSourceLiteral(ios, "modern", false)); assertNull(mojo.toSourceLiteral(ios, "nonsense", false)); } + + /// A value that is not already canonical is refused, not normalised. + /// AndroidGradleBuilder compares android.hideStatusBar with .equals("true"), + /// so `TRUE` is false today and migrating it to `true` would flip the app's + /// behaviour while reporting a successful migration. + @Test + public void aNonCanonicalScalarIsRefused() { + MigrateBuildHintsMojo mojo = new MigrateBuildHintsMojo(); + com.codename1.build.shared.BuildHints.Hint bool = + com.codename1.build.shared.BuildHints.byName("android.hideStatusBar"); + assertEquals("true", mojo.toSourceLiteral(bool, "true", false)); + assertNull(mojo.toSourceLiteral(bool, "TRUE", false)); + assertNull(mojo.toSourceLiteral(bool, "True", false)); + assertNull(mojo.toSourceLiteral(bool, "true ", false)); + + com.codename1.build.shared.BuildHints.Hint ios = + com.codename1.build.shared.BuildHints.byName("ios.themeMode"); + assertEquals("IosThemeMode.MODERN", mojo.toSourceLiteral(ios, "modern", false)); + assertNull(mojo.toSourceLiteral(ios, "MODERN", false)); + } + + /// An int that does not round-trip would be rewritten too. + @Test + public void anIntThatDoesNotRoundTripIsRefused() { + MigrateBuildHintsMojo mojo = new MigrateBuildHintsMojo(); + com.codename1.build.shared.BuildHints.Hint i = + com.codename1.build.shared.BuildHints.byName("android.min_sdk_version"); + assertEquals("24", mojo.toSourceLiteral(i, "24", false)); + assertNull(mojo.toSourceLiteral(i, "024", false)); + assertNull(mojo.toSourceLiteral(i, "+24", false)); + } } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index f3339030bc2..841f85954bc 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -349,6 +349,36 @@ public void blankingKeepsThePositionsIntact() { assertEquals(2, blanked.split("\n", -1).length - 1); } + /// The whole nesting path, in order. Checking only the innermost name let an + /// unrelated Main.B.Wrong vouch for a deleted Main.A.Wrong; checking only the + /// outer class was the same bug one level out. + @Test + public void aNestedPathMustNestTheSameWay() { + String src = "package com.example;\n" + + "public class Main {\n" + + " static class A { }\n" + + " static class B { static class Wrong { } }\n" + + "}\n"; + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + src, new String[] {"Main", "B", "Wrong"})); + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + src, new String[] {"Main", "A", "Wrong"})); + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + src, new String[] {"Main", "Wrong"})); + } + + /// Braces inside comments and strings must not move the nesting. + @Test + public void bracesInCommentsAndStringsDoNotBreakNesting() { + String src = "class Main {\n" + + " // }\n" + + " String s = \"}\";\n" + + " static class Wrong { }\n" + + "}\n"; + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + src, new String[] {"Main", "Wrong"})); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 4a4d7e47dfc..e33f79fc4dd 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2336,10 +2336,14 @@ static boolean declaresClass(String text, String main, String pkg, boolean kotli boolean wholeWord = (at == 0 || !continuesAName(text.charAt(at - 1))) && start < text.length() && !continuesAName(text.charAt(start)); if (wholeWord) { - int i = start; - while (i < text.length() - && (text.charAt(i) == ' ' || text.charAt(i) == '\t')) { - i++; + // Every legal separator, not just a space: `class\nMain` and + // `class /* why */ Main` are both valid Java and Kotlin, and + // stopping at a newline read the declaration as unnamed -- + // so the file did not declare the main class, ownership read + // as empty, and Add wrote the duplicate. + int i = nextLiveChar(text, start, kotlin); + if (i < 0) { + break; } int end = i; while (end < text.length() && continuesAName(text.charAt(end))) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 4105e3ca0a9..56bc6064317 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -600,4 +600,18 @@ public void aCommentedPackageStatementIsNotThePackage() { assertFalse(CodenameOneSettings.declaresClass( "// package com.example\nclass MyApp\n", "MyApp", "com.example", true)); } + + /// `class\nMain` and `class /* why */ Main` are both legal. Stopping at a + /// space read the declaration as unnamed, so the file did not declare the + /// main class and ownership came back empty. + @Test + public void anyLegalSeparatorBeforeTheNameIsAccepted() { + assertTrue(CodenameOneSettings.declaresClass( + "package com.example\nclass\nMyApp\n", "MyApp", "com.example", true)); + assertTrue(CodenameOneSettings.declaresClass( + "package com.example\nclass /* why */ MyApp\n", "MyApp", "com.example", true)); + assertTrue(CodenameOneSettings.declaresClass( + "package com.example;\npublic class\t MyApp {}\n", "MyApp", + "com.example", false)); + } } From a9928437b423576cdf5d067ead0a31c227636267 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 20:41:31 +0300 Subject: [PATCH 043/115] Stop skipping the attribute the orphan filter reads The orphan filter has never run. ClassScanner reads with SKIP_DEBUG, which also suppresses visitSource, so getSourceFile() was null for every class and hasBackingSource() took its "cannot tell" branch every time. The package match, the declaration match and the nesting path built on top of it were all dead code, and nothing said so -- each layer answered "keep the class", which is the same thing the mechanism does when it works and finds a source. Found while checking that a test for the named-local-class fix actually failed without the fix. It did not, and the reason was this rather than anything about local classes. With SKIP_CODE already set there are no method bodies to walk, so what dropping SKIP_DEBUG costs is parsing one string per class. The fix it was hiding is real too. javac names a NAMED local class Main$1Wrong, and the rule only treated a wholly numeric segment as javac's own -- so `1Wrong` was looked for in the source, not found, and the live annotated class was dropped before the placement check could reject it. A segment beginning with a digit is javac's, and there is nothing to conclude from not finding it. declaresType still required exactly one space after the keyword, so `class\nWrong` and `class /* why */ Wrong` read as no declaration -- a live type looked stale, and the migration goal reported it could not find the main source. It shares the whitespace-tolerant scan the nesting check already used. Both tests were re-run against the pre-fix code to confirm they fail, which is what turned up the inert filter in the first place. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/annotations/ClassScanner.java | 13 +++- .../BuildHintAnnotationProcessor.java | 56 +++++--------- .../BuildHintAnnotationProcessorTest.java | 77 ++++++++++++++++++- 3 files changed, 107 insertions(+), 39 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ClassScanner.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ClassScanner.java index 3740790be33..a823b7d99e2 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ClassScanner.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/annotations/ClassScanner.java @@ -43,8 +43,15 @@ /// Walks a directory of compiled `.class` files and produces the /// `AnnotatedClass` index passed to processors. /// -/// Uses ASM's `ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | -/// ClassReader.SKIP_FRAMES` flags — we only care about declarations +/// Uses ASM's `ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES` flags — we only +/// care about declarations. +/// +/// NOT `SKIP_DEBUG`, which also suppresses `visitSource` and so the SourceFile +/// attribute. That attribute is the only reliable link from a compiled class +/// back to the file that declared it — Kotlin does not require the two to share +/// a name — and skipping it made every caller of `getSourceFile()` see null and +/// silently take its "cannot tell" branch. With SKIP_CODE already set there are +/// no method bodies to walk, so what this costs is parsing one string per class /// (annotations, signatures), never method bodies. This keeps scanning fast /// even on large projects. /// @@ -118,7 +125,7 @@ public static AnnotatedClass readClass(InputStream in, File source) throws Proce try { ClassReader reader = new ClassReader(in); Collector c = new Collector(source); - reader.accept(c, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + reader.accept(c, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); return c.build(); } catch (IOException e) { throw new ProcessingException("Could not read class from " + source + ": " + e.getMessage(), e); diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 921e1afb06c..a717ffbd8d8 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -363,6 +363,14 @@ static boolean declaresNestedPath(String text, String[] path) { /// wrong-identity bug one level along -- and Main.Wrong not existing is /// exactly when its .class is an orphan. private static int declarationOf(String code, String simple, int from, int end) { + return declarationOf(code, simple, from, end, true); + } + + /// As above; `directOnly` restricts the match to brace depth zero within the + /// range, which is what nesting identity needs and what a plain "does this + /// file declare X" question does not. + private static int declarationOf(String code, String simple, int from, int end, + boolean directOnly) { int depth = 0; int i = from; while (i < end && i < code.length()) { @@ -377,7 +385,7 @@ private static int declarationOf(String code, String simple, int from, int end) i++; continue; } - if (depth != 0 || !Character.isJavaIdentifierStart(c) + if ((directOnly && depth != 0) || !Character.isJavaIdentifierStart(c) || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { i++; continue; @@ -433,7 +441,7 @@ private static int matchingBrace(String code, int open) { /// source declares and which cannot carry an annotation in the first place, /// so there is nothing to look for and nothing to conclude from not finding /// it. - private static String[] nestedNameOf(String binaryName) { + static String[] nestedNameOf(String binaryName) { int dot = binaryName.lastIndexOf('.'); String simple = dot < 0 ? binaryName : binaryName.substring(dot + 1); if (simple.indexOf('$') < 0) { @@ -441,20 +449,15 @@ private static String[] nestedNameOf(String binaryName) { } String[] path = simple.split("\\$"); for (String segment : path) { - if (segment.length() == 0) { - return null; - } - boolean digits = true; - for (int i = 0; i < segment.length(); i++) { - if (!Character.isDigit(segment.charAt(i))) { - digits = false; - break; - } - } - // An anonymous or synthetic segment. No source declares one and none - // can carry a TYPE annotation, so there is nothing to look for and - // nothing to conclude from not finding it. - if (digits) { + // A segment BEGINNING with a digit is javac's, not the developer's: + // $1 for an anonymous class and $1Wrong for a named local one. No + // source declares either spelling, so looking for it finds nothing -- + // and concluding "orphan" from that dropped a live annotated local + // class before the placement check could reject it, which let the + // build succeed with the requested hints silently discarded. + // + // Checking for wholly-numeric missed $1Wrong exactly. + if (segment.length() == 0 || Character.isDigit(segment.charAt(0))) { return null; } } @@ -467,25 +470,8 @@ private static String[] nestedNameOf(String binaryName) { /// `// class Wrong` left behind by the very edit that deleted the type would /// otherwise vouch for its own orphan. public static boolean declaresType(String text, String simple) { - text = blankNonCode(text); - String[] keywords = {"class ", "interface ", "enum ", "object ", "record "}; - for (String keyword : keywords) { - int at = text.indexOf(keyword); - while (at >= 0) { - int start = at + keyword.length(); - int end = start; - while (end < text.length() - && Character.isJavaIdentifierPart(text.charAt(end))) { - end++; - } - if (text.substring(start, end).equals(simple) - && (at == 0 || !Character.isJavaIdentifierPart(text.charAt(at - 1)))) { - return true; - } - at = text.indexOf(keyword, start); - } - } - return false; + String code = blankNonCode(text); + return declarationOf(code, simple, 0, code.length(), false) >= 0; } /// `text` with every comment and string literal replaced by spaces, so a diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 841f85954bc..795855f945c 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -40,6 +40,7 @@ import java.util.Map; import java.util.Properties; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -379,6 +380,69 @@ public void bracesInCommentsAndStringsDoNotBreakNesting() { src, new String[] {"Main", "Wrong"})); } + /// javac names a NAMED local class Main$1Wrong. No source declares that + /// spelling, so looking for it finds nothing -- and concluding "orphan" from + /// that dropped a live annotated class before the placement check could + /// reject it, letting the build succeed with the hints silently discarded. + /// Checking for a wholly numeric segment missed $1Wrong exactly. + @Test + public void anAnnotationOnANamedLocalClassIsStillReported() throws Exception { + String source = "package com.example;\n" + + "import com.codename1.annotations.buildhints.*;\n" + + "public class MyApp {\n" + + " void go() {\n" + + " @Ios(teamId = \"ABCDE12345\")\n" + + " class Wrong {}\n" + + " new Wrong();\n" + + " }\n" + + "}\n"; + File classes = tmp.newFolder(); + JavaSourceCompiler.compile(JavaSourceCompiler.singleSource(MAIN, source), + classes, Arrays.asList(testClassesDir(), coreJar())); + // The source root has to be real, or the orphan filter is never consulted + // and this passes whatever it does. + ProcessorContext ctx = run(classes, settings(), MAIN, false, sourceRootWith(source)); + assertErrorContaining(ctx, "belong on the application's main class"); + } + + /// The rule the case above rests on, stated directly: javac's own segments + /// are unjudgeable, a developer's are not. + @Test + public void javacsOwnNestedSegmentsAreNotLookedFor() { + assertNull(BuildHintAnnotationProcessor.nestedNameOf("com.example.Main$1Wrong")); + assertNull(BuildHintAnnotationProcessor.nestedNameOf("com.example.Main$1")); + assertNull(BuildHintAnnotationProcessor.nestedNameOf("com.example.Main$A$1B")); + assertArrayEquals(new String[] {"Main", "Wrong"}, + BuildHintAnnotationProcessor.nestedNameOf("com.example.Main$Wrong")); + assertNull(BuildHintAnnotationProcessor.nestedNameOf("com.example.Main")); + } + + /// A source root holding MyApp.java with the given text. + private File sourceRootWith(String source) throws Exception { + File root = tmp.newFolder(); + File dir = new File(root, "com" + File.separator + "example"); + assertTrue(dir.mkdirs()); + try (java.io.Writer w = new java.io.OutputStreamWriter( + new FileOutputStream(new File(dir, "MyApp.java")), "UTF-8")) { + w.write(source); + } + return root; + } + + /// A declaration may put any legal whitespace, or a comment, between the + /// keyword and the name. Requiring exactly one space read `class\nWrong` as + /// no declaration at all -- so a live type looked stale to the orphan check, + /// and the migration goal reported it could not find the main source. + @Test + public void anyLegalSeparatorBeforeATypeNameIsAccepted() { + assertTrue(BuildHintAnnotationProcessor.declaresType("class\nWrong {}", "Wrong")); + assertTrue(BuildHintAnnotationProcessor.declaresType("class\tWrong {}", "Wrong")); + assertTrue(BuildHintAnnotationProcessor.declaresType("class /* why */ Wrong {}", "Wrong")); + assertTrue(BuildHintAnnotationProcessor.declaresType( + "public\nfinal\nclass\n Wrong\n{}", "Wrong")); + assertFalse(BuildHintAnnotationProcessor.declaresType("class Wronger {}", "Wrong")); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ @@ -400,10 +464,21 @@ private File compile(String annotations) throws Exception { private ProcessorContext run(File classes, Properties settings, String mainClass, boolean expectClean) throws Exception { + return run(classes, settings, mainClass, expectClean, null); + } + + /// With `sourceRoot` the orphan filter runs for real. Without it the context + /// reports no compile source roots, which the filter reads as "not told" and + /// keeps every class -- so a test that means to exercise the filter has to + /// supply one, or it passes whatever the filter does. + private ProcessorContext run(File classes, Properties settings, String mainClass, + boolean expectClean, File sourceRoot) throws Exception { Map index = ClassScanner.scan(classes); BuildHintAnnotationProcessor proc = new BuildHintAnnotationProcessor(); ProcessorContext ctx = new ProcessorContext(classes, tmp.newFolder(), index, - new SystemStreamLog(), tmp.newFolder(), settings, mainClass); + new SystemStreamLog(), tmp.newFolder(), settings, mainClass, + sourceRoot == null ? null + : java.util.Collections.singletonList(sourceRoot.getAbsolutePath())); proc.start(ctx); for (AnnotatedClass cls : index.values()) { proc.processClass(cls, ctx); From d8c148adf34bfe61d3a915cca495dca813403452 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 20:54:05 +0300 Subject: [PATCH 044/115] Count braces over code, and read the whole file Three from the same review, all in the source-identity machinery. A char literal was left intact by the blanking, so a lone '{' was counted as syntax, the nesting scan lost its place, and a live nested class read as an orphan -- dropped before the placement check could report its misplaced annotation, letting the build succeed with the hints silently discarded. Blanked now, escapes included, so '\'' does not end the literal early. readHead stopped at 400 lines. A type declared below that -- after a long generated header, or a big import block -- was not found, with the same consequence. A prefix is the wrong shape for this job anyway: a nesting scan cannot start in the middle of a file and still count braces. It reads the whole file, with a 4MB cap that exists to reject something which is not source at all; exceeding it returns null, which the caller reads as "cannot tell" and keeps the class. Settings accepted a NESTED declaration as the main class, so an unrelated `class Outer { class Main }` in the same package ended the fallback search on the wrong file and the real main class's annotations were never read. The declaration has to be top-level, judged by brace depth over code. My first char-literal test had both '{' and '}', which cancel out: it passed with the fix removed and proved nothing. It is unbalanced now and fails without the fix, which is the second test in this review that needed re-running against the un-fixed code before it was worth anything. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 40 ++++++++++-- .../BuildHintAnnotationProcessorTest.java | 41 ++++++++++++ .../settings/CodenameOneSettings.java | 64 +++++++++++++------ .../settings/BuildHintCatalogTest.java | 22 +++++++ 4 files changed, 143 insertions(+), 24 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index a717ffbd8d8..fed0658871f 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -502,6 +502,26 @@ public static String blankNonCode(String text) { if (i < out.length) { out[i++] = ' '; } + } else if (c == '\'') { + // A char literal. '{' is legal and would otherwise be counted as + // syntax, so the nesting scan loses its place and reads a live + // class as an orphan. + out[i++] = ' '; + while (i < out.length) { + if (out[i] == '\\' && i + 1 < out.length) { + out[i++] = ' '; + out[i++] = ' '; + continue; + } + boolean closing = out[i] == '\''; + if (out[i] != '\n') { + out[i] = ' '; + } + i++; + if (closing) { + break; + } + } } else if (c == '"') { boolean triple = i + 2 < out.length && out[i + 1] == '"' && out[i + 2] == '"'; int quotes = triple ? 3 : 1; @@ -533,18 +553,28 @@ public static String blankNonCode(String text) { return new String(out); } - /// The first 400 lines of `f`, or null when it cannot be read. + /// The whole of `f`, or null when it cannot be read or is implausibly large. /// - /// Bounded because this runs per annotated class, and both the package - /// statement and the type declarations of interest are at the top. + /// Not a prefix. A line bound looked harmless and was not: a type declared + /// below it -- after a long generated header, or a big import block -- was + /// not found, so a live class read as an orphan and its misplaced annotation + /// was skipped instead of reported. A nesting scan also cannot start in the + /// middle of a file and still count braces. + /// + /// The size cap is a guard against something that is not source at all, not + /// a budget: 4MB is far past any hand-written Java or Kotlin file, and + /// exceeding it returns null, which the caller reads as "cannot tell" and so + /// keeps the class. private static String readHead(File f) { + if (f.length() > 4L * 1024 * 1024) { + return null; + } BufferedReader r = null; try { r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); StringBuilder sb = new StringBuilder(); String line; - int read = 0; - while ((line = r.readLine()) != null && read++ < 400) { + while ((line = r.readLine()) != null) { sb.append(line).append('\n'); } return sb.toString(); diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 795855f945c..3cb82de35fa 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -443,6 +443,47 @@ public void anyLegalSeparatorBeforeATypeNameIsAccepted() { assertFalse(BuildHintAnnotationProcessor.declaresType("class Wronger {}", "Wrong")); } + /// A brace inside a char literal is not syntax. Counting it loses the + /// nesting, so a live nested class reads as an orphan and its misplaced + /// annotation is skipped instead of reported. + @Test + public void aBraceInACharLiteralDoesNotMoveTheNesting() { + // Deliberately UNBALANCED. My first version of this test had both '{' + // and '}', which cancel out, so it passed with the char branch removed + // and proved nothing. + String src = "class Main {\n" + + " char open = '{';\n" + + " static class Wrong { }\n" + + "}\n"; + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + src, new String[] {"Main", "Wrong"})); + + // An escaped quote must not end the literal early, or the brace after it + // is counted again. + String escaped = "class Main {\n" + + " char quote = '\\'';\n" + + " char open = '{';\n" + + " static class Wrong { }\n" + + "}\n"; + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + escaped, new String[] {"Main", "Wrong"})); + } + + /// A declaration below any fixed prefix must still be found: a line bound + /// meant a type after a long header read as absent, so a live class looked + /// stale and its placement error was never reported. + @Test + public void aDeclarationFarDownTheFileIsStillFound() { + StringBuilder src = new StringBuilder("package com.example;\n"); + for (int i = 0; i < 900; i++) { + src.append("import java.util.List").append(i).append(";\n"); + } + src.append("public class MyApp {}\n"); + assertTrue(BuildHintAnnotationProcessor.declaresType(src.toString(), "MyApp")); + assertEquals("com.example", + BuildHintAnnotationProcessor.declaredPackageIn(src.toString())); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index e33f79fc4dd..424950b434b 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2329,32 +2329,58 @@ static boolean declaresClass(String text, String main, String pkg, boolean kotli if (!(pkg == null || pkg.isEmpty() ? "" : pkg).equals(declaredPkg)) { return false; } - for (String keyword : new String[]{"class", "object"}) { - int at = nextMarker(text, keyword, 0, kotlin); - while (at >= 0) { - int start = at + keyword.length(); - boolean wholeWord = (at == 0 || !continuesAName(text.charAt(at - 1))) - && start < text.length() && !continuesAName(text.charAt(start)); - if (wholeWord) { - // Every legal separator, not just a space: `class\nMain` and - // `class /* why */ Main` are both valid Java and Kotlin, and - // stopping at a newline read the declaration as unnamed -- - // so the file did not declare the main class, ownership read - // as empty, and Add wrote the duplicate. - int i = nextLiveChar(text, start, kotlin); - if (i < 0) { - break; - } - int end = i; + // At the TOP level. An application's main class is not nested, and + // accepting a nested one let an unrelated `class Outer { class Main }` + // in the same package end the search on the wrong file -- so the + // annotations on the real main class were never read, and Settings + // offered Add for a hint that is already annotated. + int depth = 0; + int i = 0; + while (i < text.length()) { + char c = text.charAt(i); + if (c == '"' || c == '\'' || c == '/') { + int skipped = skipNonCode(text, i, kotlin); + if (skipped > i) { + i = skipped; + continue; + } + } + if (c == '{') { + depth++; + i++; + continue; + } + if (c == '}') { + depth--; + i++; + continue; + } + if (depth != 0 || !continuesAName(c) + || (i > 0 && continuesAName(text.charAt(i - 1)))) { + i++; + continue; + } + int wordEnd = i; + while (wordEnd < text.length() && continuesAName(text.charAt(wordEnd))) { + wordEnd++; + } + String word = text.substring(i, wordEnd); + if ("class".equals(word) || "object".equals(word)) { + // Every legal separator, not just a space: `class\nMain` and + // `class /* why */ Main` are both valid Java and Kotlin, and + // stopping at a newline read the declaration as unnamed. + int n = nextLiveChar(text, wordEnd, kotlin); + if (n >= 0) { + int end = n; while (end < text.length() && continuesAName(text.charAt(end))) { end++; } - if (text.substring(i, end).equals(main)) { + if (text.substring(n, end).equals(main)) { return true; } } - at = nextMarker(text, keyword, start, kotlin); } + i = wordEnd; } return false; } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 56bc6064317..01d3c11d421 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -614,4 +614,26 @@ public void anyLegalSeparatorBeforeTheNameIsAccepted() { "package com.example;\npublic class\t MyApp {}\n", "MyApp", "com.example", false)); } + + /// An application's main class is top-level. Accepting a nested one let an + /// unrelated `class Outer { class Main }` end the fallback search on the + /// wrong file, so annotations on the real main class were never read. + @Test + public void aNestedDeclarationIsNotTheMainClass() { + assertFalse(CodenameOneSettings.declaresClass( + "package com.example\nclass Outer { class MyApp }\n", "MyApp", + "com.example", true)); + assertTrue(CodenameOneSettings.declaresClass( + "package com.example\nclass Outer { }\nclass MyApp\n", "MyApp", + "com.example", true)); + } + + /// A brace inside a char literal is not syntax; counting it loses the depth + /// and turns a top-level declaration into a nested one or the reverse. + @Test + public void aBraceInACharLiteralDoesNotMoveTheDepth() { + assertTrue(CodenameOneSettings.declaresClass( + "package com.example;\npublic class Helper { char c = '{'; }\n" + + "class MyApp {}\n", "MyApp", "com.example", false)); + } } From 2034b41f96691afe11abf356ce688da0d6dd034f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:06:00 +0300 Subject: [PATCH 045/115] Follow the language's own rules for imports, packages and scope Three from the same review, each a place still using a shortcut where the language has a rule. A single-type import shadows an on-demand one. A file importing our package with a wildcard AND another library's Ios by name is using theirs, and reading the wildcard as sufficient made Settings hide the editor for a hint the processor never emits. An explicit import of the same simple name from anywhere else now wins. Our own explicit import is not "another library's", and a Kotlin `as` import is not a single-type import of that name at all -- it introduces its alias -- so neither shadows. `package\ncom.example;` is valid Java, and a line-oriented parse saw an empty remainder and reported the default package: a live class then looked like it belonged elsewhere, read as an orphan, and its misplaced annotation went unreported. Parsed as tokens across whitespace now, comments included. Top level is brace depth zero, not column zero. The insertion point was found by a pattern anchored to the start of a line, so ` public class MyApp` -- which compiles fine -- rolled the migration back with "Could not find the class declaration", on a project whose source the token-aware lookup had just accepted. Found by depth over blanked code, and the point returned precedes the modifiers so the annotations do not land between `public` and `class`. Each of the three tests was run against the pre-fix code and fails there. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 119 +++++++++++++++--- .../BuildHintAnnotationProcessor.java | 39 ++++-- .../MigrateBuildHintsPropertyParsingTest.java | 28 +++++ .../BuildHintAnnotationProcessorTest.java | 15 +++ .../settings/CodenameOneSettings.java | 43 ++++++- .../settings/BuildHintCatalogTest.java | 33 +++++ 6 files changed, 250 insertions(+), 27 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 74863f95570..793e5ec4a9a 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -884,21 +884,110 @@ private void insertAnnotations(File source, String annotations, String simpleNam * happens to appear first.

*/ static int classDeclarationIndex(String text, boolean kotlin, String simpleName) { - String modifiers = kotlin - ? "(?:public |internal |private |open |abstract |final |sealed |data |value |annotation )*" - : "(?:public |protected |private |abstract |final |static |strictfp |sealed |non-sealed )*"; - String kinds = kotlin ? "(?:class|object|interface)" : "(?:class|interface|enum|record)"; - java.util.regex.Pattern named = java.util.regex.Pattern.compile( - "(?m)^" + modifiers + kinds + "\\s+" - + java.util.regex.Pattern.quote(simpleName == null ? "" : simpleName) - + "\\b"); - java.util.regex.Matcher m = named.matcher(text); - if (simpleName != null && simpleName.length() > 0 && m.find()) { - return m.start(); - } - java.util.regex.Matcher any = java.util.regex.Pattern.compile( - "(?m)^" + modifiers + kinds + "\\s+\\w").matcher(text); - return any.find() ? any.start() : -1; + // Top level means brace depth zero, not column zero. Anchoring the + // pattern to the start of a line refused ` public class MyApp`, which + // compiles perfectly well -- so the goal rolled back with "Could not find + // the class declaration" on a project whose source it had just accepted + // through the token-aware lookup. + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(text); + int first = -1; + int depth = 0; + int i = 0; + while (i < code.length()) { + char c = code.charAt(i); + if (c == '{') { + depth++; + i++; + continue; + } + if (c == '}') { + depth--; + i++; + continue; + } + if (depth != 0 || !Character.isJavaIdentifierStart(c) + || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { + i++; + continue; + } + int wordEnd = i; + while (wordEnd < code.length() + && Character.isJavaIdentifierPart(code.charAt(wordEnd))) { + wordEnd++; + } + String word = code.substring(i, wordEnd); + if (isTypeKind(word, kotlin)) { + int n = wordEnd; + while (n < code.length() && Character.isWhitespace(code.charAt(n))) { + n++; + } + int end = n; + while (end < code.length() + && Character.isJavaIdentifierPart(code.charAt(end))) { + end++; + } + if (end > n) { + // The declaration's own modifiers come before the keyword, and + // the annotations have to go before those. + int start = startOfModifiers(code, i); + if (simpleName != null && simpleName.length() > 0 + && code.substring(n, end).equals(simpleName)) { + return start; + } + if (first < 0) { + first = start; + } + } + } + i = wordEnd; + } + return first; + } + + private static boolean isTypeKind(String word, boolean kotlin) { + if (kotlin) { + return "class".equals(word) || "object".equals(word) || "interface".equals(word); + } + return "class".equals(word) || "interface".equals(word) || "enum".equals(word) + || "record".equals(word); + } + + /// Back up over the modifiers preceding the keyword at `at`, so the + /// annotations land above `public final class` rather than inside it. + private static int startOfModifiers(String code, int at) { + int start = at; + while (true) { + int i = start - 1; + while (i >= 0 && (code.charAt(i) == ' ' || code.charAt(i) == '\t')) { + i--; + } + if (i < 0) { + return start; + } + int wordEnd = i + 1; + while (i >= 0 && (Character.isJavaIdentifierPart(code.charAt(i)) + || code.charAt(i) == '-')) { + i--; + } + String word = code.substring(i + 1, wordEnd); + if (word.length() == 0 || !isModifier(word)) { + return start; + } + start = i + 1; + } + } + + private static boolean isModifier(String word) { + String[] modifiers = {"public", "protected", "private", "abstract", "final", "static", + "strictfp", "sealed", "non-sealed", "internal", "open", "data", + "value", "annotation", "inner", "expect", "actual"}; + for (String m : modifiers) { + if (m.equals(word)) { + return true; + } + } + return false; } /** diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index fed0658871f..9e38c85ca74 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -593,22 +593,39 @@ private static String readHead(File f) { /// The package `text` declares, or "" for the default package. public static String declaredPackageIn(String text) { - text = blankNonCode(text); - for (String line : text.split("\n")) { - String t = line.trim(); - if (!t.startsWith("package")) { + // Tokens, not lines. `package\ncom.example;` is valid Java, and reading + // one physical line saw an empty remainder and reported the default + // package -- so a live class looked like it belonged somewhere else, read + // as an orphan, and its misplaced annotation went unreported. + String code = blankNonCode(text); + int i = 0; + while (i < code.length()) { + char c = code.charAt(i); + if (!Character.isJavaIdentifierStart(c) + || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { + i++; continue; } - String rest = t.substring("package".length()); - if (rest.length() == 0 || Character.isJavaIdentifierPart(rest.charAt(0))) { + int wordEnd = i; + while (wordEnd < code.length() + && Character.isJavaIdentifierPart(code.charAt(wordEnd))) { + wordEnd++; + } + if (!"package".equals(code.substring(i, wordEnd))) { + i = wordEnd; continue; } - rest = rest.trim(); - int end = rest.indexOf(';'); - if (end >= 0) { - rest = rest.substring(0, end); + int n = wordEnd; + while (n < code.length() && Character.isWhitespace(code.charAt(n))) { + n++; + } + StringBuilder name = new StringBuilder(); + while (n < code.length() + && (Character.isJavaIdentifierPart(code.charAt(n)) || code.charAt(n) == '.')) { + name.append(code.charAt(n)); + n++; } - return rest.trim(); + return name.toString(); } return ""; } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 1894f5b6ced..683ea6f986a 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -200,4 +200,32 @@ public void anIntThatDoesNotRoundTripIsRefused() { assertNull(mojo.toSourceLiteral(i, "024", false)); assertNull(mojo.toSourceLiteral(i, "+24", false)); } + + /// Top level is brace depth zero, not column zero. Anchoring to the start of + /// a line refused ` public class MyApp`, which compiles fine -- so the goal + /// rolled back on a project whose source it had just accepted. + @Test + public void anIndentedDeclarationIsStillTopLevel() { + String src = "package com.example;\n\n public final class MyApp {\n}\n"; + int at = MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp"); + assertEquals(src.indexOf(" public final class") + 2, at); + } + + /// The insertion point precedes the modifiers, so the annotations do not land + /// between `public` and `class`. + @Test + public void theInsertionPointPrecedesTheModifiers() { + String src = "package com.example;\npublic abstract class MyApp {\n}\n"; + assertEquals(src.indexOf("public abstract class"), + MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); + } + + /// A nested type of the same name is not the top-level declaration. + @Test + public void aNestedTypeIsNotTheInsertionPoint() { + String src = "package com.example;\nclass Outer {\n class MyApp {}\n}\n" + + "class MyApp {}\n"; + assertEquals(src.lastIndexOf("class MyApp {}"), + MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); + } } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 3cb82de35fa..add985eb6e4 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -484,6 +484,21 @@ public void aDeclarationFarDownTheFileIsStillFound() { BuildHintAnnotationProcessor.declaredPackageIn(src.toString())); } + /// `package\ncom.example;` is valid Java. A line-oriented parse saw an empty + /// remainder and reported the default package, so a live class looked like it + /// belonged elsewhere, read as an orphan, and its misplaced annotation went + /// unreported. + @Test + public void aPackageDeclarationMaySpanLines() { + assertEquals("com.example", + BuildHintAnnotationProcessor.declaredPackageIn("package\ncom.example;\n")); + assertEquals("com.example", + BuildHintAnnotationProcessor.declaredPackageIn("package com.example ;\n")); + assertEquals("com.example", + BuildHintAnnotationProcessor.declaredPackageIn("package /* x */ com.example\n")); + assertEquals("", BuildHintAnnotationProcessor.declaredPackageIn("// package com.example;")); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 424950b434b..3046ac33632 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2409,6 +2409,14 @@ private String readIfPresent(String path) { /// `@Build` of some other library counts as ours. static boolean importsAnnotation(String source, String simple, boolean kotlin) { String pkg = "com.codename1.annotations.buildhints."; + // An explicit single-type import of the same simple name, from anywhere + // else, wins over our wildcard -- that is the language rule, not a + // preference. Without it a file importing our package on demand AND some + // other library's Ios by name had its @Ios read as ours, so Settings hid + // the editor for a hint the processor never emits. + if (importsOtherTypeNamed(source, simple, pkg, kotlin)) { + return false; + } for (String needle : new String[] {pkg + simple, pkg + "*"}) { int at = nextMarker(source, needle, 0, kotlin); while (at >= 0) { @@ -2428,7 +2436,40 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin) { return false; } - /// Whether an `as` rename follows the import target at `after`. + /// Whether a live import brings a DIFFERENT type of this simple name into + /// scope by name. + /// + /// A single-type import shadows an on-demand one, so ours loses. An aliased + /// Kotlin import is not one of these: it introduces its alias, not `simple`. + static boolean importsOtherTypeNamed(String source, String simple, String ourPkg, + boolean kotlin) { + int at = nextMarker(source, simple, 0, kotlin); + while (at >= 0) { + int after = at + simple.length(); + boolean whole = (at == 0 || !continuesAName(source.charAt(at - 1))) + && (after >= source.length() || !continuesAName(source.charAt(after))); + if (whole && precededByQualifiedImport(source, at) + && !hasAsClause(source, after) + && !source.startsWith(ourPkg + simple, at - ourPkg.length() < 0 + ? 0 : at - ourPkg.length())) { + return true; + } + at = nextMarker(source, simple, after, kotlin); + } + return false; + } + + /// Whether the token at `at` ends a dotted name that an `import` introduces. + private static boolean precededByQualifiedImport(String source, int at) { + int i = at - 1; + while (i >= 0 && (continuesAName(source.charAt(i)) || source.charAt(i) == '.')) { + i--; + } + return precededByImport(source, i + 1) && i >= 0 && at > 0 + && source.charAt(at - 1) == '.'; + } + + /// Whether an `as` rename follows the import target at `after`. /// Whether an `as` rename follows the import target at `after`. private static boolean hasAsClause(String source, int after) { int i = after; while (i < source.length() && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 01d3c11d421..5058d1695bb 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -636,4 +636,37 @@ public void aBraceInACharLiteralDoesNotMoveTheDepth() { "package com.example;\npublic class Helper { char c = '{'; }\n" + "class MyApp {}\n", "MyApp", "com.example", false)); } + + /// A single-type import shadows an on-demand one -- the language rule, not a + /// preference. A file importing our package with a wildcard AND another + /// library's Ios by name is using theirs. + @Test + public void anExplicitImportBeatsOurWildcard() { + String src = "import com.codename1.annotations.buildhints.*;\n" + + "import com.example.other.Ios;\n" + + "@Ios(teamId = \"T\")\npublic class MyApp {}\n"; + assertFalse(CodenameOneSettings.importsAnnotation(src, "Ios", false)); + + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints(src, out, false); + assertNull(out.get("ios.teamId")); + } + + /// Our own explicit import is not "another library's". + @Test + public void ourOwnExplicitImportStillCounts() { + String src = "import com.codename1.annotations.buildhints.*;\n" + + "import com.codename1.annotations.buildhints.Ios;\n" + + "@Ios(teamId = \"T\")\npublic class MyApp {}\n"; + assertTrue(CodenameOneSettings.importsAnnotation(src, "Ios", false)); + } + + /// Nor is a Kotlin alias, which introduces its alias rather than the name. + @Test + public void anAliasedForeignImportDoesNotShadow() { + String src = "import com.codename1.annotations.buildhints.*\n" + + "import com.example.other.Ios as TheirIos\n" + + "@Ios(teamId = \"T\")\nclass MyApp\n"; + assertTrue(CodenameOneSettings.importsAnnotation(src, "Ios", true)); + } } From bb30a7e976f71eb0cb0200185e11a8443908e4a9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:17:43 +0300 Subject: [PATCH 046/115] Stop a hint value from forging the digest around it The fingerprint concatenated members with plain delimiters, and a value is exactly where a developer writes arbitrary text. So @Ios(bundleVersion = "1;teamId=java.lang.String:X") @Ios(bundleVersion = "1", teamId = "X") rendered identically and hashed the same. With processing no longer running, the stale manifest was then accepted for a genuinely different configuration and the build kept the old bundle version while omitting the new team ID -- silently, which is what the digest exists to prevent. Every variable-length piece is length-prefixed now -- descriptor, member name, type name, value, and the member and element counts -- so nothing a value contains can be read as structure. Lists and nested annotations go through the same encoding rather than their own delimiters. Verified against the pre-fix code: the forgery test fails there. The other two are regression guards rather than discriminators for this change, and pin the half that matters just as much -- the same annotations, written in either order, must still fingerprint the same, or the check would refuse every build instead of only the wrong ones. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 49 +++++++++++++------ .../BuildHintAnnotationProcessorTest.java | 40 +++++++++++++++ 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 9e38c85ca74..e59dfe21945 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -839,15 +839,14 @@ public static String sourceDigest(AnnotatedClass cls) throws ProcessingException if (!known.contains(descriptor)) { continue; } - sb.append(descriptor).append('{'); + emit(sb, descriptor); AnnotationValues values = cls.getClassAnnotation(descriptor); + emit(sb, String.valueOf(values.all().size())); for (Map.Entry e : new TreeMap(values.all()).entrySet()) { - sb.append(e.getKey()).append('='); + emit(sb, e.getKey()); renderForDigest(e.getValue(), sb); - sb.append(';'); } - sb.append('}'); } try { java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); @@ -863,35 +862,53 @@ public static String sourceDigest(AnnotatedClass cls) throws ProcessingException } } + /// Appends `s` length-prefixed, so nothing it contains can be read as + /// structure. + /// + /// A value IS a place an attacker -- or an unlucky developer -- writes + /// arbitrary text: with plain delimiters, + /// `@Ios(bundleVersion = "1;teamId=java.lang.String:X")` rendered exactly + /// like `@Ios(bundleVersion = "1", teamId = "X")`, so a stale manifest was + /// accepted for a different configuration and the build silently kept the old + /// values. + private static void emit(StringBuilder sb, String s) { + sb.append(s.length()).append(':').append(s).append(';'); + } + /// The type is part of the rendering, so an int 1 and the string "1" -- which /// print alike but are different annotations -- do not fingerprint alike. + /// + /// Every variable-length piece goes through `emit`, so no value can forge the + /// structure around it. private static void renderForDigest(Object value, StringBuilder sb) { if (value == null) { - sb.append("null"); + emit(sb, "null"); } else if (value instanceof String[]) { // How ASM delivers an enum member: {descriptor, CONSTANT_NAME}. String[] e = (String[]) value; - sb.append("enum:").append(e.length > 0 ? e[0] : "") - .append('.').append(e.length > 1 ? e[1] : ""); + emit(sb, "enum"); + emit(sb, e.length > 0 ? e[0] : ""); + emit(sb, e.length > 1 ? e[1] : ""); } else if (value instanceof List) { - sb.append('['); - for (Object item : (List) value) { + List list = (List) value; + emit(sb, "list"); + emit(sb, String.valueOf(list.size())); + for (Object item : list) { renderForDigest(item, sb); - sb.append(','); } - sb.append(']'); } else if (value instanceof AnnotationValues) { AnnotationValues nested = (AnnotationValues) value; - sb.append(nested.getDescriptor()).append('{'); + emit(sb, "annotation"); + emit(sb, nested.getDescriptor()); + emit(sb, String.valueOf(nested.all().size())); for (Map.Entry e : new TreeMap(nested.all()).entrySet()) { - sb.append(e.getKey()).append('='); + emit(sb, e.getKey()); renderForDigest(e.getValue(), sb); - sb.append(';'); } - sb.append('}'); } else { - sb.append(value.getClass().getName()).append(':').append(value); + emit(sb, value.getClass().getName()); + emit(sb, String.valueOf(value)); } } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index add985eb6e4..ad01b840dc9 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -499,6 +499,46 @@ public void aPackageDeclarationMaySpanLines() { assertEquals("", BuildHintAnnotationProcessor.declaredPackageIn("// package com.example;")); } + /// A value is a place a developer writes arbitrary text, so it must not be + /// able to forge the structure around it. With plain delimiters these two + /// annotations fingerprinted identically, and a stale manifest was then + /// accepted for a genuinely different configuration. + @Test + public void aValueCannotForgeTheDigestStructure() throws Exception { + String forged = digestOf( + "@Ios(bundleVersion = \"1;teamId=java.lang.String:X\")"); + String real = digestOf("@Ios(bundleVersion = \"1\", teamId = \"X\")"); + assertFalse("a value must not be able to imitate another member", + forged.equals(real)); + } + + /// Neighbouring values must not run together either: {"a","bc"} is not + /// {"ab","c"}, and a list of one is not the value itself. + @Test + public void adjacentValuesDoNotRunTogether() throws Exception { + assertFalse(digestOf("@Ios(pods = {\"a\", \"bc\"})") + .equals(digestOf("@Ios(pods = {\"ab\", \"c\"})"))); + assertFalse(digestOf("@Ios(pods = {\"a\"})") + .equals(digestOf("@Ios(teamId = \"a\")"))); + } + + /// ...while the same annotations still fingerprint the same, or the check + /// would refuse every build instead of only the wrong ones. + @Test + public void theSameAnnotationsFingerprintTheSame() throws Exception { + assertEquals(digestOf("@Ios(teamId = \"X\", bundleVersion = \"1\")"), + digestOf("@Ios(bundleVersion = \"1\", teamId = \"X\")")); + } + + /// The digest of a main class annotated so. + private String digestOf(String annotations) throws Exception { + File dir = tmp.newFolder(); + JavaSourceCompiler.compile(JavaSourceCompiler.singleSource(MAIN, source(annotations)), + dir, Arrays.asList(testClassesDir(), coreJar())); + Map index = ClassScanner.scan(dir); + return BuildHintAnnotationProcessor.sourceDigest(index.values().iterator().next()); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ From bd903c4e06457593ccb463d4ba95f5e003c4d085 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:30:31 +0300 Subject: [PATCH 047/115] Store the spelling that works, and put the import above the annotations Two from the same review. Settings validated a closed domain case-insensitively and then stored what the developer typed. AndroidGradleBuilder copies android.installLocation straight into the case-sensitive android:installLocation manifest attribute, so `INTERNALONLY` was marked valid and failed the Android build. The value is stored in the domain's own spelling now -- only the spelling changes, never the choice, and an accepted alias resolves to the value it names. Deliberately not the other way round. Rejecting non-canonical spellings would flag configurations that work today, since several hints ARE read with equalsIgnoreCase, and Settings has no way to know which. The migration is where strictness belongs, because it rewrites a value that a builder may compare exactly; the editor's job is to store one that every reader accepts. classDeclarationIndex points at the modifiers, so `head` ends with any annotation the class already carries -- and the import anchor of head.length() put the new import between that annotation and the class, which is valid in neither language. The verification build then failed and rolled a correct migration back. The anchor is the start of the leading annotation run now. That walk is over blanked code, which the test forced: an argument may contain a parenthesis inside a string -- @SuppressWarnings("a(b") -- and counting that one left the walk stranded mid-literal. My first version did exactly that and the test caught it. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 63 ++++++++++++++++++- .../MigrateBuildHintsPropertyParsingTest.java | 24 +++++++ .../settings/CodenameOneSettings.java | 26 +++++++- .../settings/BuildHintCatalogTest.java | 15 +++++ 4 files changed, 126 insertions(+), 2 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 793e5ec4a9a..f456ede4ac0 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -865,7 +865,14 @@ private void insertAnnotations(File source, String annotations, String simpleNam // nothing useful while the properties entries had already been // deleted. int pkg = head.indexOf("package "); - int anchor = pkg >= 0 ? head.indexOf('\n', pkg) + 1 : head.length(); + // Before any annotation the class already carries. classDeclarationIndex + // points at the modifiers, so `head` ends with an existing + // @SuppressWarnings -- and anchoring on head.length() put the import + // between that annotation and the class, which is not valid in either + // language. The verification build then failed and rolled a correct + // migration back. + int anchor = pkg >= 0 ? head.indexOf('\n', pkg) + 1 + : startOfLeadingAnnotations(head); head = head.substring(0, anchor) + (pkg >= 0 ? "\n" : "") + importLine + "\n" + (pkg >= 0 ? "" : "\n") + head.substring(anchor); } @@ -883,6 +890,60 @@ private void insertAnnotations(File source, String annotations, String simpleNam * the type named by {@code codename1.mainName} is preferred over whatever * happens to appear first.

*/ + /// The index in `head` where the run of annotations immediately preceding the + /// declaration begins, or `head.length()` when there is none. + /// + /// Walks back over whitespace and complete `@Name(...)` forms, matching + /// parentheses so a multi-line annotation does not stop the walk early. + /// + /// Over blanked code, because an argument may contain a parenthesis inside a + /// string -- `@SuppressWarnings("a(b")` -- and counting that one leaves the + /// walk stranded in the middle of a literal. Positions are preserved by the + /// blanking, so the index returned indexes the original text. + static int startOfLeadingAnnotations(String original) { + String head = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(original); + int at = head.length(); + while (true) { + int i = at - 1; + while (i >= 0 && Character.isWhitespace(head.charAt(i))) { + i--; + } + if (i < 0) { + return at; + } + if (head.charAt(i) == ')') { + int depth = 0; + while (i >= 0) { + char c = head.charAt(i); + if (c == ')') { + depth++; + } else if (c == '(') { + depth--; + if (depth == 0) { + i--; + break; + } + } + i--; + } + if (depth != 0) { + return at; + } + } + // The annotation's name, then its @. + int nameEnd = i + 1; + while (i >= 0 && (Character.isJavaIdentifierPart(head.charAt(i)) + || head.charAt(i) == '.')) { + i--; + } + if (i < 0 || head.charAt(i) != '@' || nameEnd == i + 1) { + return at; + } + at = i; + } + } + static int classDeclarationIndex(String text, boolean kotlin, String simpleName) { // Top level means brace depth zero, not column zero. Anchoring the // pattern to the start of a line refused ` public class MyApp`, which diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 683ea6f986a..87ac465f76f 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -228,4 +228,28 @@ public void aNestedTypeIsNotTheInsertionPoint() { assertEquals(src.lastIndexOf("class MyApp {}"), MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); } + + /// A default-package class that already carries an annotation: the import + /// must go ABOVE it. Anchoring on the declaration put the import between the + /// annotation and the class, which is not valid in either language. + @Test + public void theImportGoesAboveAnExistingAnnotation() { + String head = "/* c */\n@SuppressWarnings(\"unchecked\")\n"; + assertEquals(head.indexOf("@SuppressWarnings"), + MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); + } + + /// A parenthesis inside an annotation argument must not stop the walk. + @Test + public void anArgumentContainingAParenthesisDoesNotStopTheWalk() { + String head = "@Deprecated\n@SuppressWarnings(\"a(b\")\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); + } + + /// With no annotation there is nothing to move above. + @Test + public void withNoAnnotationTheAnchorIsTheEnd() { + String head = "/* copyright */\n\n"; + assertEquals(head.length(), MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); + } } diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 3046ac33632..9bfcc266adc 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -742,7 +742,13 @@ private Component activeHintEditor(BuildHintMetadata meta, String value, BuildHi valueField.addDataChangedListener((type, index) -> { String next = valueField.getText() == null ? "" : valueField.getText().trim(); if (isValidHintValue(meta, next)) { - settings.setBuildHint(meta.name(), next); + // Stored in the domain's own spelling. Accepting `INTERNALONLY` + // and writing it back verbatim marked the value valid and then + // failed the Android build, because the builder copies it into + // the case-sensitive android:installLocation attribute. What + // the developer meant is unambiguous, and only one spelling of + // it works everywhere. + settings.setBuildHint(meta.name(), canonicalHintValue(meta, next)); valueField.setUIID(uiid("SettingsField")); } else { valueField.setUIID(uiid("SettingsFieldError")); @@ -811,6 +817,24 @@ private void configureHintField(TextField field, BuildHintMetadata meta) { } } + /// `value` in the spelling the catalog declares, or `value` when the domain + /// is open or does not recognise it. + /// + /// Only the spelling changes, never the choice: an accepted alias resolves to + /// the canonical value it names, which is the one every reader accepts. + private String canonicalHintValue(BuildHintMetadata meta, String value) { + if (value == null || value.length() == 0 || meta.values().isEmpty()) { + return value; + } + com.codename1.build.shared.BuildHints.Hint hint = + com.codename1.build.shared.BuildHints.byName(meta.name()); + if (hint == null || hint.values().isEmpty()) { + return value; + } + String canonical = hint.canonicalValue(value); + return canonical == null ? value : canonical; + } + private boolean isValidHintValue(BuildHintMetadata meta, String value) { if (value == null || value.trim().length() == 0) { return true; diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 5058d1695bb..1f3ea372bd5 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -669,4 +669,19 @@ public void anAliasedForeignImportDoesNotShadow() { + "@Ios(teamId = \"T\")\nclass MyApp\n"; assertTrue(CodenameOneSettings.importsAnnotation(src, "Ios", true)); } + + /// Only one spelling of a closed-domain value works everywhere: + /// AndroidGradleBuilder copies android.installLocation straight into the + /// case-sensitive android:installLocation manifest attribute. Accepting + /// `INTERNALONLY` and storing it verbatim marked it valid and then failed the + /// Android build. + @Test + public void aClosedDomainValueHasOneWorkingSpelling() { + com.codename1.build.shared.BuildHints.Hint h = + com.codename1.build.shared.BuildHints.byName("android.installLocation"); + assertEquals("internalOnly", h.canonicalValue("INTERNALONLY")); + assertEquals("internalOnly", h.canonicalValue("internalonly")); + assertEquals("internalOnly", h.canonicalValue("internalOnly")); + assertNull(h.canonicalValue("nowhere")); + } } From 73974eb4a9ffced933be29e8d569350dcaa592da Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:43:16 +0300 Subject: [PATCH 048/115] Read imports forwards, and triple quotes by language Three from the same review. Migrating a list trimmed every element and dropped the empty ones. That is a rewrite, not a tidy-up: android.xgradle is newline-delimited raw Groovy that the builder appends as it stands, so the indentation inside a multiline string is part of the value, and the verification cannot see the difference because it checks that the hint came back and not what it holds. Elements are emitted verbatim now and none is dropped, so splitting on the separator and joining the result reproduces the original exactly -- which is the property that makes this lossless. The import check backed up from a name over spaces and tabs, so `import /* build hints */ com.codename1...Ios;` was not recognised and the live @Ios was read as somebody else's. Backing up is the wrong shape for this: it has to step over comments in reverse. Imports are parsed FORWARDS now, once, into the name each introduces and its alias -- and the wildcard rule, the explicit-import-shadows rule and the Kotlin alias all read that one list instead of three backward walks that could disagree. Two helpers went with it. blankNonCode read a triple-quoted literal the same way in both languages, which the Settings-side scanner already knew it could not: Kotlin closes at the LAST three quotes of a run, Java processes escapes so \""" is not a delimiter. Reading one as the other over-consumes and blanks the declaration after it, so a live class reads as an orphan and its misplaced annotation is never reported. It takes the language now, from the file's own extension where there is a file. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 35 ++-- .../BuildHintAnnotationProcessor.java | 103 ++++++++-- .../settings/CodenameOneSettings.java | 194 +++++++----------- .../settings/BuildHintCatalogTest.java | 24 +++ 4 files changed, 210 insertions(+), 146 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index f456ede4ac0..f3aab9f9de2 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -644,18 +644,22 @@ String toSourceLiteral(BuildHints.Hint hint, String value, boolean kotlin) { if (sep == null || sep.length() == 0) { return quoteFor(v, kotlin); } + // Verbatim, and every element kept. Trimming and dropping empties + // looked tidy and is a rewrite: android.xgradle is newline + // delimited raw Groovy that AndroidGradleBuilder appends as it + // stands, so the indentation inside a multiline string is part of + // the value. Splitting on the separator and joining the elements + // back with it now reproduces the original string exactly, which + // is the only property that makes this migration lossless -- the + // verification cannot see it, since it checks that the hint came + // back and not what it holds. String[] parts = v.split(java.util.regex.Pattern.quote(sep), -1); StringBuilder sb = new StringBuilder(kotlin ? "[" : "{"); - int written = 0; - for (String part : parts) { - String t = part.trim(); - if (t.length() == 0) { - continue; - } - if (written++ > 0) { + for (int i = 0; i < parts.length; i++) { + if (i > 0) { sb.append(", "); } - sb.append(quoteFor(t, kotlin)); + sb.append(quoteFor(parts[i], kotlin)); } return sb.append(kotlin ? ']' : '}').toString(); } @@ -819,10 +823,11 @@ private boolean declares(File f, String pkg, String simple) { } catch (IOException ex) { return false; } + boolean kotlin = f.getName().endsWith(".kt"); return pkg.equals(com.codename1.maven.processors.BuildHintAnnotationProcessor - .declaredPackageIn(text)) + .declaredPackageIn(text, kotlin)) && com.codename1.maven.processors.BuildHintAnnotationProcessor - .declaresType(text, simple); + .declaresType(text, simple, kotlin); } /** @@ -872,7 +877,7 @@ private void insertAnnotations(File source, String annotations, String simpleNam // language. The verification build then failed and rolled a correct // migration back. int anchor = pkg >= 0 ? head.indexOf('\n', pkg) + 1 - : startOfLeadingAnnotations(head); + : startOfLeadingAnnotations(head, kotlin); head = head.substring(0, anchor) + (pkg >= 0 ? "\n" : "") + importLine + "\n" + (pkg >= 0 ? "" : "\n") + head.substring(anchor); } @@ -901,8 +906,12 @@ private void insertAnnotations(File source, String annotations, String simpleNam /// walk stranded in the middle of a literal. Positions are preserved by the /// blanking, so the index returned indexes the original text. static int startOfLeadingAnnotations(String original) { + return startOfLeadingAnnotations(original, false); + } + + static int startOfLeadingAnnotations(String original, boolean kotlin) { String head = com.codename1.maven.processors.BuildHintAnnotationProcessor - .blankNonCode(original); + .blankNonCode(original, kotlin); int at = head.length(); while (true) { int i = at - 1; @@ -951,7 +960,7 @@ static int classDeclarationIndex(String text, boolean kotlin, String simpleName) // the class declaration" on a project whose source it had just accepted // through the token-aware lookup. String code = com.codename1.maven.processors.BuildHintAnnotationProcessor - .blankNonCode(text); + .blankNonCode(text, kotlin); int first = -1; int depth = 0; int i = 0; diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index e59dfe21945..f15f9c08bcd 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -317,7 +317,11 @@ private static boolean matches(File f, String pkg, String simple, String[] neste // thing this decides is whether to IGNORE an annotated class. return true; } - if (!pkg.equals(declaredPackageIn(text)) || !declaresType(text, simple)) { + // The file names its own language, and a triple-quoted literal is read + // differently in each. + boolean kotlin = f.getName().endsWith(".kt"); + if (!pkg.equals(declaredPackageIn(text, kotlin)) + || !declaresType(text, simple, kotlin)) { return false; } // The whole nesting PATH has to be there, in order. Checking only the @@ -325,7 +329,7 @@ private static boolean matches(File f, String pkg, String simple, String[] neste // Main.A.Wrong, so the orphan stayed and failed the placement check on // every incremental build. Checking only the outer class was the same bug // one level out. - return nested == null || declaresNestedPath(text, nested); + return nested == null || declaresNestedPath(text, nested, kotlin); } /// Whether `text` declares the chain `path` -- {"Main", "A", "Wrong"} -- each @@ -335,7 +339,11 @@ private static boolean matches(File f, String pkg, String simple, String[] neste /// literal is already spaces, so a brace inside either cannot throw the /// nesting off. static boolean declaresNestedPath(String text, String[] path) { - String code = blankNonCode(text); + return declaresNestedPath(text, path, false); + } + + static boolean declaresNestedPath(String text, String[] path, boolean kotlin) { + String code = blankNonCode(text, kotlin); int from = 0; int end = code.length(); for (String segment : path) { @@ -470,7 +478,12 @@ static String[] nestedNameOf(String binaryName) { /// `// class Wrong` left behind by the very edit that deleted the type would /// otherwise vouch for its own orphan. public static boolean declaresType(String text, String simple) { - String code = blankNonCode(text); + return declaresType(text, simple, false); + } + + /// As above, reading the source by `kotlin`'s rules. + public static boolean declaresType(String text, String simple, boolean kotlin) { + String code = blankNonCode(text, kotlin); return declarationOf(code, simple, 0, code.length(), false) >= 0; } @@ -478,6 +491,18 @@ public static boolean declaresType(String text, String simple) { /// declaration can be looked for without a quoted or commented mention of one /// answering for it. Lengths and line breaks are preserved. public static String blankNonCode(String text) { + return blankNonCode(text, false); + } + + /// As above, reading triple-quoted literals by the rules of `kotlin`'s + /// language. + /// + /// The two differ and reading one as the other over-consumes: a Kotlin raw + /// string ends at the LAST three quotes of a run, while a Java text block + /// processes escapes so `\"""` is not a delimiter. Getting it wrong blanks + /// the declaration that follows, so a live class reads as an orphan and its + /// misplaced annotation is never reported. + public static String blankNonCode(String text, boolean kotlin) { char[] out = text.toCharArray(); int i = 0; while (i < out.length) { @@ -522,29 +547,30 @@ public static String blankNonCode(String text) { break; } } - } else if (c == '"') { - boolean triple = i + 2 < out.length && out[i + 1] == '"' && out[i + 2] == '"'; - int quotes = triple ? 3 : 1; - for (int q = 0; q < quotes && i < out.length; q++) { - out[i++] = ' '; + } else if (c == '"' && i + 2 < out.length && out[i + 1] == '"' && out[i + 2] == '"') { + int close = kotlin ? endOfKotlinRawString(out, i) : endOfJavaTextBlock(out, i); + while (i < close && i < out.length) { + if (out[i] != '\n') { + out[i] = ' '; + } + i++; } + } else if (c == '"') { + out[i++] = ' '; while (i < out.length) { - if (!triple && out[i] == '\\' && i + 1 < out.length) { + if (out[i] == '\\' && i + 1 < out.length) { out[i++] = ' '; out[i++] = ' '; continue; } - if (out[i] == '"' && (!triple - || (i + 2 < out.length && out[i + 1] == '"' && out[i + 2] == '"'))) { - for (int q = 0; q < quotes && i < out.length; q++) { - out[i++] = ' '; - } - break; - } + boolean closing = out[i] == '"'; if (out[i] != '\n') { out[i] = ' '; } i++; + if (closing) { + break; + } } } else { i++; @@ -553,6 +579,44 @@ public static String blankNonCode(String text) { return new String(out); } + /// Index just past a Java text block opening at `i`. Escapes apply, so a + /// backslash consumes the next character and cannot start the delimiter. + private static int endOfJavaTextBlock(char[] c, int i) { + int j = i + 3; + while (j < c.length) { + if (c[j] == '\\') { + j += 2; + continue; + } + if (c[j] == '"' && j + 2 < c.length && c[j + 1] == '"' && c[j + 2] == '"') { + return j + 3; + } + j++; + } + return c.length; + } + + /// Index just past a Kotlin raw string opening at `i`. No escapes, and a run + /// of quotes closes at its LAST three, so the extra ones belong to the value. + private static int endOfKotlinRawString(char[] c, int i) { + int j = i + 3; + while (j < c.length) { + if (c[j] != '"') { + j++; + continue; + } + int run = j; + while (run < c.length && c[run] == '"') { + run++; + } + if (run - j >= 3) { + return run; + } + j = run; + } + return c.length; + } + /// The whole of `f`, or null when it cannot be read or is implausibly large. /// /// Not a prefix. A line bound looked harmless and was not: a type declared @@ -593,6 +657,11 @@ private static String readHead(File f) { /// The package `text` declares, or "" for the default package. public static String declaredPackageIn(String text) { + return declaredPackageIn(text, false); + } + + /// As above, reading the source by `kotlin`'s rules. + public static String declaredPackageIn(String text, boolean kotlin) { // Tokens, not lines. `package\ncom.example;` is valid Java, and reading // one physical line saw an empty remainder and reported the default // package -- so a live class looked like it belonged somewhere else, read diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 9bfcc266adc..d6c4fc18a2b 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2427,82 +2427,94 @@ private String readIfPresent(String path) { } } - /// Whether a live import brings `simple` in from the build hints package. - /// - /// Either the type by name or the package on demand. Without this the bare - /// `@Build` of some other library counts as ours. - static boolean importsAnnotation(String source, String simple, boolean kotlin) { - String pkg = "com.codename1.annotations.buildhints."; - // An explicit single-type import of the same simple name, from anywhere - // else, wins over our wildcard -- that is the language rule, not a - // preference. Without it a file importing our package on demand AND some - // other library's Ios by name had its @Ios read as ours, so Settings hid - // the editor for a hint the processor never emits. - if (importsOtherTypeNamed(source, simple, pkg, kotlin)) { - return false; - } - for (String needle : new String[] {pkg + simple, pkg + "*"}) { - int at = nextMarker(source, needle, 0, kotlin); - while (at >= 0) { - int after = at + needle.length(); - boolean whole = needle.endsWith("*") - || after >= source.length() || !continuesAName(source.charAt(after)); - // `import ...Ios as BuildIos` does NOT put Ios in scope -- it puts - // BuildIos there, and the file is then free to import someone - // else's Ios. Counting the aliased import as a simple-name import - // attributed that other annotation to us. - if (whole && precededByImport(source, at) && !hasAsClause(source, after)) { - return true; - } - at = nextMarker(source, needle, after, kotlin); - } + /// One import directive: the dotted name it introduces and its alias, if any. + static final class Imported { + final String name; + final String alias; + + Imported(String name, String alias) { + this.name = name; + this.alias = alias; } - return false; } - /// Whether a live import brings a DIFFERENT type of this simple name into - /// scope by name. + /// Every live `import` in `source`, read FORWARDS. /// - /// A single-type import shadows an on-demand one, so ours loses. An aliased - /// Kotlin import is not one of these: it introduces its alias, not `simple`. - static boolean importsOtherTypeNamed(String source, String simple, String ourPkg, - boolean kotlin) { - int at = nextMarker(source, simple, 0, kotlin); + /// Forwards rather than by backing up from a name, because backing up has to + /// step over comments in reverse -- and + /// `import /* build hints */ com.codename1.annotations.buildhints.Ios;` is + /// legal, so a backward walk that skipped only spaces missed the import and + /// the live @Ios was read as somebody else's. + static java.util.List importsIn(String source, boolean kotlin) { + java.util.List out = new java.util.ArrayList<>(); + int at = nextMarker(source, "import", 0, kotlin); while (at >= 0) { - int after = at + simple.length(); + int after = at + "import".length(); boolean whole = (at == 0 || !continuesAName(source.charAt(at - 1))) - && (after >= source.length() || !continuesAName(source.charAt(after))); - if (whole && precededByQualifiedImport(source, at) - && !hasAsClause(source, after) - && !source.startsWith(ourPkg + simple, at - ourPkg.length() < 0 - ? 0 : at - ourPkg.length())) { - return true; + && after < source.length() && !continuesAName(source.charAt(after)); + if (!whole) { + at = nextMarker(source, "import", after, kotlin); + continue; } - at = nextMarker(source, simple, after, kotlin); + int i = nextLiveChar(source, after, kotlin); + if (i < 0) { + return out; + } + StringBuilder name = new StringBuilder(); + while (i < source.length() + && (continuesAName(source.charAt(i)) || source.charAt(i) == '.' + || source.charAt(i) == '*')) { + name.append(source.charAt(i)); + i++; + } + String alias = null; + int a = nextLiveChar(source, i, kotlin); + if (a >= 0 && source.regionMatches(a, "as", 0, 2) + && a + 2 < source.length() && !continuesAName(source.charAt(a + 2))) { + int n = nextLiveChar(source, a + 2, kotlin); + if (n >= 0) { + int nameEnd = n; + while (nameEnd < source.length() + && continuesAName(source.charAt(nameEnd))) { + nameEnd++; + } + if (nameEnd > n) { + alias = source.substring(n, nameEnd); + } + } + } + if (name.length() > 0) { + out.add(new Imported(name.toString(), alias)); + } + at = nextMarker(source, "import", i, kotlin); } - return false; + return out; } - /// Whether the token at `at` ends a dotted name that an `import` introduces. - private static boolean precededByQualifiedImport(String source, int at) { - int i = at - 1; - while (i >= 0 && (continuesAName(source.charAt(i)) || source.charAt(i) == '.')) { - i--; + /// Whether a live import brings `simple` in from the build hints package. + /// + /// Either the type by name or the package on demand, and neither if some + /// other library's type of that name is imported explicitly: a single-type + /// import shadows an on-demand one, so their `Ios` beats our wildcard. That + /// is the language's rule, not a preference. + static boolean importsAnnotation(String source, String simple, boolean kotlin) { + String pkg = "com.codename1.annotations.buildhints."; + boolean ours = false; + for (Imported imported : importsIn(source, kotlin)) { + if (imported.alias != null) { + // Introduces its alias, not this name, so it neither grants the + // simple spelling nor shadows it. + continue; + } + if (imported.name.equals(pkg + simple) || imported.name.equals(pkg + "*")) { + ours = true; + } else if (imported.name.endsWith("." + simple)) { + return false; + } } - return precededByImport(source, i + 1) && i >= 0 && at > 0 - && source.charAt(at - 1) == '.'; + return ours; } - /// Whether an `as` rename follows the import target at `after`. /// Whether an `as` rename follows the import target at `after`. - private static boolean hasAsClause(String source, int after) { - int i = after; - while (i < source.length() && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { - i++; - } - return source.regionMatches(i, "as", 0, 2) - && i + 2 < source.length() - && !continuesAName(source.charAt(i + 2)); - } /// The name a Kotlin `import ... as Alias` gives an annotation, or null. /// The name a Kotlin `import ... as Alias` gives an annotation, or null. /// @@ -2513,64 +2525,14 @@ private static boolean hasAsClause(String source, int after) { /// itself created. static String kotlinImportAlias(String source, String simple, boolean kotlin) { String needle = "com.codename1.annotations.buildhints." + simple; - // Same comment-aware walk the marker search uses, and the occurrence has - // to be a live `import` directive. A commented-out earlier alias -- - // `// import ...Ios as Old` above the real `import ...Ios as BuildIos` -- - // otherwise won, the live `@BuildIos` was never looked for, and the hint - // read as unowned again: the exact bug the alias support was added for. - int at = nextMarker(source, needle, 0, kotlin); - while (at >= 0) { - int after = at + needle.length(); - if (!precededByImport(source, at)) { - at = nextMarker(source, needle, after, kotlin); - continue; + for (Imported imported : importsIn(source, kotlin)) { + if (imported.alias != null && needle.equals(imported.name)) { + return imported.alias; } - if (after >= source.length() || !continuesAName(source.charAt(after))) { - int i = after; - while (i < source.length() && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { - i++; - } - if (source.regionMatches(i, "as", 0, 2) - && i + 2 < source.length() - && !continuesAName(source.charAt(i + 2))) { - i += 2; - while (i < source.length() - && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { - i++; - } - int start = i; - while (i < source.length() && continuesAName(source.charAt(i))) { - i++; - } - if (i > start) { - return source.substring(start, i); - } - } - } - at = nextMarker(source, needle, after, kotlin); } return null; } - /// Whether the token at `at` is the target of an `import` on the same line. - /// - /// Without this a mention of the package in code or in a doc string would be - /// read as an import directive. - private static boolean precededByImport(String source, int at) { - int i = at - 1; - while (i >= 0 && (source.charAt(i) == ' ' || source.charAt(i) == '\t')) { - i--; - } - if (i < 5) { - return false; - } - if (!source.regionMatches(i - 5, "import", 0, 6)) { - return false; - } - int before = i - 6; - return before < 0 || !continuesAName(source.charAt(before)); - } - /// Maps every `@Group(attr = ...)` on the main class to the hints it sets. /// Java rules for the source text; see the three-argument form. static void collectAnnotationOwnedHints(String source, java.util.Map out) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 1f3ea372bd5..33f23579971 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -684,4 +684,28 @@ public void aClosedDomainValueHasOneWorkingSpelling() { assertEquals("internalOnly", h.canonicalValue("internalOnly")); assertNull(h.canonicalValue("nowhere")); } + + /// `import /* build hints */ com.codename1...Ios;` is legal. Backing up from + /// the name over spaces only missed it, so the live @Ios was read as somebody + /// else's and Add offered a hint that is already annotated. + @Test + public void anImportSeparatedByCommentsOrNewlinesIsRecognised() { + assertTrue(CodenameOneSettings.importsAnnotation( + "import /* build hints */ com.codename1.annotations.buildhints.Ios;\n", + "Ios", false)); + assertTrue(CodenameOneSettings.importsAnnotation( + "import\n com.codename1.annotations.buildhints.Ios;\n", "Ios", false)); + assertEquals("BuildIos", CodenameOneSettings.kotlinImportAlias( + "import com.codename1.annotations.buildhints.Ios as BuildIos\n", + "Ios", true)); + } + + /// ...and a mention that is not an import still does not count. + @Test + public void aNonImportMentionIsStillNotAnImport() { + assertFalse(CodenameOneSettings.importsAnnotation( + "val t = com.codename1.annotations.buildhints.Ios::class\n", "Ios", true)); + assertFalse(CodenameOneSettings.importsAnnotation( + "// import com.codename1.annotations.buildhints.Ios;\n", "Ios", false)); + } } From fcc1c37eefc7fd26a19029b68bef1576ef1b9f1e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:54:25 +0300 Subject: [PATCH 049/115] Guard the form the ownership rule was not applied to Three from the same review, one of them a bug in last round's fix. The processor joined list elements on "has anything been written yet", so a leading EMPTY element got no separator after it. That is the same defect I had just fixed on the migration side and left here: a newline-delimited android.xgradle whose value begins with a newline lost it, silently, since the verification checks that the hint came back and not what it holds. Joined by index now. Withholding the catalog row's controls for an annotation-owned hint while leaving the custom hint form open is no protection at all: typing ios.teamId there writes exactly the second declaration the row was hiding, and the next build refuses the project. The form applies the same check, canonically, so an alias of an owned hint is caught too. Settings parsed the package declaration by taking the rest of the text and trimming it, so `package /* generated */ com.example;` started the name at the comment and the real main source was rejected by both the conventional lookup and the fallback search. Read from live tokens now, as the processor-side helper already does. Both new tests were run against the pre-fix code and fail there. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 12 +++++-- .../BuildHintAnnotationProcessorTest.java | 10 ++++++ .../settings/CodenameOneSettings.java | 35 ++++++++++++++----- .../settings/BuildHintCatalogTest.java | 12 +++++++ 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index f15f9c08bcd..269c1ed54a6 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -852,12 +852,18 @@ private String wireValue(AnnotatedClass cls, String descriptor, String member, O + "together."); return null; } + // By POSITION, not by what has been written. An element may legally be + // empty -- a newline-delimited android.xgradle whose value starts with + // a newline migrates to {"", "..."} -- and testing sb.length() then + // skipped the separator after it, silently dropping the leading + // newline from the hint the builder receives. StringBuilder sb = new StringBuilder(); - for (Object item : (List) raw) { - if (sb.length() > 0) { + List items = (List) raw; + for (int i = 0; i < items.size(); i++) { + if (i > 0) { sb.append(separator); } - String itemValue = wireValue(cls, descriptor, member, item, hint, ctx); + String itemValue = wireValue(cls, descriptor, member, items.get(i), hint, ctx); if (itemValue == null) { return null; } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index ad01b840dc9..5244d91258a 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -539,6 +539,16 @@ private String digestOf(String annotations) throws Exception { return BuildHintAnnotationProcessor.sourceDigest(index.values().iterator().next()); } + /// An element may legally be empty -- a newline-delimited value that starts + /// with a newline is {"", "..."} -- and joining on "what has been written so + /// far" skipped the separator after it, silently dropping the leading + /// newline from the hint the builder receives. + @Test + public void anEmptyListElementStillGetsItsSeparator() throws Exception { + Properties p = hintsOf("@Android(xgradle = {\"\", \"apply plugin: 'x'\"})"); + assertEquals("\napply plugin: 'x'", p.getProperty("codename1.arg.android.xgradle")); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index d6c4fc18a2b..e9bf625f953 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -616,6 +616,20 @@ private Component customHintRow() { ToastBar.showErrorMessage("Enter a build hint name."); return; } + // The same ownership rule as the catalog rows. Withholding the row's + // controls and leaving this form open is no protection at all: typing + // ios.teamId here -- or any alias of it -- writes exactly the second + // declaration the row was hiding, and the next build refuses the + // project. Canonical, so an alias of an annotation-owned hint is + // caught too. + String ownedBy = annotationOwnedHints.get( + com.codename1.build.shared.BuildHints.canonicalName(k)); + if (ownedBy != null) { + ToastBar.showErrorMessage(k + " is set by " + ownedBy + + " on the main class. Change it there -- declaring it here as well " + + "fails the build."); + return; + } settings.setBuildHint(k, value.getText() == null ? "" : value.getText()); key.setText(""); value.setText(""); @@ -2336,16 +2350,19 @@ static boolean declaresClass(String text, String main, String pkg, boolean kotli int after = pkgAt + "package".length(); if (after < text.length() && !continuesAName(text.charAt(after)) && (pkgAt == 0 || !continuesAName(text.charAt(pkgAt - 1)))) { - String rest = text.substring(after).trim(); - int cut = rest.length(); - for (int i = 0; i < rest.length(); i++) { - char c = rest.charAt(i); - if (c == ';' || c == '\n' || c == '\r') { - cut = i; - break; - } + // Live tokens, as the processor-side helper reads it. + // `package /* generated */ com.example;` is legal, and taking the + // remainder of the text and trimming it started the name at the + // comment -- so the real main source was rejected by both the + // conventional lookup and the fallback search. + int i = nextLiveChar(text, after, kotlin); + StringBuilder name = new StringBuilder(); + while (i >= 0 && i < text.length() + && (continuesAName(text.charAt(i)) || text.charAt(i) == '.')) { + name.append(text.charAt(i)); + i++; } - declaredPkg = rest.substring(0, cut).trim(); + declaredPkg = name.toString(); break; } pkgAt = nextMarker(text, "package", after, kotlin); diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 33f23579971..02e1f1fe52e 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -708,4 +708,16 @@ public void aNonImportMentionIsStillNotAnImport() { assertFalse(CodenameOneSettings.importsAnnotation( "// import com.codename1.annotations.buildhints.Ios;\n", "Ios", false)); } + + /// `package /* generated */ com.example;` is legal. Taking the remainder of + /// the text and trimming it started the name at the comment, so the real main + /// source was rejected by both the conventional lookup and the fallback. + @Test + public void aCommentBetweenPackageAndItsNameIsSkipped() { + assertTrue(CodenameOneSettings.declaresClass( + "package /* generated */ com.example;\npublic class MyApp {}\n", + "MyApp", "com.example", false)); + assertTrue(CodenameOneSettings.declaresClass( + "package\n com.example\nclass MyApp\n", "MyApp", "com.example", true)); + } } From e4a51239c4d56994c12dd9716d2b82ade8564898 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:07:03 +0300 Subject: [PATCH 050/115] Judge a Kotlin nested path only as far as Kotlin lets us Three from the same review, all in what I added. declaredPackageIn took a kotlin flag and then called the Java-mode scanner, so the flag did nothing. A Kotlin file annotation holding a raw string that ends in a quote closes on a run of four; read by Java's rules it closed at the first three and the remaining quote opened a new literal that blanked the package declaration after it. Kotlin builds a local class's binary name from the enclosing FUNCTION names -- Main$start$Wrong -- and nothing marks `start` as synthetic the way javac's $1 does. Requiring it to be a declared type read a live annotated class as an orphan and dropped it before the placement check, so the build succeeded with the hints silently missing. Past the outermost type a Kotlin segment is inconclusive now, which is the right way round: concluding orphan loses hints with no message, while keeping a stale class costs a placement error the developer can see and act on. The outermost segment is still required, because that is the type the file declares and the whole match rests on it. Java keeps the strict reading. The migration's source lookup accepted a declaration at any depth, so a leftover Main.kt holding `class Outer { class Main }` stopped the search, the annotations were inserted on Outer, and the verification build rejected the placement and rolled the migration back. It asks for a top-level declaration -- which a single-segment nested path already tests exactly -- rather than growing a fourth notion of scope. All three tests were run against the pre-fix code and fail there. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 8 ++++- .../BuildHintAnnotationProcessor.java | 21 +++++++++--- .../MigrateBuildHintsPropertyParsingTest.java | 16 ++++++++++ .../BuildHintAnnotationProcessorTest.java | 32 +++++++++++++++++++ 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index f3aab9f9de2..33fd4cf6d10 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -824,10 +824,16 @@ private boolean declares(File f, String pkg, String simple) { return false; } boolean kotlin = f.getName().endsWith(".kt"); + // TOP LEVEL, which a single-segment nested path is exactly the test for. + // An application's main class is not nested, and accepting one at any + // depth stopped the search on a leftover Main.kt holding + // `class Outer { class Main }` -- the annotations were then inserted on + // Outer, and the verification build rejected the placement and rolled the + // migration back. return pkg.equals(com.codename1.maven.processors.BuildHintAnnotationProcessor .declaredPackageIn(text, kotlin)) && com.codename1.maven.processors.BuildHintAnnotationProcessor - .declaresType(text, simple, kotlin); + .declaresNestedPath(text, new String[] {simple}, kotlin); } /** diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 269c1ed54a6..80961f242e9 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -338,18 +338,29 @@ private static boolean matches(File f, String pkg, String simple, String[] neste /// Braces are counted on the blanked text, where every comment and string /// literal is already spaces, so a brace inside either cannot throw the /// nesting off. - static boolean declaresNestedPath(String text, String[] path) { + public static boolean declaresNestedPath(String text, String[] path) { return declaresNestedPath(text, path, false); } - static boolean declaresNestedPath(String text, String[] path, boolean kotlin) { + public static boolean declaresNestedPath(String text, String[] path, boolean kotlin) { String code = blankNonCode(text, kotlin); int from = 0; int end = code.length(); - for (String segment : path) { + for (int p = 0; p < path.length; p++) { + String segment = path[p]; int at = declarationOf(code, segment, from, end); if (at < 0) { - return false; + // Kotlin builds a local class's binary name out of the enclosing + // FUNCTION names -- a Wrong declared in Main.start() is + // Main$start$Wrong, with nothing to mark `start` as synthetic. So + // past the outermost type a Kotlin segment that is not a declared + // type is inconclusive, not proof of an orphan: concluding orphan + // drops a live annotated class silently, while keeping a stale one + // costs a placement error the developer can see and act on. + // + // The outermost segment is still required, since that is the type + // the file declares and the whole match rests on it. + return kotlin && p > 0; } int open = code.indexOf('{', at); if (open < 0 || open >= end) { @@ -666,7 +677,7 @@ public static String declaredPackageIn(String text, boolean kotlin) { // one physical line saw an empty remainder and reported the default // package -- so a live class looked like it belonged somewhere else, read // as an orphan, and its misplaced annotation went unreported. - String code = blankNonCode(text); + String code = blankNonCode(text, kotlin); int i = 0; while (i < code.length()) { char c = code.charAt(i); diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 87ac465f76f..33ebea69953 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -25,6 +25,8 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNull; /// Covers the properties parsing in `MigrateBuildHintsMojo`. @@ -252,4 +254,18 @@ public void withNoAnnotationTheAnchorIsTheEnd() { String head = "/* copyright */\n\n"; assertEquals(head.length(), MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); } + + /// The migration's source lookup must require a TOP-LEVEL declaration. A + /// leftover Main.kt holding `class Outer { class Main }` otherwise stopped + /// the search, the annotations went onto Outer, and the verification build + /// rejected the placement and rolled the migration back. + @Test + public void onlyATopLevelDeclarationIdentifiesTheMainClass() { + String nested = "package com.example\nclass Outer { class MyApp }\n"; + assertFalse(com.codename1.maven.processors.BuildHintAnnotationProcessor + .declaresNestedPath(nested, new String[] {"MyApp"}, true)); + String topLevel = "package com.example\nclass Outer { }\nclass MyApp\n"; + assertTrue(com.codename1.maven.processors.BuildHintAnnotationProcessor + .declaresNestedPath(topLevel, new String[] {"MyApp"}, true)); + } } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 5244d91258a..ae3a01f3e1e 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -549,6 +549,38 @@ public void anEmptyListElementStillGetsItsSeparator() throws Exception { assertEquals("\napply plugin: 'x'", p.getProperty("codename1.arg.android.xgradle")); } + /// Kotlin builds a local class's binary name out of the enclosing FUNCTION + /// names -- Main$start$Wrong -- with nothing marking `start` as synthetic. + /// Requiring it to be a declared type dropped the live annotated class + /// silently. Past the outermost type a Kotlin segment is inconclusive. + @Test + public void aKotlinLocalClassPathIsInconclusiveNotAnOrphan() { + String kt = "package com.example\n" + + "class Main {\n" + + " fun start() {\n" + + " class Wrong\n" + + " }\n" + + "}\n"; + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + kt, new String[] {"Main", "start", "Wrong"}, true)); + // The outermost type is still required: it is what the file declares. + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + kt, new String[] {"Other", "start", "Wrong"}, true)); + // Java keeps the strict reading, since javac marks its locals with $1. + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + "class Main { void start() { } }", new String[] {"Main", "start", "Wrong"}, + false)); + } + + /// A Kotlin file annotation holding a raw string that ends in a quote closes + /// on a run of four. Reading it by Java's rules blanked the package + /// declaration that followed. + @Test + public void aKotlinRawStringBeforeThePackageDoesNotEatIt() { + String kt = "@file:Suppress(\"\"\"a\"\"\"\")\npackage com.example\nclass MyApp\n"; + assertEquals("com.example", BuildHintAnnotationProcessor.declaredPackageIn(kt, true)); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ From 272939c1a62f7ab24187a331ce606d53145a81bd Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:15:34 +0300 Subject: [PATCH 051/115] Tell a Kotlin function segment from a deleted nested type The previous round's leniency was too broad: past the outermost type ANY missing Kotlin segment counted as inconclusive, so a nested type deleted without a clean kept its orphan and failed the placement check on every incremental build. One silent failure had been traded for a loud permanent one. The two cases are distinguishable, so they are distinguished. An intermediate segment that is not a type is looked up as a `fun` -- which is what Kotlin names a local class after -- and the search descends into its body. Only when it is neither does the benefit of the doubt apply, and only for an intermediate: some other construct names those (an init block, a property accessor) and this does not model them all. The last segment never gets that benefit. It is the class itself, and a nested type that is genuinely gone has to be reported. Nor does the outermost, which is the type the file declares and what the whole match rests on -- my first attempt lost that, and the existing assertion for it failed. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 84 ++++++++++++++++--- .../BuildHintAnnotationProcessorTest.java | 10 +++ 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 80961f242e9..f988cd9201d 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -348,25 +348,39 @@ public static boolean declaresNestedPath(String text, String[] path, boolean kot int end = code.length(); for (int p = 0; p < path.length; p++) { String segment = path[p]; + boolean last = p == path.length - 1; int at = declarationOf(code, segment, from, end); - if (at < 0) { + // p > 0: the outermost segment is the type the file declares and is + // always required. Only what Kotlin may have synthesised BETWEEN it + // and the class gets the benefit of the doubt. + if (at < 0 && kotlin && !last && p > 0) { // Kotlin builds a local class's binary name out of the enclosing // FUNCTION names -- a Wrong declared in Main.start() is - // Main$start$Wrong, with nothing to mark `start` as synthetic. So - // past the outermost type a Kotlin segment that is not a declared - // type is inconclusive, not proof of an orphan: concluding orphan - // drops a live annotated class silently, while keeping a stale one - // costs a placement error the developer can see and act on. - // - // The outermost segment is still required, since that is the type - // the file declares and the whole match rests on it. - return kotlin && p > 0; + // Main$start$Wrong, with nothing to mark `start` as synthetic the + // way javac's $1 does. So an intermediate segment that is not a + // type may be a function, and the class is inside its body. + at = functionDeclarationOf(code, segment, from, end); + if (at < 0) { + // Some other Kotlin construct names an intermediate segment -- + // an init block, a property accessor. Inconclusive, and + // deliberately so: concluding orphan drops a live annotated + // class and loses its hints with no message, while keeping a + // stale one costs a placement error that can be seen and acted + // on. This does NOT extend to the last segment, which is the + // class itself: a nested type that is genuinely gone must be + // reported, or a deleted Main$Wrong keeps its orphan and fails + // every incremental build. + return true; + } + } + if (at < 0) { + return false; } int open = code.indexOf('{', at); if (open < 0 || open >= end) { // A body-less declaration -- Kotlin's `class Foo` -- can only be // the last segment, and if it is not, nothing is nested in it. - return segment.equals(path[path.length - 1]); + return last; } from = open + 1; end = matchingBrace(code, open); @@ -437,6 +451,54 @@ private static boolean isTypeKeyword(String word) { || "object".equals(word) || "record".equals(word); } + /// The offset of a `fun` named `simple` declared directly between `from` and + /// `end`, or -1. + /// + /// Only Kotlin names a local class after its enclosing function, so this is + /// how an intermediate segment is told from a nested type that is gone. + private static int functionDeclarationOf(String code, String simple, int from, int end) { + int depth = 0; + int i = from; + while (i < end && i < code.length()) { + char c = code.charAt(i); + if (c == '{') { + depth++; + i++; + continue; + } + if (c == '}') { + depth--; + i++; + continue; + } + if (depth != 0 || !Character.isJavaIdentifierStart(c) + || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { + i++; + continue; + } + int wordEnd = i; + while (wordEnd < code.length() + && Character.isJavaIdentifierPart(code.charAt(wordEnd))) { + wordEnd++; + } + if ("fun".equals(code.substring(i, wordEnd))) { + int n = wordEnd; + while (n < end && Character.isWhitespace(code.charAt(n))) { + n++; + } + int stop = n; + while (stop < end && Character.isJavaIdentifierPart(code.charAt(stop))) { + stop++; + } + if (code.substring(n, stop).equals(simple)) { + return i; + } + } + i = wordEnd; + } + return -1; + } + /// The index just past the brace closing the one at `open`. private static int matchingBrace(String code, int open) { int depth = 0; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index ae3a01f3e1e..7a440f6c249 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -566,6 +566,16 @@ public void aKotlinLocalClassPathIsInconclusiveNotAnOrphan() { // The outermost type is still required: it is what the file declares. assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( kt, new String[] {"Other", "start", "Wrong"}, true)); + // ...and so is the LAST segment, which is the class itself. Leniency + // there would keep a deleted nested type's orphan and fail every + // incremental build. + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + kt, new String[] {"Main", "start", "Gone"}, true)); + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + "package com.example\nclass Main { }\n", new String[] {"Main", "Wrong"}, true)); + // A function that does not exist is not an excuse either. + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + kt, new String[] {"Main", "start", "Wrong", "Deeper"}, true)); // Java keeps the strict reading, since javac marks its locals with $1. assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( "class Main { void start() { } }", new String[] {"Main", "start", "Wrong"}, From 4cdf81c4bc1ff6d518fd38a0abf88e3565c7ee2d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:30:06 +0300 Subject: [PATCH 052/115] Find the package declaration in code, and clear the whole of it The import anchor was chosen by a raw search for "package ", so a header sentence mentioning the word -- "// The package layout is documented here" -- was selected instead of the declaration, and the import went in above the real statement or inside the comment. The verification build then failed and rolled back a migration that was otherwise correct. It reads the blanked code now, as the rest of this file already does. The same call also cut at the first newline after the keyword, which is inside the statement when it is written `package\ncom.example;`. The anchor clears the whole declaration: the name, an optional semicolon, and the line it ends on -- so Kotlin, which has no semicolon, lands correctly too. The existing-import search was reading raw text as well, so the word `import` in a comment could have been chosen. It reads the blanked copy for the same reason. All three tests fail against the pre-fix code. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 79 +++++++++++++++---- .../MigrateBuildHintsPropertyParsingTest.java | 37 +++++++++ 2 files changed, 100 insertions(+), 16 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 33fd4cf6d10..f0b1350e6d5 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -863,26 +863,26 @@ private void insertAnnotations(File source, String annotations, String simpleNam String head = text.substring(0, declaration); String tail = text.substring(declaration); - int lastImport = head.lastIndexOf("\nimport "); + String blankedHead = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, kotlin); + int lastImport = blankedHead.lastIndexOf("\nimport "); if (lastImport >= 0) { int eol = head.indexOf('\n', lastImport + 1); head = head.substring(0, eol + 1) + importLine + "\n" + head.substring(eol + 1); } else { - // No existing import. Anchor on the package declaration, and when the - // class is in the default package anchor on the class declaration - // instead: indexOf("package ") returns -1 there, and the old - // arithmetic then put the import at the first newline in the file, - // which is inside the copyright comment. The result compiled to - // nothing useful while the properties entries had already been - // deleted. - int pkg = head.indexOf("package "); - // Before any annotation the class already carries. classDeclarationIndex - // points at the modifiers, so `head` ends with an existing - // @SuppressWarnings -- and anchoring on head.length() put the import - // between that annotation and the class, which is not valid in either - // language. The verification build then failed and rolled a correct - // migration back. - int anchor = pkg >= 0 ? head.indexOf('\n', pkg) + 1 + // No existing import. Anchor after the package declaration, and when + // the class is in the default package anchor above any annotation it + // already carries: indexOf returns -1 there, and the old arithmetic + // then put the import at the first newline in the file, which is + // inside the copyright comment. + // + // The package is found in CODE. A header sentence mentioning the word + // -- "// The package layout is documented here" -- was selected by a + // raw search, and the import went in before the real declaration or + // inside the comment itself, so the verification build failed and + // rolled back a migration that was otherwise correct. + int pkg = livePackageIndex(blankedHead); + int anchor = pkg >= 0 ? endOfPackageDeclaration(blankedHead, pkg) : startOfLeadingAnnotations(head, kotlin); head = head.substring(0, anchor) + (pkg >= 0 ? "\n" : "") + importLine + "\n" + (pkg >= 0 ? "" : "\n") + head.substring(anchor); @@ -901,6 +901,53 @@ private void insertAnnotations(File source, String annotations, String simpleNam * the type named by {@code codename1.mainName} is preferred over whatever * happens to appear first.

*/ + /// The offset of the `package` keyword in already-blanked code, or -1. + static int livePackageIndex(String code) { + int i = 0; + while (i < code.length()) { + char c = code.charAt(i); + if (!Character.isJavaIdentifierStart(c) + || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { + i++; + continue; + } + int wordEnd = i; + while (wordEnd < code.length() + && Character.isJavaIdentifierPart(code.charAt(wordEnd))) { + wordEnd++; + } + if ("package".equals(code.substring(i, wordEnd))) { + return i; + } + i = wordEnd; + } + return -1; + } + + /// The index just past the whole package declaration beginning at `pkgAt`. + /// + /// Past the NAME and its optional semicolon, not merely to the next newline: + /// `package\ncom.example;` is valid Java, and cutting at the first newline + /// would have inserted the import into the middle of the statement. + static int endOfPackageDeclaration(String code, int pkgAt) { + int i = pkgAt + "package".length(); + while (i < code.length() && Character.isWhitespace(code.charAt(i))) { + i++; + } + while (i < code.length() + && (Character.isJavaIdentifierPart(code.charAt(i)) || code.charAt(i) == '.')) { + i++; + } + while (i < code.length() && (code.charAt(i) == ' ' || code.charAt(i) == '\t')) { + i++; + } + if (i < code.length() && code.charAt(i) == ';') { + i++; + } + int eol = code.indexOf('\n', i); + return eol < 0 ? code.length() : eol + 1; + } + /// The index in `head` where the run of annotations immediately preceding the /// declaration begins, or `head.length()` when there is none. /// diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 33ebea69953..ec1a34daa10 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -268,4 +268,41 @@ public void onlyATopLevelDeclarationIdentifiesTheMainClass() { assertTrue(com.codename1.maven.processors.BuildHintAnnotationProcessor .declaresNestedPath(topLevel, new String[] {"MyApp"}, true)); } + + /// The word "package" in a header sentence is not the package declaration. A + /// raw search selected it and the import went in before the real statement, + /// or inside the comment, so the verification build failed and rolled back an + /// otherwise correct migration. + @Test + public void theWordPackageInACommentIsNotTheDeclaration() { + String head = "// The package layout is documented here\npackage com.example;\n\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + assertEquals(head.indexOf("package com.example"), + MigrateBuildHintsMojo.livePackageIndex(code)); + } + + /// The anchor is past the whole declaration, not at the first newline: + /// `package\ncom.example;` is valid Java and cutting there would put the + /// import inside the statement. + @Test + public void theAnchorClearsAMultiLinePackageDeclaration() { + String head = "package\ncom.example;\nclass X {}\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + int pkg = MigrateBuildHintsMojo.livePackageIndex(code); + assertEquals(head.indexOf("class X"), + MigrateBuildHintsMojo.endOfPackageDeclaration(code, pkg)); + } + + /// Kotlin has no semicolon; the declaration ends with the name. + @Test + public void aKotlinPackageDeclarationEndsAtItsName() { + String head = "package com.example\nclass X\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, true); + int pkg = MigrateBuildHintsMojo.livePackageIndex(code); + assertEquals(head.indexOf("class X"), + MigrateBuildHintsMojo.endOfPackageDeclaration(code, pkg)); + } } From 5397cf75c5af0ddf5aa3cedb9c1d8a5062e2c556 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:41:36 +0300 Subject: [PATCH 053/115] Treat a modifier on an earlier line as part of the declaration `public\nclass Main` is legal, and the backward walk over modifiers crossed only spaces and tabs -- so it stopped at the line break, `public` stayed in the head, and the generated import was written after it. That is not valid Java, so the verification build failed and rolled back a migration that was otherwise correct. It crosses any whitespace now. Comments need no special case: this runs on blanked code, where they are already spaces. The walk still stops at the first word that is not a modifier, so it cannot wander back into a preceding declaration -- there is a test for that as well as for the reported case. Both fail against the pre-fix code. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 8 ++++++- .../MigrateBuildHintsPropertyParsingTest.java | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index f0b1350e6d5..79586e75a1c 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1078,11 +1078,17 @@ private static boolean isTypeKind(String word, boolean kotlin) { /// Back up over the modifiers preceding the keyword at `at`, so the /// annotations land above `public final class` rather than inside it. + /// + /// Across any whitespace, newlines included. `public\nclass Main` is legal, + /// and stopping at the line break left `public` in the head -- so the + /// generated import was written after it, which is not valid Java, and the + /// verification build rolled back a migration that was otherwise correct. + /// Comments are already spaces here, since this runs on blanked code. private static int startOfModifiers(String code, int at) { int start = at; while (true) { int i = start - 1; - while (i >= 0 && (code.charAt(i) == ' ' || code.charAt(i) == '\t')) { + while (i >= 0 && Character.isWhitespace(code.charAt(i))) { i--; } if (i < 0) { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index ec1a34daa10..f8b6a9c6ae4 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -305,4 +305,25 @@ public void aKotlinPackageDeclarationEndsAtItsName() { assertEquals(head.indexOf("class X"), MigrateBuildHintsMojo.endOfPackageDeclaration(code, pkg)); } + + /// `public\nclass Main` is legal. Stopping at the line break left `public` in + /// the head, so the generated import was written after it -- not valid Java, + /// and the verification build rolled the migration back. + @Test + public void modifiersOnEarlierLinesArePartOfTheDeclaration() { + String src = "public\nfinal\nclass MyApp {\n}\n"; + assertEquals(0, MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); + + String annotated = "@Deprecated\npublic\nclass MyApp {\n}\n"; + assertEquals(annotated.indexOf("public"), + MigrateBuildHintsMojo.classDeclarationIndex(annotated, false, "MyApp")); + } + + /// ...and the walk still stops at a word that is not a modifier. + @Test + public void aNonModifierWordStopsTheWalk() { + String src = "interface Other {}\npublic class MyApp {}\n"; + assertEquals(src.indexOf("public class"), + MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); + } } From b80eadf623a1cd97671568d4c67db9adca5c0dd9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:51:31 +0300 Subject: [PATCH 054/115] Add a hint at the value the build already uses, and let a conflict be fixed Two from the same review, both in the Settings UI. Add seeded a type-wide placeholder and ignored the default the catalog now carries, so clicking it wrote a value the project did not have: android.NotificationChannel.importance defaults to 2 and Add persisted 0, silencing the channel before the user typed anything. It uses the builder's own default when the catalog records one. A boolean with no recorded default still seeds `true`, since adding a boolean hint is how you turn something on. A hint declared in the properties file AND owned by an annotation is the build failure this feature exists to report -- and the row was replacing every control with a read-only warning, so Settings could name the problem and offered no way to fix it. The remove button stays for that state. The value remains uneditable, because editing it only moves the conflict, while removing the declaration is exactly the resolution. The warning says so. The button moved into a helper the ordinary editor and the conflict row share; a second copy is what left the conflict row with none. SettingsThemeTest asserts on the source text and was updated to match the call, not the intent -- where the control sits is unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 62 +++++++++++++++---- .../settings/BuildHintCatalogTest.java | 17 +++++ .../codename1/settings/SettingsThemeTest.java | 5 +- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index e9bf625f953..67f3155a591 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -699,14 +699,32 @@ private Component hintRow(BuildHintMetadata meta) { if (ownedBy != null) { // Set by an annotation on the main class. Editing it here would write a // second declaration and the next build would refuse the project, so the - // value is shown and the controls are withheld. - TextArea owned = new TextArea("Set by " + ownedBy + " on the main class. " - + "Change it there -- declaring it here as well fails the build."); + // value is shown and the editor is withheld. + boolean duplicate = active; + TextArea owned = new TextArea(duplicate + ? "Declared BOTH here and by " + ownedBy + " on the main class. The next " + + "build refuses that. Remove the properties declaration with the button " + + "on the right, or delete the annotation attribute." + : "Set by " + ownedBy + " on the main class. " + + "Change it there -- declaring it here as well fails the build."); owned.setUIID(uiid("SettingsRowText")); owned.setEditable(false); owned.setFocusable(false); text.add(owned); - row.add(text); + if (duplicate) { + // The properties declaration exists AND an annotation owns the + // hint, so this row is the build failure. Withholding every + // control left the tool able to report the problem and unable to + // fix it; the value stays uneditable -- editing it would only + // move the conflict -- but removing it is exactly the resolution, + // so that button stays. + Container header = new Container(new BorderLayout()); + header.add(BorderLayout.CENTER, text); + header.add(BorderLayout.EAST, removeHintButton(meta)); + row.add(header); + } else { + row.add(text); + } } else if (active) { text.add(activeHintEditor(meta, value, effectiveType)); } else { @@ -771,14 +789,7 @@ private Component activeHintEditor(BuildHintMetadata meta, String value, BuildHi }); controls.add(BorderLayout.CENTER, valueField); } - Button remove = new Button("", uiid("SettingsSmallIconButton")); - remove.setMaterialIcon(FontImage.MATERIAL_DELETE, 2.2f); - remove.addActionListener(e -> { - settings.removeBuildHint(meta.name()); - renderBuildHintsList(); - animatePage(); - }); - controls.add(BorderLayout.EAST, remove); + controls.add(BorderLayout.EAST, removeHintButton(meta)); editor.add(editorLayout.createConstraint(0, 0).widthPercentage(72), new Container()); editor.add(editorLayout.createConstraint(0, 1).widthPercentage(28), controls); return editor; @@ -797,8 +808,35 @@ private boolean hasBuildHint(String key) { return settings.keys().contains(SettingsProperties.fullBuildHintKey(key)); } + /// Deletes this hint's properties declaration. + /// + /// One implementation, used by the ordinary editor and by the conflict row: + /// removing the declaration is the resolution in both, and the second copy + /// this replaced was the reason the conflict row had no way out at all. + private Button removeHintButton(BuildHintMetadata meta) { + Button remove = new Button("", uiid("SettingsSmallIconButton")); + remove.setMaterialIcon(FontImage.MATERIAL_DELETE, 2.2f); + remove.addActionListener(e -> { + settings.removeBuildHint(meta.name()); + renderBuildHintsList(); + animatePage(); + }); + return remove; + } + private String defaultHintValue(BuildHintMetadata meta) { + // The builder's OWN default, when the catalog records one. Seeding a + // type-wide placeholder instead writes a value the project did not have: + // android.NotificationChannel.importance defaults to 2, and Add + // persisting 0 silences the channel before the user has typed anything. + // Adding a hint should start from what the build already does. + String catalogDefault = meta.defaultValue(); + if (catalogDefault != null && catalogDefault.length() > 0) { + return catalogDefault; + } if (meta.type() == BuildHintType.BOOLEAN) { + // No recorded default. `true` is still the useful seed here, since + // adding a boolean hint is how you turn something on. return "true"; } if (meta.type() == BuildHintType.INTEGER) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 02e1f1fe52e..ef8fc8a1f78 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -720,4 +720,21 @@ public void aCommentBetweenPackageAndItsNameIsSkipped() { assertTrue(CodenameOneSettings.declaresClass( "package\n com.example\nclass MyApp\n", "MyApp", "com.example", true)); } + + /// Adding a hint must start from what the build already does. Seeding a + /// type-wide placeholder wrote a value the project did not have: + /// android.NotificationChannel.importance defaults to 2, and persisting 0 + /// silences the channel before the user has typed anything. + @Test + public void theCatalogCarriesTheBuildersOwnDefault() { + BuildHintMetadata importance = + BuildHintCatalog.load().get("android.NotificationChannel.importance"); + assertNotNull(importance); + assertEquals("2", importance.defaultValue()); + + BuildHintMetadata pods = BuildHintCatalog.load().get("ios.pods"); + assertNotNull(pods); + assertTrue(pods.defaultValue() == null || pods.defaultValue().isEmpty(), + "a hint with no builder default must not invent one"); + } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java index df308511628..edcd91bc988 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/SettingsThemeTest.java @@ -133,7 +133,10 @@ public void activeBuildHintControlsStayAlignedToTheRight() throws Exception { String source = Files.readString(APP_SOURCE, StandardCharsets.UTF_8); assertTrue(source.contains("widthPercentage(72), new Container()")); assertTrue(source.contains("widthPercentage(28), controls")); - assertTrue(source.contains("controls.add(BorderLayout.EAST, remove)")); + // The delete control is EAST of the editor. The button itself moved into + // removeHintButton so the conflict row can reuse it -- what this asserts + // is where it sits, which is unchanged. + assertTrue(source.contains("controls.add(BorderLayout.EAST, removeHintButton(meta))")); } @Test From 57850e5fee3e95dfee4962bb1038135487cf9f86 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:00:14 +0300 Subject: [PATCH 055/115] Record no default where the build has none, and read a build package Two from the same review, the first a consequence of seeding catalog defaults. facebook.appId carried a default of 706695982682332, mined from a fallback deep in the builders. Both of them decide whether Facebook support is in the app at all by asking whether the hint is NULL, so that literal is only reached once the feature is already on and is never what the build uses by default. Its own doc row said "defaults to null" while the def said otherwise. Harmless until Add started seeding defaults; then clicking the row enabled Facebook integration against an unrelated shared app ID. Audited the other 495 rows for the same contradiction -- a recorded default whose documentation says there is none -- and this was the only one. The Settings source walk skipped every directory named `build`, which is an ordinary package name; this repository has com.codename1.build.shared, and the same assumption in .gitignore is what made the annotation package uncommittable earlier in this branch. A main class in such a package could not be read at all, so ownership fell back to the manifest and a newly added annotation looked unowned. `build` and `target` are skipped only outside a source tree now, where they really are output directories. Co-Authored-By: Claude Opus 5 (1M context) --- .../annotations/buildhints/Build.java | 2 +- .../build/shared/BuildHintsGeneral.java | 7 +++++- .../settings/CodenameOneSettings.java | 22 +++++++++++++++--- .../settings/BuildHintCatalogTest.java | 23 +++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/Build.java b/CodenameOne/src/com/codename1/annotations/buildhints/Build.java index 031fac834db..f27777d3518 100644 --- a/CodenameOne/src/com/codename1/annotations/buildhints/Build.java +++ b/CodenameOne/src/com/codename1/annotations/buildhints/Build.java @@ -44,7 +44,7 @@ /// The application ID for an app that requires native Facebook login /// integration, this defaults to null which means native Facebook support /// shouldn't be in the app - String facebookAppId() default "706695982682332"; + String facebookAppId() default ""; /// The Android/chrome push identifier, see the push section for more details String gcmSenderId() default ""; diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java index e1d7b1328f6..c96693cc6cc 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsGeneral.java @@ -170,10 +170,15 @@ static void register(List h) { .platform("general") .consumedBy("AndroidGradleBuilder", "IPhoneBuilder")); + // NO default, whatever the literal at the call site says. Both builders + // decide whether Facebook support is in the app at all by asking whether + // this hint is null, so the 706695982682332 further down is a fallback + // reached only once the feature is already on -- never a value the build + // uses by default. Recording it made Add enable Facebook integration + // against an unrelated shared app ID the moment the row was clicked. h.add(new Hint("facebook.appId") .annotatedAs(HintGroup.GENERAL, "facebookAppId") .type(HintType.STRING) - .def("706695982682332") .platform("general") .consumedBy("AndroidGradleBuilder", "IPhoneBuilder") .doc("The application ID for an app that requires native Facebook login integration, this " diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 67f3155a591..f35a67883cb 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2328,9 +2328,18 @@ private String findMainClassSource(String projectDir, String main, String pkg) { String name = child.endsWith("/") ? child.substring(0, child.length() - 1) : child; String path = dir + "/" + name; if (FileSystemStorage.getInstance().isDirectory(ProjectIO.fsUrl(path))) { - // target/ holds compiled copies of these same sources; walking - // it would find a generated stub and call it the main class. - if (!"target".equals(name) && !"build".equals(name) && !name.startsWith(".")) { + // target/ and build/ hold compiled copies of these same + // sources, and walking one would find a generated stub and + // call it the main class. + // + // Unless we are already inside a source tree, where `build` is + // an ordinary package name -- this repository has + // com.codename1.build.shared -- and refusing to descend meant + // a main class living there could not be read at all. An + // output directory is never nested under src/. + boolean output = ("target".equals(name) || "build".equals(name)) + && !insideSourceTree(dir); + if (!output && !name.startsWith(".")) { queue.add(path); } continue; @@ -2349,6 +2358,13 @@ private String findMainClassSource(String projectDir, String main, String pkg) { return hit != null ? hit : firstDeclaring(others, main, pkg, 400); } + /// Whether `dir` is under a source root, where `build` is a package name + /// rather than an output directory. + static boolean insideSourceTree(String dir) { + String normalised = dir.replace('\\', '/'); + return normalised.contains("/src/") || normalised.endsWith("/src"); + } + /// The text of the first of `paths` that declares `main` in `pkg`, opening at /// most `budget` of them. private String firstDeclaring(java.util.List paths, String main, String pkg, diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index ef8fc8a1f78..85c7351eab5 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -737,4 +737,27 @@ public void theCatalogCarriesTheBuildersOwnDefault() { assertTrue(pods.defaultValue() == null || pods.defaultValue().isEmpty(), "a hint with no builder default must not invent one"); } + + /// `build` is an ordinary package name -- this repository has + /// com.codename1.build.shared -- so refusing to descend into it meant a main + /// class living there could not be read. An output directory is never nested + /// under src/. + @Test + public void buildIsAPackageNameInsideASourceTree() { + assertTrue(CodenameOneSettings.insideSourceTree("/p/common/src/main/kotlin/com/example")); + assertTrue(CodenameOneSettings.insideSourceTree("/p/common/src")); + assertFalse(CodenameOneSettings.insideSourceTree("/p/common")); + assertFalse(CodenameOneSettings.insideSourceTree("/p/common/target/classes")); + } + + /// facebook.appId has no default: both builders decide whether Facebook + /// support is in the app by asking whether the hint is null, so seeding the + /// literal from the call site enabled the integration against an unrelated + /// shared app ID the moment Add was clicked. + @Test + public void facebookAppIdHasNoDefault() { + BuildHintMetadata meta = BuildHintCatalog.load().get("facebook.appId"); + assertNotNull(meta); + assertTrue(meta.defaultValue() == null || meta.defaultValue().isEmpty()); + } } From 7c0c2030afa3f1c068a94d62ed21ff682f0bfa53 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:08:51 +0300 Subject: [PATCH 056/115] Read a qualified import component by component `import com.codename1.annotations. /* generated */ buildhints.Ios;` is legal Java, and reading the name as one contiguous run of identifier characters and dots stopped at the separator and recorded only the prefix. The import went unrecognised, so the live @Ios read as somebody else's, Settings offered the hint for Add, and the next build refused the duplicate. Parsed component by component now, stepping over whitespace and comments around each dot, with the wildcard still ending the name. Both directions were affected, and the second is the one that would have hurt more quietly: an explicit FOREIGN import written the same way stopped short too, so it no longer shadowed our wildcard and their annotation was read as ours -- the opposite error, from the same line of code. There is a test for each, and both fail against the pre-fix code. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 35 ++++++++++++++++--- .../settings/BuildHintCatalogTest.java | 25 +++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index f35a67883cb..9639507f207 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2531,12 +2531,37 @@ static java.util.List importsIn(String source, boolean kotlin) { if (i < 0) { return out; } + // Component by component, stepping over whitespace and comments + // around each dot. `import com.codename1.annotations. /* x */ + // buildhints.Ios;` is legal, and reading the name as one contiguous + // run stopped at the separator and recorded only the prefix -- so the + // import was not recognised and the live @Ios read as somebody + // else's. StringBuilder name = new StringBuilder(); - while (i < source.length() - && (continuesAName(source.charAt(i)) || source.charAt(i) == '.' - || source.charAt(i) == '*')) { - name.append(source.charAt(i)); - i++; + while (i >= 0 && i < source.length()) { + if (source.charAt(i) == '*') { + name.append('*'); + i++; + break; + } + int end = i; + while (end < source.length() && continuesAName(source.charAt(end))) { + end++; + } + if (end == i) { + break; + } + name.append(source, i, end); + int dot = nextLiveChar(source, end, kotlin); + if (dot < 0 || source.charAt(dot) != '.') { + i = end; + break; + } + name.append('.'); + i = nextLiveChar(source, dot + 1, kotlin); + } + if (i < 0) { + i = source.length(); } String alias = null; int a = nextLiveChar(source, i, kotlin); diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 85c7351eab5..ebac05c2f69 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -760,4 +760,29 @@ public void facebookAppIdHasNoDefault() { assertNotNull(meta); assertTrue(meta.defaultValue() == null || meta.defaultValue().isEmpty()); } + + /// A qualified import may carry whitespace or a comment around any dot. + /// Reading the name as one contiguous run stopped at the separator and + /// recorded only the prefix, so the import went unrecognised and the live + /// @Ios read as somebody else's. + @Test + public void aQualifiedImportMaySpanSeparators() { + assertTrue(CodenameOneSettings.importsAnnotation( + "import com.codename1.annotations. /* generated */ buildhints.Ios;\n", + "Ios", false)); + assertTrue(CodenameOneSettings.importsAnnotation( + "import com.codename1\n .annotations\n .buildhints\n .*;\n", + "Ios", false)); + assertEquals("BuildIos", CodenameOneSettings.kotlinImportAlias( + "import com.codename1.annotations . buildhints . Ios as BuildIos\n", + "Ios", true)); + } + + /// ...and a foreign import spanning separators still shadows ours. + @Test + public void aForeignImportSpanningSeparatorsStillShadows() { + assertFalse(CodenameOneSettings.importsAnnotation( + "import com.codename1.annotations.buildhints.*;\n" + + "import com.example . other . Ios;\n", "Ios", false)); + } } From 78ee230a9f86908af84a83d34b6c745604d13b9f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:19:17 +0300 Subject: [PATCH 057/115] Ask for a value when the build has none to inherit Seeding catalog defaults made Add correct for a hint that has one and wrong for a hint that does not. android.targetSDKVersion has no fixed default -- the server uses the highest platform it has installed when the line is ABSENT -- so writing the integer placeholder does not create an unset hint, it overrides that choice with a value nobody made. `0` selects the legacy android-14 target and emits targetSdkVersion="0". The same was true of strings, less visibly: an empty value is still a present one, and presence is the switch for facebook.appId, so Add would have enabled Facebook with no ID. So defaultHintValue returns null when there is nothing safe to write, and the row asks. Add reveals a field; Save validates, canonicalises and persists, and refuses an empty value naming the hint. Nothing is written until there is something to write. A boolean keeps its `true` seed: two values are the whole domain, so that is a real choice rather than a placeholder. The hint's own documentation said "defaults to 21" while the builder uses maxPlatformVersion. That is precisely the defect this PR exists to remove -- a sentence describing something the build does not do -- so it now says the build server uses its highest installed platform. Co-Authored-By: Claude Opus 5 (1M context) --- .../annotations/buildhints/Android.java | 7 +- .../impl/javase/BuildHintCatalogDefaults.java | 2 +- .../build/shared/BuildHintsAndroid.java | 7 +- .../settings/CodenameOneSettings.java | 81 +++++++++++++++---- .../settings/BuildHintCatalogTest.java | 27 +++++++ 5 files changed, 100 insertions(+), 24 deletions(-) diff --git a/CodenameOne/src/com/codename1/annotations/buildhints/Android.java b/CodenameOne/src/com/codename1/annotations/buildhints/Android.java index a308905c7fe..50d7ed12eaa 100644 --- a/CodenameOne/src/com/codename1/annotations/buildhints/Android.java +++ b/CodenameOne/src/com/codename1/annotations/buildhints/Android.java @@ -121,9 +121,10 @@ /// Values are joined with `\n` when the hint is written. String[] repositories() default {}; - /// Indicates the Android SDK used to compile the Android build defaults to 21. - /// Notice that not all targets will work since the source might have some - /// limitations and not all SDK targets are installed on the build servers. + /// The Android SDK the build compiles against. Unset, the build server uses the + /// highest platform it has installed, so leaving this alone tracks the server + /// rather than pinning a number. Not every target works: the source may have + /// limitations, and not all SDK targets are installed. int targetSDKVersion() default 0; /// `auto`, `modern` / `material`, `hololight` (default for existing apps), diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java index a52fcccc77f..bcfa12d8761 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintCatalogDefaults.java @@ -333,7 +333,7 @@ static void register() { if (!handWritten.contains("android.targetSDKVersion")) { set("{{#Android#android.targetSDKVersion}}.label", "Target sDKVersion"); set("{{#Android#android.targetSDKVersion}}.type", "TextField"); - set("{{#Android#android.targetSDKVersion}}.description", "Indicates the Android SDK used to compile the Android build defaults to 21. Notice that not all targets will work since the source might have some limitations and not all SDK targets are installed on the build servers."); + set("{{#Android#android.targetSDKVersion}}.description", "The Android SDK the build compiles against. Unset, the build server uses the highest platform it has installed, so leaving this alone tracks the server rather than pinning a number. Not every target works: the source may have limitations, and not all SDK targets are installed."); } if (!handWritten.contains("and.themeMode")) { set("{{#Android#and.themeMode}}.label", "Theme mode"); diff --git a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java index 6dc89e194aa..da6159c6a8d 100644 --- a/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java +++ b/maven/build-hint-catalog/src/main/java/com/codename1/build/shared/BuildHintsAndroid.java @@ -1348,9 +1348,10 @@ static void register(List h) { .type(HintType.INT) .platform("android") .consumedBy("AndroidGradleBuilder") - .doc("Indicates the Android SDK used to compile the Android build defaults to 21. Notice that " - + "not all targets will work since the source might have some limitations and not all SDK " - + "targets are installed on the build servers.")); + .doc("The Android SDK the build compiles against. Unset, the build server uses the " + + "highest platform it has installed, so leaving this alone tracks the " + + "server rather than pinning a number. Not every target works: the source " + + "may have limitations, and not all SDK targets are installed.")); h.add(new Hint("android.textureView") .group(HintGroup.ANDROID) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 9639507f207..ac64e389a5a 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -732,12 +732,55 @@ private Component hintRow(BuildHintMetadata meta) { controls.setUIID(uiid("SettingsHintEditor")); Button add = new Button("Add", uiid("SettingsOutline")); add.setMaterialIcon(FontImage.MATERIAL_ADD, 1.2f); - add.addActionListener(e -> { - settings.setBuildHint(meta.name(), defaultHintValue(meta)); - renderBuildHintsList(); - animatePage(); - }); - controls.add(add); + String seed = defaultHintValue(meta); + if (seed != null) { + add.addActionListener(e -> { + settings.setBuildHint(meta.name(), seed); + renderBuildHintsList(); + animatePage(); + }); + controls.add(add); + } else { + // Nothing safe to seed. The build decides this hint's value + // itself when the line is ABSENT -- android.targetSDKVersion is + // computed from the installed platforms -- so writing a + // placeholder does not create an unset hint, it overrides the + // computation with a value nobody chose. `0` there selects the + // legacy android-14 target and emits targetSdkVersion="0"; an + // empty string is no better for a hint whose presence is the + // switch, which is how facebook.appId=\"\" would enable Facebook + // with no ID. + // + // So the row asks for a value and writes nothing until it has + // one. Add is what reveals the field, not what saves. + TextField pending = new TextField("", "value"); + pending.setUIID(uiid("SettingsField")); + configureHintField(pending, meta); + Button save = new Button("Save", uiid("SettingsOutline")); + save.addActionListener(e -> { + String typed = pending.getText() == null ? "" : pending.getText().trim(); + if (typed.length() == 0) { + ToastBar.showErrorMessage(meta.name() + + " has no default -- enter the value you want."); + return; + } + if (!isValidHintValue(meta, typed)) { + ToastBar.showErrorMessage(typed + " is not a valid value for " + + meta.name() + "."); + return; + } + settings.setBuildHint(meta.name(), canonicalHintValue(meta, typed)); + renderBuildHintsList(); + animatePage(); + }); + add.addActionListener(e -> { + controls.removeAll(); + controls.add(pending).add(save); + controls.getComponentForm().revalidate(); + pending.startEditingAsync(); + }); + controls.add(add); + } Container header = new Container(new BorderLayout()); header.add(BorderLayout.CENTER, text); header.add(BorderLayout.EAST, controls); @@ -824,25 +867,29 @@ private Button removeHintButton(BuildHintMetadata meta) { return remove; } + /// The value Add should write, or null when there is nothing safe to write. + /// + /// The builder's OWN default when the catalog records one: seeding a + /// type-wide placeholder instead writes a value the project did not have -- + /// android.NotificationChannel.importance defaults to 2, and Add persisting 0 + /// silences the channel before the user has typed anything. Adding a hint + /// should start from what the build already does. + /// + /// Null when it records none, because for those the ABSENCE of the line is + /// itself the configuration -- android.targetSDKVersion is computed from the + /// installed platforms when unset, and a placeholder overrides that + /// computation rather than leaving it alone. A boolean is the one exception: + /// its two values are the whole domain, so `true` is a real choice and is + /// what adding the hint means. private String defaultHintValue(BuildHintMetadata meta) { - // The builder's OWN default, when the catalog records one. Seeding a - // type-wide placeholder instead writes a value the project did not have: - // android.NotificationChannel.importance defaults to 2, and Add - // persisting 0 silences the channel before the user has typed anything. - // Adding a hint should start from what the build already does. String catalogDefault = meta.defaultValue(); if (catalogDefault != null && catalogDefault.length() > 0) { return catalogDefault; } if (meta.type() == BuildHintType.BOOLEAN) { - // No recorded default. `true` is still the useful seed here, since - // adding a boolean hint is how you turn something on. return "true"; } - if (meta.type() == BuildHintType.INTEGER) { - return "0"; - } - return ""; + return null; } private int descriptionRows(String text) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index ebac05c2f69..92fbc5545ab 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -785,4 +785,31 @@ public void aForeignImportSpanningSeparatorsStillShadows() { "import com.codename1.annotations.buildhints.*;\n" + "import com.example . other . Ios;\n", "Ios", false)); } + + /// For a hint whose value the build computes when the line is ABSENT, there + /// is nothing safe to seed. android.targetSDKVersion has no catalog default, + /// and writing 0 does not create an unset hint -- it overrides the + /// computation, selecting the legacy android-14 target and emitting + /// targetSdkVersion="0". + @Test + public void aHintWithNoDefaultHasNothingSafeToSeed() { + BuildHintCatalog catalog = BuildHintCatalog.load(); + BuildHintMetadata target = catalog.get("android.targetSDKVersion"); + assertNotNull(target); + assertTrue(target.defaultValue() == null || target.defaultValue().isEmpty(), + "the catalog must not invent a default the builder computes"); + + BuildHintMetadata facebook = catalog.get("facebook.appId"); + assertNotNull(facebook); + assertTrue(facebook.defaultValue() == null || facebook.defaultValue().isEmpty(), + "presence is the switch, so an empty seed would enable the feature"); + } + + /// ...while a hint the builder does have a default for is seeded with it. + @Test + public void aHintWithADefaultIsSeededWithIt() { + assertEquals("2", + BuildHintCatalog.load().get("android.NotificationChannel.importance") + .defaultValue()); + } } From 504062182f56becf6b9c3cdcfb7c688a515cbeb6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:28:34 +0300 Subject: [PATCH 058/115] Read a package name the way an import is read, and see a live misplacement Three from the same review. Two of them are one bug in two parsers: `package com /* generated */ . example;` is legal, and both readers took the name as a contiguous run of identifier characters and dots, so they stopped at the separator and recorded `com`. The processor then read a live class as belonging elsewhere -- an orphan, dropped before the placement check -- and Settings rejected the real main source and fell back to the manifest. This is the separator fix I made for imports last round and did not carry to package names; both read component by component now. The third undoes an over-narrowing of my own. Restricting the guard to the main class made it immune to stale output, and also blind to a LIVE annotation on another class: @Target(TYPE) accepts that placement, so with process-annotations unbound the build succeeded having neither applied the hint nor said the annotation was in the wrong place -- the silent failure this feature exists to remove. Staleness is a question about the source, not about which class it is, so it is asked directly: the guard reports another class only when the module's compile source roots still declare it, using the same test the processor uses. When the roots are unknown it stays silent, because an orphan would otherwise fail the build and the processor still refuses the placement whenever it runs. Both parser tests fail against the pre-fix code. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/CN1BuildMojo.java | 55 +++++++++++++++++++ .../BuildHintAnnotationProcessor.java | 54 ++++++++++++++---- .../BuildHintAnnotationProcessorTest.java | 14 +++++ .../settings/CodenameOneSettings.java | 37 ++++++++++--- .../settings/BuildHintCatalogTest.java | 13 +++++ 5 files changed, 153 insertions(+), 20 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index 533fdf13618..44931a5d63c 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -2897,6 +2897,21 @@ private String classCarryingBuildHintAnnotations(List classpathElements, getLog().debug("cn1: could not read " + expectedMain + " from " + element, ex); } } + // The main class carries none. A LIVE class elsewhere still counts: + // @Target(TYPE) accepts the placement, so without this the build + // succeeds having neither applied the hint nor said the annotation is + // in the wrong place -- which is the silent failure the whole feature + // removes. It is only stale output that must not count, and that is a + // question about the source, not about which class it is. + // + // Reported as a misplacement, because that is what it is: had the + // processor run it would have refused the build for this class. + for (String element : classpathElements) { + String live = liveAnnotatedClass(new File(element), descriptors); + if (live != null) { + return live; + } + } return null; } for (String element : classpathElements) { @@ -2921,6 +2936,46 @@ private String classCarryingBuildHintAnnotations(List classpathElements, return null; } + /** + * The first annotated class in this element whose source still exists, or null. + * + *

Stale output is excluded the same way the processor excludes it — + * by asking whether the module's configured source roots still declare the + * class — so an orphan left by a rename cannot fail the build, while a + * class the developer actually wrote does.

+ */ + private String liveAnnotatedClass(File element, java.util.Collection descriptors) { + List roots; + try { + roots = project == null ? null : project.getCompileSourceRoots(); + } catch (RuntimeException ex) { + return null; + } + if (roots == null || roots.isEmpty()) { + // Not told where the sources are, so staleness cannot be judged and + // an orphan would fail the build. Silence is the lesser harm: the + // processor still refuses this placement whenever it runs. + return null; + } + String hit = element.isDirectory() + ? findAnnotatedClass(element, descriptors) + : (element.isFile() && element.getName().endsWith(".jar") + ? findAnnotatedClassInJar(element, descriptors) : null); + if (hit == null) { + return null; + } + try { + com.codename1.maven.annotations.AnnotatedClass cls = readClass(element, hit); + if (cls != null && com.codename1.maven.processors.BuildHintAnnotationProcessor + .hasBackingSource(cls, roots)) { + return hit; + } + } catch (IOException | com.codename1.maven.annotations.ProcessingException ex) { + getLog().debug("cn1: could not read " + hit + " from " + element, ex); + } + return null; + } + /** Whether the named class, read from this classpath element, is annotated. */ private boolean mainClassCarriesAnnotation(File element, String binaryName, java.util.Collection descriptors) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index f988cd9201d..b645049649a 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -223,7 +223,12 @@ private static boolean isMainClass(AnnotatedClass cls, ProcessorContext ctx) { /// differ from the class it declares, so a package-path lookup would call a /// perfectly live class orphaned. private static boolean hasBackingSource(AnnotatedClass cls, ProcessorContext ctx) { - List roots = ctx.getCompileSourceRoots(); + return hasBackingSource(cls, ctx.getCompileSourceRoots()); + } + + /// As above, for a caller that has the roots but no ProcessorContext. + public static boolean hasBackingSource(AnnotatedClass cls, List compileSourceRoots) { + List roots = compileSourceRoots; if (roots == null || roots.isEmpty()) { return true; } @@ -545,6 +550,36 @@ static String[] nestedNameOf(String binaryName) { return path; } + /// The dotted name starting at or after `from`, skipping whitespace around + /// each dot. Blanked code, so comments are whitespace already. + static String qualifiedNameAt(String code, int from) { + int i = from; + StringBuilder name = new StringBuilder(); + while (i < code.length()) { + while (i < code.length() && Character.isWhitespace(code.charAt(i))) { + i++; + } + int end = i; + while (end < code.length() && Character.isJavaIdentifierPart(code.charAt(end))) { + end++; + } + if (end == i) { + break; + } + name.append(code, i, end); + int dot = end; + while (dot < code.length() && Character.isWhitespace(code.charAt(dot))) { + dot++; + } + if (dot >= code.length() || code.charAt(dot) != '.') { + break; + } + name.append('.'); + i = dot + 1; + } + return name.toString(); + } + /// Whether `text` declares a type called `simple`. /// /// Comments and string literals are blanked first: a commented-out @@ -757,17 +792,12 @@ public static String declaredPackageIn(String text, boolean kotlin) { i = wordEnd; continue; } - int n = wordEnd; - while (n < code.length() && Character.isWhitespace(code.charAt(n))) { - n++; - } - StringBuilder name = new StringBuilder(); - while (n < code.length() - && (Character.isJavaIdentifierPart(code.charAt(n)) || code.charAt(n) == '.')) { - name.append(code.charAt(n)); - n++; - } - return name.toString(); + // Component by component. `package com /* generated */ . example;` is + // legal, and reading the name as one contiguous run stopped at the + // separator and recorded `com` -- so a live class looked like it + // belonged elsewhere, read as an orphan, and its misplaced annotation + // went unreported. Comments are already spaces here. + return qualifiedNameAt(code, wordEnd); } return ""; } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 7a440f6c249..38591dcbb5f 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -591,6 +591,20 @@ public void aKotlinRawStringBeforeThePackageDoesNotEatIt() { assertEquals("com.example", BuildHintAnnotationProcessor.declaredPackageIn(kt, true)); } + /// `package com /* generated */ . example;` is legal. Reading the name as one + /// contiguous run recorded `com`, so a live class looked like it belonged + /// elsewhere, read as an orphan, and its misplaced annotation went + /// unreported. + @Test + public void aPackageNameMaySpanSeparators() { + assertEquals("com.example", BuildHintAnnotationProcessor.declaredPackageIn( + "package com /* generated */ . example;\n", false)); + assertEquals("com.example", BuildHintAnnotationProcessor.declaredPackageIn( + "package com\n . example\n", true)); + assertEquals("com.example.deep", BuildHintAnnotationProcessor.declaredPackageIn( + "package com . example . deep ;\n", false)); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index ac64e389a5a..2c1900a1b03 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2456,14 +2456,11 @@ static boolean declaresClass(String text, String main, String pkg, boolean kotli // remainder of the text and trimming it started the name at the // comment -- so the real main source was rejected by both the // conventional lookup and the fallback search. - int i = nextLiveChar(text, after, kotlin); - StringBuilder name = new StringBuilder(); - while (i >= 0 && i < text.length() - && (continuesAName(text.charAt(i)) || text.charAt(i) == '.')) { - name.append(text.charAt(i)); - i++; - } - declaredPkg = name.toString(); + // Component by component, exactly as the import reader does. + // `package com /* generated */ . example;` is legal, and reading + // the name as one contiguous run recorded `com` and rejected the + // real main source. + declaredPkg = qualifiedNameAt(text, after, kotlin); break; } pkgAt = nextMarker(text, "package", after, kotlin); @@ -2545,6 +2542,30 @@ private String readIfPresent(String path) { } } + /// The dotted name starting at or after `from`, stepping over whitespace and + /// comments around each dot. + static String qualifiedNameAt(String source, int from, boolean kotlin) { + int i = nextLiveChar(source, from, kotlin); + StringBuilder name = new StringBuilder(); + while (i >= 0 && i < source.length()) { + int end = i; + while (end < source.length() && continuesAName(source.charAt(end))) { + end++; + } + if (end == i) { + break; + } + name.append(source, i, end); + int dot = nextLiveChar(source, end, kotlin); + if (dot < 0 || source.charAt(dot) != '.') { + break; + } + name.append('.'); + i = nextLiveChar(source, dot + 1, kotlin); + } + return name.toString(); + } + /// One import directive: the dotted name it introduces and its alias, if any. static final class Imported { final String name; diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 92fbc5545ab..ac1b25696ae 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -812,4 +812,17 @@ public void aHintWithADefaultIsSeededWithIt() { BuildHintCatalog.load().get("android.NotificationChannel.importance") .defaultValue()); } + + /// The same separator rule as imports, applied to the package name: reading + /// it as one contiguous run recorded `com` and rejected the real main source. + @Test + public void aPackageNameMaySpanSeparatorsInSettingsToo() { + assertTrue(CodenameOneSettings.declaresClass( + "package com /* generated */ . example;\npublic class MyApp {}\n", + "MyApp", "com.example", false)); + assertTrue(CodenameOneSettings.declaresClass( + "package com\n . example\nclass MyApp\n", "MyApp", "com.example", true)); + assertFalse(CodenameOneSettings.declaresClass( + "package com . other;\npublic class MyApp {}\n", "MyApp", "com.example", false)); + } } From d4e4516ef401c8bacf656b5a73244c6a8ab2972b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:38:33 +0300 Subject: [PATCH 059/115] Name a found class properly, keep looking, and read imports as declarations Four from the same review, two of them defects in the guard I widened last round. findAnnotatedClass returned the class FILE's basename, so com/example/Wrong.class came back as `Wrong`. The message named a class that does not exist, and reading it back by name found nothing -- so the guard I had just taught to look at live non-main classes saw none of them. It returns binary names now. It also returned the first hit and stopped. An incremental output directory can hold a stale annotated class and a live one at once, so whether a real misplacement was reported depended on the order File.listFiles happened to return. Every candidate is considered, and the first with a backing source is the answer. The jar and directory walks are one method now rather than two that differed in what they returned. The migration aborted on `text.contains("com.codename1.annotations.buildhints")`, so a javadoc line mentioning the package stopped a migration on a source that compiles perfectly well and imports nothing. It looks for a live import. And its insertion point after the last import was the first newline after the keyword, which is inside `import java.\n util.List;`. The new import was spliced into the middle of the old one and the verification build rolled back a correct migration. The anchor clears the whole declaration: the name read component by component, a Kotlin `as` alias, an optional semicolon, and the line it ends on. That name walk is now one implementation returning either the text or its end, because a second copy that agreed about the name but not about where it stopped is exactly what this bug was. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/CN1BuildMojo.java | 132 ++++++++++-------- .../maven/MigrateBuildHintsMojo.java | 92 +++++++++++- .../BuildHintAnnotationProcessor.java | 48 +++++-- .../MigrateBuildHintsPropertyParsingTest.java | 47 +++++++ 4 files changed, 244 insertions(+), 75 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index 44931a5d63c..ed37aadc883 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -2914,23 +2914,13 @@ private String classCarryingBuildHintAnnotations(List classpathElements, } return null; } + // A reactor `package` build hands us the dependency module's jar rather + // than its output directory, which findAnnotatedClasses handles alongside + // a directory -- that is exactly the shape this check has to work in. for (String element : classpathElements) { - File f = new File(element); - if (f.isDirectory()) { - String hit = findAnnotatedClass(f, descriptors); - if (hit != null) { - return hit; - } - continue; - } - // A reactor `package` build hands us the dependency module's jar - // rather than its output directory, which is exactly the shape this - // check has to work in. - if (f.isFile() && f.getName().endsWith(".jar")) { - String hit = findAnnotatedClassInJar(f, descriptors); - if (hit != null) { - return hit; - } + String hit = findAnnotatedClass(new File(element), descriptors); + if (hit != null) { + return hit; } } return null; @@ -2957,21 +2947,19 @@ private String liveAnnotatedClass(File element, java.util.Collection des // processor still refuses this placement whenever it runs. return null; } - String hit = element.isDirectory() - ? findAnnotatedClass(element, descriptors) - : (element.isFile() && element.getName().endsWith(".jar") - ? findAnnotatedClassInJar(element, descriptors) : null); - if (hit == null) { - return null; - } - try { - com.codename1.maven.annotations.AnnotatedClass cls = readClass(element, hit); - if (cls != null && com.codename1.maven.processors.BuildHintAnnotationProcessor - .hasBackingSource(cls, roots)) { - return hit; + // Every candidate, not the first: rejecting one stale class must not end + // the search, or whether a live misplacement is reported depends on the + // order the directory happened to be listed in. + for (String hit : findAnnotatedClasses(element, descriptors)) { + try { + com.codename1.maven.annotations.AnnotatedClass cls = readClass(element, hit); + if (cls != null && com.codename1.maven.processors.BuildHintAnnotationProcessor + .hasBackingSource(cls, roots)) { + return hit; + } + } catch (IOException | com.codename1.maven.annotations.ProcessingException ex) { + getLog().debug("cn1: could not read " + hit + " from " + element, ex); } - } catch (IOException | com.codename1.maven.annotations.ProcessingException ex) { - getLog().debug("cn1: could not read " + hit + " from " + element, ex); } return null; } @@ -3004,28 +2992,6 @@ private boolean mainClassCarriesAnnotation(File element, String binaryName, return false; } - private String findAnnotatedClassInJar(File jar, java.util.Collection descriptors) { - try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(jar)) { - java.util.Enumeration entries = zip.entries(); - while (entries.hasMoreElements()) { - java.util.zip.ZipEntry entry = entries.nextElement(); - if (entry.isDirectory() || !entry.getName().endsWith(".class")) { - continue; - } - try (InputStream in = zip.getInputStream(entry)) { - String name = carriesBuildHintAnnotation(in, descriptors) - ? entry.getName() : null; - if (name != null) { - return name.substring(0, name.length() - ".class".length()) - .replace('/', '.'); - } - } - } - } catch (IOException | RuntimeException ex) { - getLog().debug("cn1: could not scan " + jar + ": " + ex.getMessage()); - } - return null; - } private boolean carriesBuildHintAnnotation(InputStream in, java.util.Collection descriptors) @@ -3049,16 +3015,59 @@ public org.objectweb.asm.AnnotationVisitor visitAnnotation( } private String findAnnotatedClass(File dir, java.util.Collection descriptors) { + List all = findAnnotatedClasses(dir, descriptors); + return all.isEmpty() ? null : all.get(0); + } + + /** + * Every annotated class under this element, by BINARY name. + * + *

The binary name, not the file's own: returning {@code Wrong} for + * {@code com/example/Wrong.class} made the message name a class that does not + * exist, and made re-reading it by name fail, so the guard saw nothing.

+ * + *

All of them, not the first: an incremental output directory can hold a + * stale annotated class and a live one at once, and stopping at whichever + * {@code File.listFiles} returned first made the answer depend on directory + * order.

+ */ + private List findAnnotatedClasses(File element, java.util.Collection descriptors) { + List out = new ArrayList(); + if (element.isDirectory()) { + collectAnnotatedClasses(element, element, descriptors, out); + } else if (element.isFile() && element.getName().endsWith(".jar")) { + try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(element)) { + java.util.Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + java.util.zip.ZipEntry entry = entries.nextElement(); + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; + } + try (InputStream in = zip.getInputStream(entry)) { + if (carriesBuildHintAnnotation(in, descriptors)) { + out.add(entry.getName() + .substring(0, entry.getName().length() - ".class".length()) + .replace('/', '.')); + } + } + } + } catch (IOException | RuntimeException ex) { + getLog().debug("cn1: could not scan " + element + ": " + ex.getMessage()); + } + } + return out; + } + + private void collectAnnotatedClasses(File root, File dir, + java.util.Collection descriptors, + List out) { File[] children = dir.listFiles(); if (children == null) { - return null; + return; } for (File f : children) { if (f.isDirectory()) { - String hit = findAnnotatedClass(f, descriptors); - if (hit != null) { - return hit; - } + collectAnnotatedClasses(root, f, descriptors, out); continue; } if (!f.getName().endsWith(".class")) { @@ -3066,13 +3075,18 @@ private String findAnnotatedClass(File dir, java.util.Collection descrip } try (InputStream in = new FileInputStream(f)) { if (carriesBuildHintAnnotation(in, descriptors)) { - return f.getName().substring(0, f.getName().length() - ".class".length()); + String rel = f.getAbsolutePath() + .substring(root.getAbsolutePath().length()) + .replace(File.separatorChar, '/'); + while (rel.startsWith("/")) { + rel = rel.substring(1); + } + out.add(rel.substring(0, rel.length() - ".class".length()).replace('/', '.')); } } catch (IOException | RuntimeException ex) { getLog().debug("cn1: could not scan " + f + ": " + ex.getMessage()); } } - return null; } /** diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 79586e75a1c..5f379121354 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -850,7 +850,13 @@ private void insertAnnotations(File source, String annotations, String simpleNam String importLine = kotlin ? "import com.codename1.annotations.buildhints.*" : "import com.codename1.annotations.buildhints.*;"; - if (text.contains("com.codename1.annotations.buildhints")) { + String blanked = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(text, kotlin); + // A live import, not the words anywhere in the file. A comment or a + // string mentioning the package -- a javadoc line about build hints, say + // -- aborted the migration on a source that compiles perfectly well and + // has no import at all. + if (importsBuildHints(blanked)) { throw new IOException(source.getName() + " already imports the build hint " + "annotations; migrate the remaining hints by hand so nothing is " + "overwritten."); @@ -865,10 +871,14 @@ private void insertAnnotations(File source, String annotations, String simpleNam String blankedHead = com.codename1.maven.processors.BuildHintAnnotationProcessor .blankNonCode(head, kotlin); - int lastImport = blankedHead.lastIndexOf("\nimport "); + int lastImport = lastImportIndex(blankedHead); if (lastImport >= 0) { - int eol = head.indexOf('\n', lastImport + 1); - head = head.substring(0, eol + 1) + importLine + "\n" + head.substring(eol + 1); + // After the whole declaration. `import java.\n util.List;` is legal + // and the first newline after the keyword is inside it, so cutting + // there spliced the new import into the middle of the old one and the + // verification build rolled a correct migration back. + int at = endOfImportDeclaration(blankedHead, lastImport); + head = head.substring(0, at) + importLine + "\n" + head.substring(at); } else { // No existing import. Anchor after the package declaration, and when // the class is in the default package anchor above any annotation it @@ -901,6 +911,80 @@ private void insertAnnotations(File source, String annotations, String simpleNam * the type named by {@code codename1.mainName} is preferred over whatever * happens to appear first.

*/ + /// Whether blanked `code` contains a live import of the build hint package. + static boolean importsBuildHints(String code) { + for (int at = importKeywordAt(code, 0); at >= 0; + at = importKeywordAt(code, at + "import".length())) { + String name = com.codename1.maven.processors.BuildHintAnnotationProcessor + .qualifiedNameAt(code, at + "import".length()); + if (name.startsWith("com.codename1.annotations.buildhints")) { + return true; + } + } + return false; + } + + /// The offset of the last live `import` keyword in blanked `code`, or -1. + static int lastImportIndex(String code) { + int last = -1; + for (int at = importKeywordAt(code, 0); at >= 0; + at = importKeywordAt(code, at + "import".length())) { + last = at; + } + return last; + } + + private static int importKeywordAt(String code, int from) { + int i = from; + while (i < code.length()) { + char c = code.charAt(i); + if (!Character.isJavaIdentifierStart(c) + || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { + i++; + continue; + } + int end = i; + while (end < code.length() && Character.isJavaIdentifierPart(code.charAt(end))) { + end++; + } + if ("import".equals(code.substring(i, end))) { + return i; + } + i = end; + } + return -1; + } + + /// The index just past the import declaration beginning at `importAt`. + static int endOfImportDeclaration(String code, int importAt) { + int i = importAt + "import".length(); + // The name, then an optional Kotlin `as` alias, then an optional + // semicolon, then the rest of that line. + for (int pass = 0; pass < 2; pass++) { + // Component by component: `import java.\n util.List;` is legal, and a + // contiguous run stops at the newline in the middle of the name. + i = com.codename1.maven.processors.BuildHintAnnotationProcessor + .qualifiedNameEnd(code, i); + int probe = i; + while (probe < code.length() && Character.isWhitespace(code.charAt(probe))) { + probe++; + } + if (!code.startsWith("as", probe) || probe + 2 >= code.length() + || Character.isJavaIdentifierPart(code.charAt(probe + 2))) { + break; + } + i = probe + 2; + } + while (i < code.length() && (code.charAt(i) == ' ' || code.charAt(i) == '\t')) { + i++; + } + if (i < code.length() && code.charAt(i) == ';') { + i++; + } + int eol = code.indexOf('\n', i); + return eol < 0 ? code.length() : eol + 1; + } + /// The offset of the `package` keyword in already-blanked code, or -1. static int livePackageIndex(String code) { int i = 0; diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index b645049649a..d52f83232d6 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -552,32 +552,56 @@ static String[] nestedNameOf(String binaryName) { /// The dotted name starting at or after `from`, skipping whitespace around /// each dot. Blanked code, so comments are whitespace already. - static String qualifiedNameAt(String code, int from) { - int i = from; + public static String qualifiedNameAt(String code, int from) { StringBuilder name = new StringBuilder(); + readQualifiedName(code, from, name); + return name.toString(); + } + + /// The index just past the dotted name starting at or after `from`, or + /// `from` when there is none. The same walk as {@link #qualifiedNameAt}, so + /// the two cannot disagree about where a name ends. + public static int qualifiedNameEnd(String code, int from) { + return readQualifiedName(code, from, null); + } + + private static int readQualifiedName(String code, int from, StringBuilder name) { + int i = from; + int end = from; while (i < code.length()) { while (i < code.length() && Character.isWhitespace(code.charAt(i))) { i++; } - int end = i; - while (end < code.length() && Character.isJavaIdentifierPart(code.charAt(end))) { - end++; + int stop = i; + if (stop < code.length() && code.charAt(stop) == '*') { + if (name != null) { + name.append('*'); + } + return stop + 1; + } + while (stop < code.length() && Character.isJavaIdentifierPart(code.charAt(stop))) { + stop++; } - if (end == i) { - break; + if (stop == i) { + return end; } - name.append(code, i, end); - int dot = end; + if (name != null) { + name.append(code, i, stop); + } + end = stop; + int dot = stop; while (dot < code.length() && Character.isWhitespace(code.charAt(dot))) { dot++; } if (dot >= code.length() || code.charAt(dot) != '.') { - break; + return end; + } + if (name != null) { + name.append('.'); } - name.append('.'); i = dot + 1; } - return name.toString(); + return end; } /// Whether `text` declares a type called `simple`. diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index f8b6a9c6ae4..5351e9241d1 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -326,4 +326,51 @@ public void aNonModifierWordStopsTheWalk() { assertEquals(src.indexOf("public class"), MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); } + + /// The words in a comment are not an import. A javadoc line mentioning the + /// package aborted the migration on a source that compiles perfectly well. + @Test + public void mentioningThePackageIsNotImportingIt() { + String mention = com.codename1.maven.processors.BuildHintAnnotationProcessor.blankNonCode( + "// see com.codename1.annotations.buildhints for the annotations\n" + + "public class MyApp {}\n", false); + assertFalse(MigrateBuildHintsMojo.importsBuildHints(mention)); + + String real = com.codename1.maven.processors.BuildHintAnnotationProcessor.blankNonCode( + "import com.codename1.annotations.buildhints.Ios;\npublic class MyApp {}\n", + false); + assertTrue(MigrateBuildHintsMojo.importsBuildHints(real)); + } + + /// An import may legally span lines, so the insertion point is the end of the + /// DECLARATION. Cutting at the first newline after the keyword spliced the + /// new import into the middle of the old one. + @Test + public void theInsertionPointClearsAMultiLineImport() { + String head = "import java.\n util.List;\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + int last = MigrateBuildHintsMojo.lastImportIndex(code); + assertEquals(head.length(), MigrateBuildHintsMojo.endOfImportDeclaration(code, last)); + } + + /// A Kotlin alias belongs to the declaration too. + @Test + public void theInsertionPointClearsAKotlinAlias() { + String head = "import com.example.Ios as TheirIos\nclass X\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, true); + int last = MigrateBuildHintsMojo.lastImportIndex(code); + assertEquals(head.indexOf("class X"), + MigrateBuildHintsMojo.endOfImportDeclaration(code, last)); + } + + /// The LAST import is the anchor, not the first. + @Test + public void theAnchorIsTheLastImport() { + String head = "import a.B;\nimport c.D;\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + assertEquals(head.indexOf("import c.D"), MigrateBuildHintsMojo.lastImportIndex(code)); + } } From f3194ba50163b112ec4d95999cd822216a0b2fc2 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:50:02 +0300 Subject: [PATCH 060/115] Close a Kotlin block comment where Kotlin closes it, and read @Name as tokens Two from the same review, in both source scanners each time. Kotlin block comments NEST and Java's do not, and both scanners assumed Java. In `/* docs /* sample */ package old.name */` the comment ended at the inner `*/`, so `package old.name` was read as live code: the processor decided a class belonged elsewhere and dropped a live annotated one as an orphan, and Settings rejected the real main source. Depth is tracked when the source is Kotlin, and deliberately not when it is Java, where the inner `*/` really does close it -- the tests assert both readings of the same text. The fully qualified annotation was matched as one contiguous literal, so `@com.codename1.annotations. /* generated */ buildhints.Ios(...)` was not seen. Ownership then read as empty, and since the manifest is only trusted for hints the source confirms, the hint looked unowned and Add wrote the duplicate. The marker loop is gone. Rather than three literal spellings, every `@` that is real code is visited and the name after it is read component by component, then compared against the imported simple name, the fully qualified name and the Kotlin alias. That is one place where the question "is this our annotation" is answered, instead of three strings that each had to be spelled correctly, and it is what makes the separator case work for all three spellings at once. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 32 +++-- .../BuildHintAnnotationProcessorTest.java | 13 ++ .../settings/CodenameOneSettings.java | 118 +++++++++++------- .../settings/BuildHintCatalogTest.java | 40 ++++++ 4 files changed, 149 insertions(+), 54 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index d52f83232d6..d9842e03582 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -644,21 +644,33 @@ public static String blankNonCode(String text, boolean kotlin) { out[i++] = ' '; } } else if (c == '/' && i + 1 < out.length && out[i + 1] == '*') { - out[i++] = ' '; - out[i++] = ' '; - while (i < out.length && !(out[i] == '*' && i + 1 < out.length - && out[i + 1] == '/')) { + // Kotlin block comments NEST; Java's do not. Stopping at the + // first */ in Kotlin ends the comment early, and the text after + // it -- `package old.name */` in a commented-out block -- is then + // read as live code, so a class looks like it belongs elsewhere + // and a live annotated one is dropped as an orphan. + int depth = 0; + while (i < out.length) { + if (out[i] == '/' && i + 1 < out.length && out[i + 1] == '*') { + depth++; + out[i++] = ' '; + out[i++] = ' '; + continue; + } + if (out[i] == '*' && i + 1 < out.length && out[i + 1] == '/') { + depth--; + out[i++] = ' '; + out[i++] = ' '; + if (depth == 0 || !kotlin) { + break; + } + continue; + } if (out[i] != '\n') { out[i] = ' '; } i++; } - if (i < out.length) { - out[i++] = ' '; - } - if (i < out.length) { - out[i++] = ' '; - } } else if (c == '\'') { // A char literal. '{' is legal and would otherwise be counted as // syntax, so the nesting scan loses its place and reads a live diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 38591dcbb5f..faace9697fa 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -605,6 +605,19 @@ public void aPackageNameMaySpanSeparators() { "package com . example . deep ;\n", false)); } + /// Kotlin block comments NEST; Java's do not. Stopping at the first `*/` in + /// Kotlin ended the comment early, so a commented-out package declaration was + /// read as live code and a class looked like it belonged elsewhere. + @Test + public void aNestedKotlinBlockCommentStaysClosed() { + String kt = "/* docs /* sample */ package old.name */\n" + + "package com.example\nclass MyApp\n"; + assertEquals("com.example", BuildHintAnnotationProcessor.declaredPackageIn(kt, true)); + // Java does not nest, so there the inner `*/` really does close it and + // `package old.name` is what follows. + assertEquals("old.name", BuildHintAnnotationProcessor.declaredPackageIn(kt, false)); + } + // ------------------------------------------------------------------ // helpers // ------------------------------------------------------------------ diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 2c1900a1b03..6fb8c62f48c 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2542,6 +2542,30 @@ private String readIfPresent(String path) { } } + /// The index just past the dotted name starting at or after `from`, or + /// `from` when there is none. The same walk as `qualifiedNameAt`, so the two + /// cannot disagree about where a name ends. + static int qualifiedNameEnd(String source, int from, boolean kotlin) { + int i = nextLiveChar(source, from, kotlin); + int end = from; + while (i >= 0 && i < source.length()) { + int stop = i; + while (stop < source.length() && continuesAName(source.charAt(stop))) { + stop++; + } + if (stop == i) { + return end; + } + end = stop; + int dot = nextLiveChar(source, stop, kotlin); + if (dot < 0 || source.charAt(dot) != '.') { + return end; + } + i = nextLiveChar(source, dot + 1, kotlin); + } + return end; + } + /// The dotted name starting at or after `from`, stepping over whitespace and /// comments around each dot. static String qualifiedNameAt(String source, int from, boolean kotlin) { @@ -2722,52 +2746,34 @@ static void collectAnnotationOwnedHints(String source, java.util.Map markerList = new java.util.ArrayList(); - if (imported) { - markerList.add("@" + simple); - } - markerList.add("@com.codename1.annotations.buildhints." + simple); - if (alias != null) { - markerList.add("@" + alias); - } - String[] markers = markerList.toArray(new String[markerList.size()]); + String qualified = "com.codename1.annotations.buildhints." + simple; + + // Every `@` that is real code, with the name after it read component + // by component. Matching literal strings could not see + // `@com.codename1.annotations. /* generated */ buildhints.Ios`, which + // is legal -- ownership then read as empty and Add wrote the + // duplicate. + int at = nextMarker(source, "@", 0, kotlin); boolean found = false; - for (int m = 0; m < markers.length && !found; m++) { - // Found by walking the source rather than by indexOf, so a - // commented-out annotation or one quoted in a string is not read - // as live code. That mattered in the direction nobody would - // notice: a `// @Ios(teamId = "old")` left behind made Settings - // treat the hint as annotation-owned and withhold Add and the - // editor, for a hint the processor never emits. - int at = nextMarker(source, markers[m], 0, kotlin); - while (at >= 0) { - // "@Ios" must not match "@IosPrivacy": the next character has - // to end the name. - int after = at + markers[m].length(); - if (after < source.length() && continuesAName(source.charAt(after))) { - at = nextMarker(source, markers[m], after, kotlin); - continue; - } - // The annotation's OWN argument list, not the next one in - // the file. Parentheses are optional -- a bare `@Ios` is - // legal -- and searching forward then adopted whatever call - // came next, so `@Ios` above a `configure(teamId = "...")` - // read as owning ios.teamId and Settings withheld controls - // for a hint the processor never emits. + while (at >= 0 && !found) { + String name = qualifiedNameAt(source, at + 1, kotlin); + int after = qualifiedNameEnd(source, at + 1, kotlin); + boolean ours = (imported && name.equals(simple)) + || name.equals(qualified) + || (alias != null && name.equals(alias)); + if (ours) { int open = nextLiveChar(source, after, kotlin); - if (open < 0 || source.charAt(open) != '(') { - at = nextMarker(source, markers[m], after, kotlin); - continue; + if (open >= 0 && source.charAt(open) == '(') { + String args = balancedArgs(source, open, kotlin); + if (args != null && declaresAttribute(args, h.attr(), kotlin)) { + out.put(com.codename1.build.shared.BuildHints.canonicalName(h.name()), + "@" + simple + "(" + h.attr() + ")"); + found = true; + break; + } } - String args = balancedArgs(source, open, kotlin); - if (args != null && declaresAttribute(args, h.attr(), kotlin)) { - out.put(com.codename1.build.shared.BuildHints.canonicalName(h.name()), - "@" + simple + "(" + h.attr() + ")"); - found = true; - break; - } - at = nextMarker(source, markers[m], after, kotlin); } + at = nextMarker(source, "@", at + 1, kotlin); } } } @@ -2969,8 +2975,32 @@ private static int skipNonCode(String s, int i, boolean kotlin) { return nl < 0 ? s.length() : nl; } if (n == '*') { - int close = s.indexOf("*/", i + 2); - return close < 0 ? s.length() : close + 2; + // Kotlin block comments NEST; Java's do not. Stopping at the + // first */ in Kotlin ends the comment early and the rest of it is + // then read as live code. + if (!kotlin) { + int close = s.indexOf("*/", i + 2); + return close < 0 ? s.length() : close + 2; + } + int depth = 0; + int j = i; + while (j < s.length()) { + if (s.charAt(j) == '/' && j + 1 < s.length() && s.charAt(j + 1) == '*') { + depth++; + j += 2; + continue; + } + if (s.charAt(j) == '*' && j + 1 < s.length() && s.charAt(j + 1) == '/') { + depth--; + j += 2; + if (depth == 0) { + return j; + } + continue; + } + j++; + } + return s.length(); } } return i; diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index ac1b25696ae..edc79b2a38e 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -825,4 +825,44 @@ public void aPackageNameMaySpanSeparatorsInSettingsToo() { assertFalse(CodenameOneSettings.declaresClass( "package com . other;\npublic class MyApp {}\n", "MyApp", "com.example", false)); } + + /// A fully qualified annotation may carry separators between components, and + /// matching a contiguous literal could not see it -- ownership read as empty + /// and Add wrote the duplicate. + @Test + public void aQualifiedAnnotationMaySpanSeparators() { + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints( + "@com.codename1.annotations. /* generated */ buildhints.Ios(teamId = \"X\")\n" + + "public class MyApp {}\n", out, false); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + + out.clear(); + CodenameOneSettings.collectAnnotationOwnedHints( + "@com.codename1.annotations\n .buildhints\n .Ios(teamId = \"X\")\n" + + "class MyApp\n", out, true); + assertEquals("@Ios(teamId)", out.get("ios.teamId")); + } + + /// Another library's qualified annotation of the same simple name is not ours + /// however it is spaced. + @Test + public void aQualifiedForeignAnnotationIsStillNotOurs() { + java.util.Map out = new java.util.HashMap(); + CodenameOneSettings.collectAnnotationOwnedHints( + "@com.example . other . Ios(teamId = \"X\")\npublic class MyApp {}\n", + out, false); + assertNull(out.get("ios.teamId")); + } + + /// Kotlin block comments NEST. Stopping at the first `*/` ended the comment + /// early, so a commented-out package declaration was read as live code. + @Test + public void aNestedKotlinBlockCommentStaysClosed() { + String kt = "/* docs /* sample */ package old.name */\n" + + "package com.example\nclass MyApp\n"; + assertTrue(CodenameOneSettings.declaresClass(kt, "MyApp", "com.example", true)); + // Java does not nest, so the same text really does end at the inner `*/`. + assertFalse(CodenameOneSettings.declaresClass(kt, "MyApp", "com.example", false)); + } } From 5957d2efe1fa736c565ed20c3a9be1e208f84070 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:59:11 +0300 Subject: [PATCH 061/115] Read the manifest out of the app's own jar, and the package name whole Two from the same review. The import anchor read the package name as a contiguous run, so `package com.\nexample;` ended it at the newline and the import was inserted before `example;` -- invalid source, and the verification build rolled back a correct migration. It uses the shared component-wise reader now, the same one the import anchor uses, so the two cannot disagree about where a name ends. The simulator only looked in classpath DIRECTORIES. I had skipped jars on the grounds that a jar's manifest belongs to whoever built it -- true of a library, and not true of the case that matters: the javase-only nested build the simulator runs resolves the application's OWN common module as a dependency artifact, so its manifest has no directory anywhere. desktop.titleBar and nativeTheme were then simply absent under cn1:run while the device build applied them, which is the asymmetry this publishing step exists to remove. Jars are read, and the stamp is what tells the app's from a library's: one is accepted only when it was generated for this application's main class, so a dependency carrying its own manifest is passed over rather than picked and then rejected. The timestamp staleness check is skipped for a jar, because the class and the manifest were written by the same build into the same archive and cannot disagree. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 149 ++++++++++++++---- .../maven/MigrateBuildHintsMojo.java | 15 +- .../MigrateBuildHintsPropertyParsingTest.java | 13 ++ 3 files changed, 136 insertions(+), 41 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index fca1228cc72..ec228a60440 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -490,40 +490,28 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat if (projectDir == null) { return; } - File f = findAnnotationManifest(projectDir, classPathStr); - if (f == null) { + String expectedMain = configuredMainClass(projectDir); + FoundManifest found = findAnnotationManifest(projectDir, classPathStr, expectedMain); + if (found == null) { return; } - java.util.Properties p = new java.util.Properties(); - FileInputStream in = null; - try { - in = new FileInputStream(f); - p.load(in); - } catch (IOException ex) { - System.err.println("Warning: could not read " + f + ": " + ex.getMessage()); - return; - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException ignored) { - // read-only stream; nothing useful to do - } - } - } + java.util.Properties p = found.hints; + File f = found.file; String stampedFor = p.getProperty("cn1.buildHints.mainClass"); - String expectedMain = configuredMainClass(projectDir); if (stampedFor != null && expectedMain != null && !stampedFor.equals(expectedMain)) { // Somebody else's configuration. codename1.mainName changing without a // clean build leaves the old class and its manifest together in the // output directory, and the timestamp check finds that pair perfectly // consistent -- so without the stamp the simulator runs the previous // application's hints. The native merge already refuses this. - System.err.println("Warning: " + f + " was generated for " + stampedFor + System.err.println("Warning: " + found.where + " was generated for " + stampedFor + ", not " + expectedMain + ", so its build hints were NOT applied."); return; } - File staleAgainst = classNewerThanManifest(p, f); + // Only for a manifest on disk. Inside a jar the class and the manifest + // were written by the same build into the same archive, so they cannot + // disagree and there is nothing to compare. + File staleAgainst = f == null ? null : classNewerThanManifest(p, f); if (staleAgainst != null) { // Nothing removes target/classes between builds, so a project that ran // process-annotations once and then stopped -- goal unbound, skipped, @@ -540,7 +528,7 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat // the direction that matters -- process-classes always follows compile // within a build, so a main class newer than the manifest cannot have // produced it. - System.err.println("Warning: " + f + " is older than " + System.err.println("Warning: " + found.where + " is older than " + staleAgainst.getName() + ", so it was produced by an earlier build " + "and its build hints were NOT applied."); System.err.println(" Rebuild the project so the cn1 process-annotations " @@ -695,9 +683,31 @@ private static String configuredMainClass(File projectDir) { * path is tried first, since it is right for almost every project and costs * one stat.

*/ - private static File findAnnotationManifest(File projectDir, String classPathStr) { + /** + * A manifest that was found, and where. + * + *

Not a File, because it can live inside a jar: the javase-only nested + * build the simulator runs resolves the application's own common module as a + * dependency artifact, so its manifest has no path of its own.

+ */ + private static final class FoundManifest { + final java.util.Properties hints; + /** The file on disk, or null when it came out of a jar. */ + final File file; + final String where; + + FoundManifest(java.util.Properties hints, File file, String where) { + this.hints = hints; + this.file = file; + this.where = where; + } + } + + private static FoundManifest findAnnotationManifest(File projectDir, String classPathStr, + String expectedMain) { String resource = "META-INF" + File.separator + "codenameone" + File.separator + "build-hints.properties"; + String entryName = "META-INF/codenameone/build-hints.properties"; // The classpath first, because it is the output the build is ACTUALLY // using. Trying the conventional path first looked harmless and is not: // a project that moves to a configured output directory without running @@ -712,16 +722,32 @@ private static File findAnnotationManifest(File projectDir, String classPathStr) continue; } File dir = new File(entry); - if (!dir.isDirectory()) { - // A jar can carry this resource too, but only as a dependency -- - // and a dependency's hints belong to whoever built it, which the - // main-class stamp exists to reject. Directories are this - // project's own output. + if (dir.isDirectory()) { + File candidate = new File(dir, resource); + if (candidate.isFile()) { + java.util.Properties loaded = readProperties(candidate); + if (loaded != null) { + return new FoundManifest(loaded, candidate, candidate.toString()); + } + } continue; } - File candidate = new File(dir, resource); - if (candidate.isFile()) { - return candidate; + // A jar too. The javase-only nested build the simulator runs + // resolves the application's OWN common module as a dependency + // artifact, so its manifest has no directory anywhere -- skipping + // jars meant desktop.titleBar and nativeTheme were simply absent + // under cn1:run while the device build applied them. + // + // The stamp is what tells that jar from a library's: a jar is + // accepted only when it was generated for THIS application's main + // class, so a dependency carrying its own manifest is passed over + // rather than picked and then rejected. + if (dir.isFile() && entry.endsWith(".jar") && expectedMain != null) { + java.util.Properties loaded = readJarEntry(dir, entryName); + if (loaded != null + && expectedMain.equals(loaded.getProperty("cn1.buildHints.mainClass"))) { + return new FoundManifest(loaded, null, entryName + " in " + dir.getName()); + } } } } @@ -729,7 +755,64 @@ private static File findAnnotationManifest(File projectDir, String classPathStr) // module's output directory at all still finds a conventional build. File conventional = new File(projectDir, "target" + File.separator + "classes" + File.separator + resource); - return conventional.isFile() ? conventional : null; + if (!conventional.isFile()) { + return null; + } + java.util.Properties loaded = readProperties(conventional); + return loaded == null ? null + : new FoundManifest(loaded, conventional, conventional.toString()); + } + + /** Loads a properties file, or null when it cannot be read. */ + private static java.util.Properties readProperties(File f) { + java.util.Properties p = new java.util.Properties(); + FileInputStream in = null; + try { + in = new FileInputStream(f); + p.load(in); + return p; + } catch (IOException ex) { + System.err.println("Warning: could not read " + f + ": " + ex.getMessage()); + return null; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + // read-only stream; nothing useful to do + } + } + } + } + + /** Loads one entry of a jar as properties, or null when it is not there. */ + private static java.util.Properties readJarEntry(File jar, String entryName) { + java.util.zip.ZipFile zip = null; + try { + zip = new java.util.zip.ZipFile(jar); + java.util.zip.ZipEntry entry = zip.getEntry(entryName); + if (entry == null) { + return null; + } + java.util.Properties p = new java.util.Properties(); + java.io.InputStream in = zip.getInputStream(entry); + try { + p.load(in); + } finally { + in.close(); + } + return p; + } catch (IOException ex) { + return null; + } finally { + if (zip != null) { + try { + zip.close(); + } catch (IOException ignored) { + // read-only archive + } + } + } } /** diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 5f379121354..c160f4cc91c 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1014,14 +1014,13 @@ static int livePackageIndex(String code) { /// `package\ncom.example;` is valid Java, and cutting at the first newline /// would have inserted the import into the middle of the statement. static int endOfPackageDeclaration(String code, int pkgAt) { - int i = pkgAt + "package".length(); - while (i < code.length() && Character.isWhitespace(code.charAt(i))) { - i++; - } - while (i < code.length() - && (Character.isJavaIdentifierPart(code.charAt(i)) || code.charAt(i) == '.')) { - i++; - } + // Component by component. `package com.\nexample;` is legal, and a + // contiguous scan stops at the newline -- so the import was inserted + // before `example;` and the verification build rolled back a correct + // migration. Same reader the import anchor uses, so the two cannot + // disagree about where a name ends. + int i = com.codename1.maven.processors.BuildHintAnnotationProcessor + .qualifiedNameEnd(code, pkgAt + "package".length()); while (i < code.length() && (code.charAt(i) == ' ' || code.charAt(i) == '\t')) { i++; } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 5351e9241d1..057259850cc 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -373,4 +373,17 @@ public void theAnchorIsTheLastImport() { .blankNonCode(head, false); assertEquals(head.indexOf("import c.D"), MigrateBuildHintsMojo.lastImportIndex(code)); } + + /// `package com.\nexample;` is legal, and a contiguous scan stops at the + /// newline -- so the import was inserted before `example;`, producing invalid + /// source, and the verification build rolled back a correct migration. + @Test + public void theAnchorClearsAPackageNameThatSpansLines() { + String head = "package com.\nexample;\nclass X {}\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + int pkg = MigrateBuildHintsMojo.livePackageIndex(code); + assertEquals(head.indexOf("class X"), + MigrateBuildHintsMojo.endOfPackageDeclaration(code, pkg)); + } } From e75b8a4b93248aebc620d17099cf0eb948b1f1d2 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:12:45 +0300 Subject: [PATCH 062/115] Descend through Kotlin companions, and skip the static import modifier Two source-parsing shortcuts, both found by review. An unnamed `companion object` is `Companion` in the binary name and is spelled with no name at all in the source, so nothing there matches that segment. The intermediate-segment leniency then accepted the whole path without ever checking the class at the end of it, so a deleted `Main$Companion$Wrong` kept its orphan and failed every incremental build until the output directory was cleaned. The companion is now recognised as a scope and the walk continues into it; a named companion already went through the ordinary lookup, and an intermediate segment nothing accounts for is still inconclusive. `import static a.b.C.d;` passed `static` to the qualified-name reader as if it were the imported name, so the declaration ended at the newline inside the real name and the generated import was spliced into the middle of `import static java.util.\n Collections.emptyList;`. The optional modifier is consumed first, on a whole-token match so that `import staticky.Thing;` is untouched. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 13 ++++ .../BuildHintAnnotationProcessor.java | 68 +++++++++++++++++++ .../MigrateBuildHintsPropertyParsingTest.java | 23 +++++++ .../BuildHintAnnotationProcessorTest.java | 41 +++++++++++ 4 files changed, 145 insertions(+) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index c160f4cc91c..b368393e1fc 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -958,6 +958,19 @@ private static int importKeywordAt(String code, int from) { /// The index just past the import declaration beginning at `importAt`. static int endOfImportDeclaration(String code, int importAt) { int i = importAt + "import".length(); + // Java's optional `static` first. Passing it to the name reader made + // `static` itself the imported name, and the declaration then ended at + // the newline inside the real name -- so `import static java.util.\n + // Collections.emptyList;` had the generated import spliced into it. + int probeStatic = i; + while (probeStatic < code.length() && Character.isWhitespace(code.charAt(probeStatic))) { + probeStatic++; + } + if (code.startsWith("static", probeStatic) + && probeStatic + 6 < code.length() + && !Character.isJavaIdentifierPart(code.charAt(probeStatic + 6))) { + i = probeStatic + 6; + } // The name, then an optional Kotlin `as` alias, then an optional // semicolon, then the rest of that line. for (int pass = 0; pass < 2; pass++) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index d9842e03582..dcfed42b1c5 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -365,6 +365,17 @@ public static boolean declaresNestedPath(String text, String[] path, boolean kot // way javac's $1 does. So an intermediate segment that is not a // type may be a function, and the class is inside its body. at = functionDeclarationOf(code, segment, from, end); + if (at < 0 && "Companion".equals(segment)) { + // Kotlin's UNNAMED companion object is called Companion in the + // binary name and is declared as `companion object` with no + // name at all, so nothing in the source is spelled Companion. + // Accepting the whole path here on that basis stopped the walk + // before the class itself, and a deleted + // Main$Companion$Wrong then kept its orphan and failed every + // incremental build. Recognised as a scope so the remaining + // segments are still checked. + at = companionObjectAt(code, from, end); + } if (at < 0) { // Some other Kotlin construct names an intermediate segment -- // an init block, a property accessor. Inconclusive, and @@ -456,6 +467,63 @@ private static boolean isTypeKeyword(String word) { || "object".equals(word) || "record".equals(word); } + /// The offset of an unnamed `companion object` declared directly between + /// `from` and `end`, or -1. + /// + /// A NAMED companion -- `companion object Named` -- needs nothing special: + /// its name is what appears in the binary name and the ordinary declaration + /// lookup finds it. + private static int companionObjectAt(String code, int from, int end) { + int depth = 0; + int i = from; + while (i < end && i < code.length()) { + char c = code.charAt(i); + if (c == '{') { + depth++; + i++; + continue; + } + if (c == '}') { + depth--; + i++; + continue; + } + if (depth != 0 || !Character.isJavaIdentifierStart(c) + || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { + i++; + continue; + } + int wordEnd = i; + while (wordEnd < code.length() + && Character.isJavaIdentifierPart(code.charAt(wordEnd))) { + wordEnd++; + } + if ("companion".equals(code.substring(i, wordEnd))) { + int n = wordEnd; + while (n < end && Character.isWhitespace(code.charAt(n))) { + n++; + } + int stop = n; + while (stop < end && Character.isJavaIdentifierPart(code.charAt(stop))) { + stop++; + } + if ("object".equals(code.substring(n, stop))) { + int after = stop; + while (after < end && Character.isWhitespace(code.charAt(after))) { + after++; + } + // Unnamed only: a name here means the binary path carries that + // name instead, and the ordinary lookup has already handled it. + if (after < end && code.charAt(after) == '{') { + return i; + } + } + } + i = wordEnd; + } + return -1; + } + /// The offset of a `fun` named `simple` declared directly between `from` and /// `end`, or -1. /// diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 057259850cc..e52a859aa3c 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -354,6 +354,29 @@ public void theInsertionPointClearsAMultiLineImport() { assertEquals(head.length(), MigrateBuildHintsMojo.endOfImportDeclaration(code, last)); } + /// `static` is a modifier, not the imported name. Reading it as the name + /// ended the declaration at the newline inside the REAL name, so the + /// generated import was spliced into the middle of the static import and the + /// verification build rolled back a valid migration. + @Test + public void theInsertionPointClearsAMultiLineStaticImport() { + String head = "import static java.util.\n Collections.emptyList;\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + int last = MigrateBuildHintsMojo.lastImportIndex(code); + assertEquals(head.length(), MigrateBuildHintsMojo.endOfImportDeclaration(code, last)); + } + + /// A name that merely STARTS with `static` is a name. + @Test + public void anImportOfATypeNamedStaticallyIsNotAStaticImport() { + String head = "import staticky.Thing;\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + int last = MigrateBuildHintsMojo.lastImportIndex(code); + assertEquals(head.length(), MigrateBuildHintsMojo.endOfImportDeclaration(code, last)); + } + /// A Kotlin alias belongs to the declaration too. @Test public void theInsertionPointClearsAKotlinAlias() { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index faace9697fa..4de963f06bc 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -582,6 +582,47 @@ public void aKotlinLocalClassPathIsInconclusiveNotAnOrphan() { false)); } + /// Kotlin's UNNAMED companion object is `Companion` in the binary name and + /// is spelled `companion object` in the source, so nothing there is called + /// Companion. Treating that as inconclusive accepted the whole path without + /// ever checking the class at the end of it, so a deleted + /// `Main$Companion$Wrong` kept its orphan and failed every incremental + /// build until the output directory was cleaned. + @Test + public void anUnnamedCompanionObjectIsAScopeNotAWildcard() { + String kt = "package com.example\n" + + "class Main {\n" + + " companion object {\n" + + " class Wrong\n" + + " }\n" + + "}\n"; + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + kt, new String[] {"Main", "Companion", "Wrong"}, true)); + // The class at the end of the path is still checked. + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + kt, new String[] {"Main", "Companion", "Gone"}, true)); + // No companion at all keeps the surrounding leniency: an intermediate + // segment nothing accounts for is inconclusive, because concluding + // orphan there drops a live annotated class silently while keeping a + // stale one only costs a visible placement error. + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + "package com.example\nclass Main {\n class Wrong\n}\n", + new String[] {"Main", "Companion", "Wrong"}, true)); + + // A NAMED companion carries its own name into the binary name, so the + // ordinary declaration lookup is what applies to it. + String named = "package com.example\n" + + "class Main {\n" + + " companion object Named {\n" + + " class Wrong\n" + + " }\n" + + "}\n"; + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + named, new String[] {"Main", "Named", "Wrong"}, true)); + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + named, new String[] {"Main", "Named", "Gone"}, true)); + } + /// A Kotlin file annotation holding a raw string that ends in a quote closes /// on a run of four. Reading it by Java's rules blanked the package /// declaration that followed. From ca610d7b5addf06d04755e9d71cefdf71ca08e9d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:24:18 +0300 Subject: [PATCH 063/115] Read Kotlin's backtick-escaped names as the names they are Kotlin lets a declaration escape its name in backticks, and the binary name is plainly the text between them -- `class `when`` compiles to a class called when. Both source parsers read the name with the identifier rule, stopped at the backtick and recorded an empty name. In the processor a live annotated type then looked undeclared, so the orphan filter classified it as stale and dropped it before placement validation: the misplaced hints went unreported on a green build. In the Settings tool the main source was rejected, so nothing knew which hints an annotation already owns and Add was offered for one of them -- the duplicate declaration that fails the next build. The function form matters too, since a local class takes the enclosing function's name as a segment of its own binary name. A quote is a legal character in an escaped name, so both scanners now step over the whole escaped identifier rather than reading into it; `class `say"hi`` used to open a literal that swallowed the rest of the file. The name itself is left as the code it is. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 53 +++++++++++++++---- .../BuildHintAnnotationProcessorTest.java | 30 +++++++++++ .../settings/CodenameOneSettings.java | 35 ++++++++++-- .../settings/BuildHintCatalogTest.java | 19 +++++++ 4 files changed, 122 insertions(+), 15 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index dcfed42b1c5..16d0a924eae 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -449,11 +449,7 @@ private static int declarationOf(String code, String simple, int from, int end, while (n < end && Character.isWhitespace(code.charAt(n))) { n++; } - int stop = n; - while (stop < end && Character.isJavaIdentifierPart(code.charAt(stop))) { - stop++; - } - if (code.substring(n, stop).equals(simple)) { + if (simple.equals(simpleNameAt(code, n, end))) { return i; } } @@ -462,6 +458,33 @@ private static int declarationOf(String code, String simple, int from, int end, return -1; } + /// The declared simple name at `n`, or null when the source runs out. + /// + /// Kotlin lets a declaration ESCAPE its name in backticks -- `class `when`` + /// compiles to a class whose binary name is plainly `when`. Reading it with + /// the identifier rule stopped at the backtick and recorded an empty name, + /// so the class looked undeclared: the orphan filter then classified a live + /// annotated type as stale and dropped it before placement validation, and + /// the misplaced hints went unreported on a green build. + /// + /// A backtick cannot appear in Java source at all outside a comment or a + /// literal, both of which are already blanked, so this needs no language + /// flag. + private static String simpleNameAt(String code, int n, int end) { + if (n < end && n < code.length() && code.charAt(n) == '`') { + int close = code.indexOf('`', n + 1); + if (close < 0 || close >= end) { + return null; + } + return code.substring(n + 1, close); + } + int stop = n; + while (stop < end && Character.isJavaIdentifierPart(code.charAt(stop))) { + stop++; + } + return code.substring(n, stop); + } + private static boolean isTypeKeyword(String word) { return "class".equals(word) || "interface".equals(word) || "enum".equals(word) || "object".equals(word) || "record".equals(word); @@ -559,11 +582,10 @@ private static int functionDeclarationOf(String code, String simple, int from, i while (n < end && Character.isWhitespace(code.charAt(n))) { n++; } - int stop = n; - while (stop < end && Character.isJavaIdentifierPart(code.charAt(stop))) { - stop++; - } - if (code.substring(n, stop).equals(simple)) { + // Backticks here too: Kotlin test code habitually names a + // function `does the thing`, and a local class inside it takes + // that name as a segment of its binary name. + if (simple.equals(simpleNameAt(code, n, end))) { return i; } } @@ -739,6 +761,17 @@ public static String blankNonCode(String text, boolean kotlin) { } i++; } + } else if (kotlin && c == '`') { + // A Kotlin escaped identifier. It is CODE, so it is left alone + // rather than blanked -- the declared name has to stay readable + // -- but it is stepped over whole, because the quote in + // `class `say"hi`` would otherwise open a literal that swallows + // the rest of the file. + int j = i + 1; + while (j < out.length && out[j] != '`' && out[j] != '\n') { + j++; + } + i = j < out.length && out[j] == '`' ? j + 1 : i + 1; } else if (c == '\'') { // A char literal. '{' is legal and would otherwise be counted as // syntax, so the nesting scan loses its place and reads a live diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 4de963f06bc..3fbdeb56b48 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -582,6 +582,36 @@ public void aKotlinLocalClassPathIsInconclusiveNotAnOrphan() { false)); } + /// Kotlin lets a declaration escape its name in backticks, and the binary + /// name is plainly the text between them. Reading it with the identifier + /// rule recorded an empty name, so a LIVE annotated type looked undeclared, + /// was dropped as an orphan before placement validation, and its misplaced + /// hints went unreported on a green build. + @Test + public void aKotlinEscapedNameIsTheTextBetweenTheBackticks() { + assertTrue(BuildHintAnnotationProcessor.declaresType( + "package com.example\nclass `when` {\n}\n", "when", true)); + + // A quote is a legal character in an escaped name, and the name is not a + // literal -- treating it as one blanked the rest of the file. + String quoted = "package com.example\nclass `say\"hi` { }\nclass Real { }\n"; + assertTrue(BuildHintAnnotationProcessor.declaresType(quoted, "Real", true)); + assertTrue(BuildHintAnnotationProcessor.declaresType(quoted, "say\"hi", true)); + + // Functions are escaped at least as often, and a local class takes the + // enclosing function's name as a segment of its own binary name. + String fn = "package com.example\n" + + "class Main {\n" + + " fun `does the thing`() {\n" + + " class Wrong\n" + + " }\n" + + "}\n"; + assertTrue(BuildHintAnnotationProcessor.declaresNestedPath( + fn, new String[] {"Main", "does the thing", "Wrong"}, true)); + assertFalse(BuildHintAnnotationProcessor.declaresNestedPath( + fn, new String[] {"Main", "does the thing", "Gone"}, true)); + } + /// Kotlin's UNNAMED companion object is `Companion` in the binary name and /// is spelled `companion object` in the source, so nothing there is called /// Companion. Treating that as inconclusive accepted the whole path without diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 6fb8c62f48c..4030f163250 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2477,7 +2477,7 @@ static boolean declaresClass(String text, String main, String pkg, boolean kotli int i = 0; while (i < text.length()) { char c = text.charAt(i); - if (c == '"' || c == '\'' || c == '/') { + if (c == '"' || c == '\'' || c == '/' || c == '`') { int skipped = skipNonCode(text, i, kotlin); if (skipped > i) { i = skipped; @@ -2510,11 +2510,24 @@ static boolean declaresClass(String text, String main, String pkg, boolean kotli // stopping at a newline read the declaration as unnamed. int n = nextLiveChar(text, wordEnd, kotlin); if (n >= 0) { + // Kotlin lets the name be ESCAPED in backticks, and the + // binary name -- which is what codename1.mainName holds -- + // is the text between them. Reading it with the identifier + // rule recorded an empty name, so the real main source was + // rejected, nothing knew which hints an annotation already + // owns, and Settings offered Add for one of them. int end = n; - while (end < text.length() && continuesAName(text.charAt(end))) { - end++; + String declared; + if (kotlin && text.charAt(n) == '`') { + int close = text.indexOf('`', n + 1); + declared = close < 0 ? null : text.substring(n + 1, close); + } else { + while (end < text.length() && continuesAName(text.charAt(end))) { + end++; + } + declared = text.substring(n, end); } - if (text.substring(n, end).equals(main)) { + if (main.equals(declared)) { return true; } } @@ -2876,7 +2889,7 @@ static int nextMarker(String source, String marker, int from, boolean kotlin) { int i = from; while (i < source.length()) { char c = source.charAt(i); - if (c == '"' || c == '\'' || c == '/') { + if (c == '"' || c == '\'' || c == '/' || c == '`') { int skipped = skipNonCode(source, i, kotlin); if (skipped > i) { i = skipped; @@ -2968,6 +2981,18 @@ private static int skipNonCode(String s, int i, boolean kotlin) { } return s.length(); } + // A Kotlin escaped identifier -- `class `when``. It is code, not a + // literal, but it is stepped over whole because a quote inside it + // (`say"hi` is a legal name) would otherwise open a literal that + // swallows every annotation after it, leaving an owned hint editable in + // Settings and letting the user add the duplicate that fails the build. + if (kotlin && c == '`') { + int close = s.indexOf('`', i + 1); + int nl = s.indexOf('\n', i + 1); + if (close >= 0 && (nl < 0 || close < nl)) { + return close + 1; + } + } if (c == '/' && i + 1 < s.length()) { char n = s.charAt(i + 1); if (n == '/') { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index edc79b2a38e..5e915422385 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -865,4 +865,23 @@ public void aNestedKotlinBlockCommentStaysClosed() { // Java does not nest, so the same text really does end at the inner `*/`. assertFalse(CodenameOneSettings.declaresClass(kt, "MyApp", "com.example", false)); } + + /// A Kotlin main class may escape its name in backticks, and + /// `codename1.mainName` holds the name between them. Reading it with the + /// identifier rule recorded an empty name, so the real main source was + /// rejected, nothing knew which hints an annotation already owns, and + /// Settings offered Add for one of them -- the duplicate declaration that + /// fails the next build. + @Test + public void aKotlinEscapedMainNameIsRecognised() { + assertTrue(CodenameOneSettings.declaresClass( + "package com.example\nclass `when` {\n}\n", "when", "com.example")); + assertFalse(CodenameOneSettings.declaresClass( + "package com.example\nclass `when` {\n}\n", "Other", "com.example")); + + // A quote inside an escaped name is legal, and is not the start of a + // literal: reading it as one blanked the declaration that followed. + String quoted = "package com.example\nclass `say\"hi` { }\nclass MyApp { }\n"; + assertTrue(CodenameOneSettings.declaresClass(quoted, "MyApp", "com.example")); + } } From e6f16401a408c42fbed044cb7f22a0ede3fb1e12 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:39:46 +0300 Subject: [PATCH 064/115] Pick this application's manifest, and finish reading escaped Kotlin names Three review findings and the CI gap that reported them. A qualified name may escape a COMPONENT -- `package com.`when`` is legal Kotlin and the class belongs to com.when. The reader stopped at the backtick and recorded `com.`, so a live annotated class looked like it belonged elsewhere, was dropped as an orphan, and its misplaced hints went unreported on a green build. The migration goal's own declaration locator had the matching gap one level along: it recorded no name for `class `when``, so a file the lookup had just accepted was reported as having no class declaration and a valid migration was rolled back. The simulator accepted the first classpath DIRECTORY carrying a manifest, without the stamp check the jar branch already had. A reactor dependency or a stale output directory earlier on the classpath ended the search there, and the caller then saw a main class that was not this one and published nothing -- so cn1:run silently dropped desktop.titleBar and nativeTheme while this application's own manifest sat in a later entry. A manifest with no stamp at all is kept as a last resort rather than treated as foreign. ant.yml was the one required workflow whose Maven steps never went through the shared retry helper, so a 429 from the Central CDN edge -- which kills the step while resolving a dependency POM, before a line is compiled -- failed the branch outright. Wrapped like every other workflow, with RETRY_ONLY_MATCHING so nothing else gets a second chance. The classifier also accepts Maven's "status code: 429" spelling, which the "status: " alternative never matched. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ant.yml | 24 +++- .../com/codename1/impl/javase/Simulator.java | 31 ++++- .../maven/MigrateBuildHintsMojo.java | 22 +++- .../BuildHintAnnotationProcessor.java | 30 +++++ .../MigrateBuildHintsPropertyParsingTest.java | 17 +++ .../BuildHintAnnotationProcessorTest.java | 18 +++ .../SimulatorAnnotationManifestTest.java | 106 ++++++++++++++++++ 7 files changed, 234 insertions(+), 14 deletions(-) create mode 100644 maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index cc2440d66a5..8fc4f8b3192 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -46,24 +46,38 @@ jobs: - name: Install dependencies run: bash scripts/ci/apt-get-install.sh xvfb - name: Build with Maven (core + in-tree archetypes) + # Every other Maven step in this repository goes through the shared retry + # helper because Maven Central answers 403/429 from the runner CDN edge + # often enough to kill a step outright -- it dies resolving a dependency + # POM, before a line is compiled, so nothing about the branch is being + # tested when it happens. This workflow was the one that never did, and it + # is a required check. RETRY_ONLY_MATCHING keeps every other failure + # failing on the first attempt, so a re-run cannot launder a flaky test + # into a pass. + env: + RETRY_ONLY_MATCHING: 'status( code)?: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not collect dependencies' run: | # The cn1{app,lib}-archetype modules are part of the maven/ reactor, # so this single install also stages them into the local repo. # -Darchetype.test.skip=true keeps the build fast; the heavier # archetype ITs run in the release workflow. cd maven - xvfb-run -a mvn install -Plocal-dev-javase -Darchetype.test.skip=true + bash ../scripts/ci/retry.sh xvfb-run -a mvn install -Plocal-dev-javase -Darchetype.test.skip=true - name: Refresh archetype catalog + env: + RETRY_ONLY_MATCHING: 'status( code)?: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not collect dependencies' run: | cd maven - xvfb-run -a mvn archetype:update-local-catalog -Plocal-dev-javase - xvfb-run -a mvn archetype:crawl -Plocal-dev-javase + bash ../scripts/ci/retry.sh xvfb-run -a mvn archetype:update-local-catalog -Plocal-dev-javase + bash ../scripts/ci/retry.sh xvfb-run -a mvn archetype:crawl -Plocal-dev-javase - name: Run Maven Unit Tests + env: + RETRY_ONLY_MATCHING: 'status( code)?: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not collect dependencies' run: | pwd - xvfb-run -a bash tests/all.sh + bash scripts/ci/retry.sh xvfb-run -a bash tests/all.sh cd maven/integration-tests - xvfb-run -a bash all.sh + bash ../../scripts/ci/retry.sh xvfb-run -a bash all.sh diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index ec228a60440..4cf56ef4d66 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -690,7 +690,7 @@ private static String configuredMainClass(File projectDir) { * build the simulator runs resolves the application's own common module as a * dependency artifact, so its manifest has no path of its own.

*/ - private static final class FoundManifest { + static final class FoundManifest { final java.util.Properties hints; /** The file on disk, or null when it came out of a jar. */ final File file; @@ -703,8 +703,8 @@ private static final class FoundManifest { } } - private static FoundManifest findAnnotationManifest(File projectDir, String classPathStr, - String expectedMain) { + static FoundManifest findAnnotationManifest(File projectDir, String classPathStr, + String expectedMain) { String resource = "META-INF" + File.separator + "codenameone" + File.separator + "build-hints.properties"; String entryName = "META-INF/codenameone/build-hints.properties"; @@ -715,6 +715,7 @@ private static FoundManifest findAnnotationManifest(File projectDir, String clas // manifest and the old class beside it, so the staleness check compares // two obsolete files against each other, finds them consistent, and // publishes last week's hints while the real ones sit on the classpath. + FoundManifest fallback = null; if (classPathStr != null) { for (String entry : classPathStr.split(java.util.regex.Pattern.quote(File.pathSeparator))) { @@ -727,7 +728,26 @@ private static FoundManifest findAnnotationManifest(File projectDir, String clas if (candidate.isFile()) { java.util.Properties loaded = readProperties(candidate); if (loaded != null) { - return new FoundManifest(loaded, candidate, candidate.toString()); + // The stamp decides here too. A reactor dependency or + // a stale JavaSE output directory earlier on the + // classpath can carry ANOTHER application's manifest, + // and taking it ended the search: the caller then saw + // a main class that was not this one and published + // nothing at all, so cn1:run silently dropped + // desktop.titleBar and nativeTheme while this + // application's own manifest sat in a later entry. + String stamp = loaded.getProperty("cn1.buildHints.mainClass"); + FoundManifest found = + new FoundManifest(loaded, candidate, candidate.toString()); + if (expectedMain == null || expectedMain.equals(stamp)) { + return found; + } + if (stamp == null && fallback == null) { + // No stamp is not a foreign manifest, only an + // unidentifiable one. Kept as a last resort so a + // manifest that predates the stamp is still read. + fallback = found; + } } } continue; @@ -751,6 +771,9 @@ private static FoundManifest findAnnotationManifest(File projectDir, String clas } } } + if (fallback != null) { + return fallback; + } // Only when the classpath carries none: a launch that did not pass the // module's output directory at all still finds a conventional build. File conventional = new File(projectDir, "target" + File.separator + "classes" diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index b368393e1fc..98302567e5e 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1141,17 +1141,29 @@ static int classDeclarationIndex(String text, boolean kotlin, String simpleName) while (n < code.length() && Character.isWhitespace(code.charAt(n))) { n++; } + // Kotlin may ESCAPE the declared name in backticks, and + // codename1.mainName holds the name between them. Reading only + // identifier characters recorded nothing for `class `when``, so + // the goal reported "Could not find the class declaration" and + // rolled back a valid migration of a file it had just accepted. int end = n; - while (end < code.length() - && Character.isJavaIdentifierPart(code.charAt(end))) { - end++; + String declared; + if (kotlin && n < code.length() && code.charAt(n) == '`') { + int close = code.indexOf('`', n + 1); + declared = close < 0 ? "" : code.substring(n + 1, close); + } else { + while (end < code.length() + && Character.isJavaIdentifierPart(code.charAt(end))) { + end++; + } + declared = code.substring(n, end); } - if (end > n) { + if (declared.length() > 0) { // The declaration's own modifiers come before the keyword, and // the annotations have to go before those. int start = startOfModifiers(code, i); if (simpleName != null && simpleName.length() > 0 - && code.substring(n, end).equals(simpleName)) { + && declared.equals(simpleName)) { return start; } if (first < 0) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 16d0a924eae..f5e9d98fd25 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -669,6 +669,36 @@ private static int readQualifiedName(String code, int from, StringBuilder name) } return stop + 1; } + // A COMPONENT may be escaped too: `package com.`when`` is legal + // Kotlin and the compiled class belongs to com.when. Stopping at the + // backtick recorded `com.`, so a live annotated class looked like it + // belonged to another package, was dropped as an orphan, and its + // misplaced hints went unreported on a green build. A backtick is + // not Java source outside a comment or literal, both already blanked, + // so this needs no language flag. + if (stop < code.length() && code.charAt(stop) == '`') { + int close = code.indexOf('`', stop + 1); + if (close < 0) { + return end; + } + if (name != null) { + name.append(code, stop + 1, close); + } + stop = close + 1; + end = stop; + int nextDot = stop; + while (nextDot < code.length() && Character.isWhitespace(code.charAt(nextDot))) { + nextDot++; + } + if (nextDot >= code.length() || code.charAt(nextDot) != '.') { + return end; + } + if (name != null) { + name.append('.'); + } + i = nextDot + 1; + continue; + } while (stop < code.length() && Character.isJavaIdentifierPart(code.charAt(stop))) { stop++; } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index e52a859aa3c..50faf2aea3e 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -354,6 +354,23 @@ public void theInsertionPointClearsAMultiLineImport() { assertEquals(head.length(), MigrateBuildHintsMojo.endOfImportDeclaration(code, last)); } + /// A Kotlin main class may escape its name in backticks, and + /// `codename1.mainName` holds the name between them. Reading only identifier + /// characters recorded nothing, so the goal reported "Could not find the + /// class declaration" and rolled back a valid migration of a file the + /// lookup had just accepted as the right one. + @Test + public void theDeclarationLocatorReadsAnEscapedKotlinName() { + String src = "package com.example\n\nclass `when` {\n}\n"; + assertEquals(src.indexOf("class `when`"), + MigrateBuildHintsMojo.classDeclarationIndex(src, true, "when")); + // An escaped declaration also counts as the first one, which is what a + // name that matches nothing falls back to. Recording no name at all left + // even that fallback unset. + assertEquals(src.indexOf("class `when`"), + MigrateBuildHintsMojo.classDeclarationIndex(src, true, "Other")); + } + /// `static` is a modifier, not the imported name. Reading it as the name /// ended the declaration at the newline inside the REAL name, so the /// generated import was spliced into the middle of the static import and the diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 3fbdeb56b48..82bde84ed8d 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -582,6 +582,24 @@ public void aKotlinLocalClassPathIsInconclusiveNotAnOrphan() { false)); } + /// A qualified name may escape a COMPONENT: `package com.`when`` is legal + /// Kotlin and the compiled class belongs to `com.when`. Stopping at the + /// backtick recorded `com.`, so a live annotated class looked like it + /// belonged to another package, was dropped as an orphan, and its misplaced + /// hints went unreported on a green build. + @Test + public void aQualifiedNameMayEscapeAComponent() { + assertEquals("com.when", BuildHintAnnotationProcessor.declaredPackageIn( + "package com.`when`\nclass Foo\n", true)); + assertEquals("com.when.x", BuildHintAnnotationProcessor.declaredPackageIn( + "package com.`when`.x\nclass Foo\n", true)); + // The first component too, and an ordinary name is unchanged. + assertEquals("in.example", BuildHintAnnotationProcessor.declaredPackageIn( + "package `in`.example\nclass Foo\n", true)); + assertEquals("com.example", BuildHintAnnotationProcessor.declaredPackageIn( + "package com.example\nclass Foo\n", true)); + } + /// Kotlin lets a declaration escape its name in backticks, and the binary /// name is plainly the text between them. Reading it with the identifier /// rule recorded an empty name, so a LIVE annotated type looked undeclared, diff --git a/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java b/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java new file mode 100644 index 00000000000..0e76ecb5350 --- /dev/null +++ b/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.impl.javase; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.Properties; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * The simulator picks the build-hint manifest that belongs to THIS application. + */ +public class SimulatorAnnotationManifestTest { + + private static File manifestIn(File dir, String mainClass, String hint) throws Exception { + File out = new File(dir, "META-INF/codenameone"); + out.mkdirs(); + File f = new File(out, "build-hints.properties"); + Properties p = new Properties(); + p.setProperty("cn1.buildHints.mainClass", mainClass); + p.setProperty("codename1.arg.desktop.titleBar", hint); + FileOutputStream os = new FileOutputStream(f); + try { + p.store(os, null); + } finally { + os.close(); + } + return f; + } + + /** + * A reactor dependency or a stale output directory earlier on the classpath + * can carry another application's manifest. Taking the first directory that + * has one ended the search there, and the caller then saw a main class that + * was not this one and published nothing -- so cn1:run silently + * dropped every annotated hint while this application's own manifest sat in + * a later classpath entry. + */ + @Test + public void aForeignDirectoryManifestIsPassedOver(@TempDir File tmp) throws Exception { + File other = new File(tmp, "other-classes"); + File mine = new File(tmp, "my-classes"); + manifestIn(other, "com.other.TheirApp", "MINIMAL"); + manifestIn(mine, "com.example.MyApp", "NATIVE"); + + String cp = other.getAbsolutePath() + File.pathSeparator + mine.getAbsolutePath(); + Simulator.FoundManifest found = + Simulator.findAnnotationManifest(tmp, cp, "com.example.MyApp"); + assertNotNull(found); + assertEquals("com.example.MyApp", found.hints.getProperty("cn1.buildHints.mainClass")); + assertEquals("NATIVE", found.hints.getProperty("codename1.arg.desktop.titleBar")); + } + + /** + * With no configured main class there is nothing to compare against, so the + * first manifest on the classpath is still the answer. + */ + @Test + public void withoutAMainClassTheFirstManifestWins(@TempDir File tmp) throws Exception { + File first = new File(tmp, "first"); + manifestIn(first, "com.other.TheirApp", "MINIMAL"); + Simulator.FoundManifest found = + Simulator.findAnnotationManifest(tmp, first.getAbsolutePath(), null); + assertNotNull(found); + assertEquals("MINIMAL", found.hints.getProperty("codename1.arg.desktop.titleBar")); + } + + /** + * Only foreign manifests, and no conventional build: nothing is published + * rather than another application's hints. + */ + @Test + public void onlyForeignManifestsFindNothing(@TempDir File tmp) throws Exception { + File other = new File(tmp, "other-classes"); + manifestIn(other, "com.other.TheirApp", "MINIMAL"); + assertNull(Simulator.findAnnotationManifest( + tmp, other.getAbsolutePath(), "com.example.MyApp")); + } +} From 10aed32695b7f355e7554eb3c8576196f632f78c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:44:58 +0300 Subject: [PATCH 065/115] Read escaped Kotlin package components in Settings too `package com.`when`` is legal and the class belongs to com.when, but the Settings tool's qualified-name readers stopped at the backtick and recorded `com.`. The real main source was then rejected, so nothing knew which hints an annotation already owns and Settings could write the duplicate properties declaration that the next build rejects -- the mirror of the orphan-drop the processor-side reader had. Both readers now share one component step that recognises the escaped form, so an import spanning one is read whole as well. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 41 +++++++++++++++---- .../settings/BuildHintCatalogTest.java | 18 ++++++++ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 4030f163250..6d1a723ec4f 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2562,10 +2562,7 @@ static int qualifiedNameEnd(String source, int from, boolean kotlin) { int i = nextLiveChar(source, from, kotlin); int end = from; while (i >= 0 && i < source.length()) { - int stop = i; - while (stop < source.length() && continuesAName(source.charAt(stop))) { - stop++; - } + int stop = componentEnd(source, i, kotlin); if (stop == i) { return end; } @@ -2581,18 +2578,44 @@ static int qualifiedNameEnd(String source, int from, boolean kotlin) { /// The dotted name starting at or after `from`, stepping over whitespace and /// comments around each dot. + /// The end of the name component at `i`, or `i` when there is none. + /// + /// A Kotlin component may be ESCAPED in backticks -- `package com.`when`` + /// is legal and the class belongs to com.when. Reading only identifier + /// characters stopped at the backtick and recorded `com.`, so the real main + /// source was rejected: nothing then knew which hints an annotation already + /// owns, and Settings could write the duplicate properties declaration that + /// the next build rejects. + private static int componentEnd(String source, int i, boolean kotlin) { + if (kotlin && i < source.length() && source.charAt(i) == '`') { + int close = source.indexOf('`', i + 1); + return close < 0 ? i : close + 1; + } + int end = i; + while (end < source.length() && continuesAName(source.charAt(end))) { + end++; + } + return end; + } + + /// That component's text, which is what the backticks quote rather than + /// include. + private static String componentText(String source, int i, int end, boolean kotlin) { + if (kotlin && i < end && source.charAt(i) == '`') { + return source.substring(i + 1, end - 1); + } + return source.substring(i, end); + } + static String qualifiedNameAt(String source, int from, boolean kotlin) { int i = nextLiveChar(source, from, kotlin); StringBuilder name = new StringBuilder(); while (i >= 0 && i < source.length()) { - int end = i; - while (end < source.length() && continuesAName(source.charAt(end))) { - end++; - } + int end = componentEnd(source, i, kotlin); if (end == i) { break; } - name.append(source, i, end); + name.append(componentText(source, i, end, kotlin)); int dot = nextLiveChar(source, end, kotlin); if (dot < 0 || source.charAt(dot) != '.') { break; diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 5e915422385..b5fecf51610 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -884,4 +884,22 @@ public void aKotlinEscapedMainNameIsRecognised() { String quoted = "package com.example\nclass `say\"hi` { }\nclass MyApp { }\n"; assertTrue(CodenameOneSettings.declaresClass(quoted, "MyApp", "com.example")); } + + /// A Kotlin package may escape a COMPONENT -- `package com.`when`` is legal + /// and the class belongs to com.when. Reading only identifier characters + /// recorded `com.`, so the real main source was rejected: nothing knew which + /// hints an annotation already owns, and Settings could write the duplicate + /// properties declaration that the next build rejects. + @Test + public void aKotlinPackageMayEscapeAComponent() { + assertTrue(CodenameOneSettings.declaresClass( + "package com.`when`\nclass MyApp {\n}\n", "MyApp", "com.when")); + assertFalse(CodenameOneSettings.declaresClass( + "package com.`when`\nclass MyApp {\n}\n", "MyApp", "com")); + // The first component too, and an ordinary name is unchanged. + assertTrue(CodenameOneSettings.declaresClass( + "package `in`.example\nclass MyApp {\n}\n", "MyApp", "in.example")); + assertTrue(CodenameOneSettings.declaresClass( + "package com.example\nclass MyApp {\n}\n", "MyApp", "com.example")); + } } From 686f6cd0d676ba85bb48372cedcf8fd62b5fc155 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:56:04 +0300 Subject: [PATCH 066/115] Keep checking placement once a manifest is accepted Three findings, all the same shape: something that had been argued to be implied by an earlier check turns out not to be. Accepting a manifest does not mean the processor ran this build. Its fingerprint covers the main class, so an annotation added to a live class BESIDE it leaves the manifest looking entirely current -- and with the goal unbound or skipped the build succeeded having neither applied that class's hints nor said the annotation was in the wrong place. The misplacement scan now runs on the accepted path too, ignoring the class the manifest was generated for, which carrying annotations is the point of. Sharing an archive does not mean sharing a build either. Nothing deletes an old manifest from target/classes, so a recompiled main class and last week's resource get packaged into one jar; the simulator skipped the staleness check for jars on the argument that the two entries could not disagree, and published the old values. Their own entry timestamps say otherwise. Zip stores those to two seconds, which is ample for telling an earlier build apart. And the Settings import reader is separate from the qualified-name reader fixed last commit, so `import com.codename1.annotations.`buildhints`.Ios` was still recorded as `com.codename1.annotations.` -- the live @Ios read as somebody else's, and the tool could write the duplicate the next build refuses. It goes through the same component step now, aliases included. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 81 ++++++++++++++++--- .../com/codename1/maven/CN1BuildMojo.java | 44 ++++++++++ .../maven/AnnotationBuildHintMergeTest.java | 81 +++++++++++++++++++ .../SimulatorAnnotationManifestTest.java | 70 ++++++++++++++++ .../settings/CodenameOneSettings.java | 22 ++--- .../settings/BuildHintCatalogTest.java | 29 +++++++ 6 files changed, 303 insertions(+), 24 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index 4cf56ef4d66..9a9d4335c3e 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -508,10 +508,13 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat + ", not " + expectedMain + ", so its build hints were NOT applied."); return; } - // Only for a manifest on disk. Inside a jar the class and the manifest - // were written by the same build into the same archive, so they cannot - // disagree and there is nothing to compare. - File staleAgainst = f == null ? null : classNewerThanManifest(p, f); + // Sharing an archive does not mean sharing a build. Nothing deletes an + // old manifest from target/classes, so a recompiled main class and last + // week's resource are packaged into the same jar -- with their own entry + // timestamps, which is what tells them apart. + String staleAgainst = found.jar != null + ? classNewerThanManifestInJar(p, found.jar) + : (f == null ? null : classNewerThanManifest(p, f)); if (staleAgainst != null) { // Nothing removes target/classes between builds, so a project that ran // process-annotations once and then stopped -- goal unbound, skipped, @@ -529,7 +532,7 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat // within a build, so a main class newer than the manifest cannot have // produced it. System.err.println("Warning: " + found.where + " is older than " - + staleAgainst.getName() + ", so it was produced by an earlier build " + + staleAgainst + ", so it was produced by an earlier build " + "and its build hints were NOT applied."); System.err.println(" Rebuild the project so the cn1 process-annotations " + "goal regenerates it."); @@ -690,15 +693,22 @@ private static String configuredMainClass(File projectDir) { * build the simulator runs resolves the application's own common module as a * dependency artifact, so its manifest has no path of its own.

*/ + /** The manifest's path inside a jar, which is its resource path with / separators. */ + private static final String ANNOTATION_HINTS_ENTRY = + "META-INF/codenameone/build-hints.properties"; + static final class FoundManifest { final java.util.Properties hints; /** The file on disk, or null when it came out of a jar. */ final File file; + /** The jar it came out of, or null when it is a file on disk. */ + final File jar; final String where; - FoundManifest(java.util.Properties hints, File file, String where) { + FoundManifest(java.util.Properties hints, File file, File jar, String where) { this.hints = hints; this.file = file; + this.jar = jar; this.where = where; } } @@ -707,7 +717,7 @@ static FoundManifest findAnnotationManifest(File projectDir, String classPathStr String expectedMain) { String resource = "META-INF" + File.separator + "codenameone" + File.separator + "build-hints.properties"; - String entryName = "META-INF/codenameone/build-hints.properties"; + String entryName = ANNOTATION_HINTS_ENTRY; // The classpath first, because it is the output the build is ACTUALLY // using. Trying the conventional path first looked harmless and is not: // a project that moves to a configured output directory without running @@ -738,7 +748,8 @@ static FoundManifest findAnnotationManifest(File projectDir, String classPathStr // application's own manifest sat in a later entry. String stamp = loaded.getProperty("cn1.buildHints.mainClass"); FoundManifest found = - new FoundManifest(loaded, candidate, candidate.toString()); + new FoundManifest(loaded, candidate, null, + candidate.toString()); if (expectedMain == null || expectedMain.equals(stamp)) { return found; } @@ -766,7 +777,8 @@ static FoundManifest findAnnotationManifest(File projectDir, String classPathStr java.util.Properties loaded = readJarEntry(dir, entryName); if (loaded != null && expectedMain.equals(loaded.getProperty("cn1.buildHints.mainClass"))) { - return new FoundManifest(loaded, null, entryName + " in " + dir.getName()); + return new FoundManifest(loaded, null, dir, + entryName + " in " + dir.getName()); } } } @@ -783,7 +795,7 @@ static FoundManifest findAnnotationManifest(File projectDir, String classPathStr } java.util.Properties loaded = readProperties(conventional); return loaded == null ? null - : new FoundManifest(loaded, conventional, conventional.toString()); + : new FoundManifest(loaded, conventional, null, conventional.toString()); } /** Loads a properties file, or null when it cannot be read. */ @@ -845,8 +857,51 @@ private static java.util.Properties readJarEntry(File jar, String entryName) { * no class file for it, no readable timestamps -- so the manifest is taken at * face value rather than discarded on a guess.

*/ - private static File classNewerThanManifest(java.util.Properties manifest, - File manifestFile) { + /** + * As above, for a manifest read out of a jar: the two entries' own + * timestamps, since being in one archive proves nothing about which build + * wrote them. + * + *

Zip stores times to two-second granularity, so the comparison is + * deliberately strict -- a class newer by less than that reads as + * consistent. It only has to catch a manifest from an EARLIER build, which + * is not a near thing.

+ */ + static String classNewerThanManifestInJar(java.util.Properties manifest, File jar) { + String main = manifest.getProperty("cn1.buildHints.mainClass"); + if (main == null || main.trim().length() == 0) { + return null; + } + String classEntry = main.trim().replace('.', '/') + ".class"; + java.util.zip.ZipFile zip = null; + try { + zip = new java.util.zip.ZipFile(jar); + java.util.zip.ZipEntry cls = zip.getEntry(classEntry); + java.util.zip.ZipEntry res = zip.getEntry(ANNOTATION_HINTS_ENTRY); + if (cls == null || res == null) { + return null; + } + long classTime = cls.getTime(); + long manifestTime = res.getTime(); + if (classTime < 0L || manifestTime < 0L) { + return null; + } + return classTime > manifestTime ? classEntry : null; + } catch (IOException ex) { + return null; + } finally { + if (zip != null) { + try { + zip.close(); + } catch (IOException ignored) { + // read-only archive + } + } + } + } + + private static String classNewerThanManifest(java.util.Properties manifest, + File manifestFile) { String main = manifest.getProperty("cn1.buildHints.mainClass"); if (main == null || main.trim().length() == 0) { return null; @@ -869,6 +924,6 @@ private static File classNewerThanManifest(java.util.Properties manifest, if (classTime == 0L || manifestTime == 0L) { return null; } - return classTime > manifestTime ? classFile : null; + return classTime > manifestTime ? classFile.getName() : null; } } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index ed37aadc883..8ee9912b43f 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -2734,11 +2734,13 @@ private void mergeAnnotationBuildHints(Properties target, List classpath } if (applied > 0) { getLog().info("cn1: applied " + applied + " build hint(s) from annotations"); + failOnMisplacedAnnotations(classpathElements, expectedMain); return; } } if (processed) { getLog().debug("cn1: annotations were processed and set no build hint"); + failOnMisplacedAnnotations(classpathElements, expectedMain); return; } // No manifest at all. If the compiled classes carry build hint @@ -2766,6 +2768,38 @@ private void mergeAnnotationBuildHints(Properties target, List classpath } } + /** + * Refuses a build where a class other than the main one carries build hint + * annotations. + * + *

Run even when a manifest was accepted, because accepting one does not + * mean the processor ran THIS build: the fingerprint covers the main class, + * so an annotation added to a live class beside it leaves the manifest + * looking entirely current. With the goal unbound or skipped, the hints from + * that class reach nothing and the build succeeds having neither applied + * them nor said the annotation is in the wrong place -- the silent failure + * this whole feature exists to remove. Had the processor run, it would have + * refused the build for the same class.

+ */ + private void failOnMisplacedAnnotations(List classpathElements, String expectedMain) + throws MojoFailureException { + if (expectedMain == null) { + return; + } + java.util.Collection descriptors = + com.codename1.build.shared.BuildHintAnnotationBinding.descriptors(); + for (String element : classpathElements) { + String live = liveAnnotatedClass(new File(element), descriptors, expectedMain); + if (live != null) { + throw new MojoFailureException(live + " carries build hint annotations, but they " + + "are only read from the application's main class (" + expectedMain + + "), so none of them reached this build.\n\nMove them onto " + + expectedMain + ", or set those hints in " + + "codenameone_settings.properties."); + } + } + } + /** * The duplicate-declaration message for a hint set by an annotation and by a * properties line, or null when there is no clash. @@ -2935,6 +2969,13 @@ private String classCarryingBuildHintAnnotations(List classpathElements, * class the developer actually wrote does.

*/ private String liveAnnotatedClass(File element, java.util.Collection descriptors) { + return liveAnnotatedClass(element, descriptors, null); + } + + /// As above, ignoring `exclude` -- the class the manifest was generated for, + /// which carrying annotations is the whole point of. + private String liveAnnotatedClass(File element, java.util.Collection descriptors, + String exclude) { List roots; try { roots = project == null ? null : project.getCompileSourceRoots(); @@ -2951,6 +2992,9 @@ private String liveAnnotatedClass(File element, java.util.Collection des // the search, or whether a live misplacement is reported depends on the // order the directory happened to be listed in. for (String hit : findAnnotatedClasses(element, descriptors)) { + if (exclude != null && exclude.equals(hit)) { + continue; + } try { com.codename1.maven.annotations.AnnotatedClass cls = readClass(element, hit); if (cls != null && com.codename1.maven.processors.BuildHintAnnotationProcessor diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java index 4f642fe1ba1..27850b51de9 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/AnnotationBuildHintMergeTest.java @@ -238,6 +238,75 @@ public void aPropertiesLineForAnUnannotatedHintIsLeftAlone() throws Exception { // helpers // ------------------------------------------------------------------ + /** + * Accepting a manifest does not mean the processor ran THIS build. Its + * fingerprint covers the main class, so an annotation added to a live class + * beside it leaves the manifest looking entirely current -- and with the + * goal unbound or skipped the build succeeded having neither applied that + * class's hints nor said the annotation was in the wrong place. + */ + @Test + public void aMisplacedAnnotationIsRefusedEvenWhenTheManifestIsCurrent() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n" + + "cn1.buildHints.sourceDigest=" + + digestOf("@Ios(teamId = \"ABCDE12345\")") + "\n" + + "codename1.arg.ios.teamId=ABCDE12345\n"); + writeAnnotatedClass(classes); + File src = writeAnnotatedHelperSource(); + compileInto(classes, "com.example.Helper", helperSource()); + + Properties target = new Properties(); + try { + merge(target, classes, "MyApp", "com.example", src); + fail("expected the misplaced annotation to be refused"); + } catch (InvocationTargetException ex) { + assertTrue(String.valueOf(ex.getCause().getMessage()), + ex.getCause().getMessage().contains("com.example.Helper")); + } + } + + /** The main class carrying them is the whole point, so it is not a misplacement. */ + @Test + public void theMainClassCarryingAnnotationsIsNotAMisplacement() throws Exception { + File classes = manifest("cn1.buildHints.mainClass=com.example.MyApp\n" + + "cn1.buildHints.sourceDigest=" + + digestOf("@Ios(teamId = \"ABCDE12345\")") + "\n" + + "codename1.arg.ios.teamId=ABCDE12345\n"); + writeAnnotatedClass(classes); + + Properties target = new Properties(); + merge(target, classes, "MyApp", "com.example", writeAnnotatedHelperSource()); + + assertEquals("ABCDE12345", target.getProperty("codename1.arg.ios.teamId")); + } + + private static String helperSource() { + return "package com.example;\n" + + "import com.codename1.annotations.buildhints.Ios;\n" + + "@Ios(pods = \"Alamofire\")\n" + + "public class Helper {\n}\n"; + } + + /** A source root holding Helper.java, so the class counts as live. */ + private File writeAnnotatedHelperSource() throws Exception { + File src = tmp.newFolder(); + File f = new File(src, "com/example/Helper.java"); + f.getParentFile().mkdirs(); + try (Writer w = new OutputStreamWriter(new FileOutputStream(f), "UTF-8")) { + w.write(helperSource()); + } + return src; + } + + private void compileInto(File classes, String binaryName, String source) throws Exception { + com.codename1.maven.annotations.JavaSourceCompiler.compile( + com.codename1.maven.annotations.JavaSourceCompiler.singleSource( + binaryName, source), + classes, + Arrays.asList(new File(Class.forName("com.codename1.annotations.buildhints.Ios") + .getProtectionDomain().getCodeSource().getLocation().toURI()))); + } + private File manifest(String body) throws Exception { File classes = tmp.newFolder(); File out = new File(classes, RESOURCE); @@ -268,7 +337,19 @@ private void writeAnnotatedClass(File classes) throws Exception { */ private void merge(Properties target, File classesDir, String mainName, String pkg) throws Exception { + merge(target, classesDir, mainName, pkg, null); + } + + private void merge(Properties target, File classesDir, String mainName, String pkg, + File sourceRoot) throws Exception { CN1BuildMojo mojo = new CN1BuildMojo(); + if (sourceRoot != null) { + org.apache.maven.project.MavenProject p = new org.apache.maven.project.MavenProject(); + p.addCompileSourceRoot(sourceRoot.getAbsolutePath()); + Field proj = findField(mojo.getClass(), "project"); + proj.setAccessible(true); + proj.set(mojo, p); + } Properties settings = new Properties(); settings.setProperty("codename1.mainName", mainName); diff --git a/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java b/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java index 0e76ecb5350..a03d0236f24 100644 --- a/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java +++ b/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java @@ -26,6 +26,8 @@ import java.io.File; import java.io.FileOutputStream; import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -103,4 +105,72 @@ public void onlyForeignManifestsFindNothing(@TempDir File tmp) throws Exception assertNull(Simulator.findAnnotationManifest( tmp, other.getAbsolutePath(), "com.example.MyApp")); } + + /** + * Writes a jar holding the main class and the manifest, with the entry times + * given. + */ + private static File jarWith(File dir, String name, long classTime, long manifestTime) + throws Exception { + File jar = new File(dir, name); + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(jar)); + try { + ZipEntry cls = new ZipEntry("com/example/MyApp.class"); + cls.setTime(classTime); + out.putNextEntry(cls); + out.write(new byte[] {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE}); + out.closeEntry(); + + ZipEntry res = new ZipEntry("META-INF/codenameone/build-hints.properties"); + res.setTime(manifestTime); + out.putNextEntry(res); + out.write("cn1.buildHints.mainClass=com.example.MyApp\n".getBytes("ISO-8859-1")); + out.closeEntry(); + } finally { + out.close(); + } + return jar; + } + + private static Properties stampedFor(String mainClass) { + Properties p = new Properties(); + p.setProperty("cn1.buildHints.mainClass", mainClass); + return p; + } + + /** + * Sharing an archive does not mean sharing a build. Nothing deletes an old + * manifest from target/classes, so a recompiled main class and + * last week's resource get packaged into the same jar -- and skipping the + * staleness check for jars published the old annotation values. + */ + @Test + public void aStaleManifestInsideAJarIsDetected(@TempDir File tmp) throws Exception { + long manifest = 1600000000000L; + long newerClass = manifest + 60000L; + File jar = jarWith(tmp, "app-stale.jar", newerClass, manifest); + + assertEquals("com/example/MyApp.class", Simulator.classNewerThanManifestInJar( + stampedFor("com.example.MyApp"), jar)); + } + + /** A manifest written after the class it describes is current. */ + @Test + public void aCurrentManifestInsideAJarIsAccepted(@TempDir File tmp) throws Exception { + long cls = 1600000000000L; + File jar = jarWith(tmp, "app-current.jar", cls, cls + 60000L); + + assertNull(Simulator.classNewerThanManifestInJar( + stampedFor("com.example.MyApp"), jar)); + } + + /** No class of that name in the jar is nothing to compare against. */ + @Test + public void aJarWithoutTheMainClassIsNotJudged(@TempDir File tmp) throws Exception { + long cls = 1600000000000L; + File jar = jarWith(tmp, "app-other.jar", cls + 60000L, cls); + + assertNull(Simulator.classNewerThanManifestInJar( + stampedFor("com.other.TheirApp"), jar)); + } } diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 6d1a723ec4f..d880a25f800 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2672,14 +2672,17 @@ static java.util.List importsIn(String source, boolean kotlin) { i++; break; } - int end = i; - while (end < source.length() && continuesAName(source.charAt(end))) { - end++; - } + // A COMPONENT may be escaped -- `import + // com.codename1.annotations.`buildhints`.Ios` is legal Kotlin. + // Reading only identifier characters recorded `annotations.`, so + // the import went unrecognised and a live @Ios was read as + // somebody else's: Settings then offered the hint as unowned and + // could write the duplicate the next build refuses. + int end = componentEnd(source, i, kotlin); if (end == i) { break; } - name.append(source, i, end); + name.append(componentText(source, i, end, kotlin)); int dot = nextLiveChar(source, end, kotlin); if (dot < 0 || source.charAt(dot) != '.') { i = end; @@ -2697,13 +2700,10 @@ static java.util.List importsIn(String source, boolean kotlin) { && a + 2 < source.length() && !continuesAName(source.charAt(a + 2))) { int n = nextLiveChar(source, a + 2, kotlin); if (n >= 0) { - int nameEnd = n; - while (nameEnd < source.length() - && continuesAName(source.charAt(nameEnd))) { - nameEnd++; - } + // The alias may be escaped too: `import a.B as `when``. + int nameEnd = componentEnd(source, n, kotlin); if (nameEnd > n) { - alias = source.substring(n, nameEnd); + alias = componentText(source, n, nameEnd, kotlin); } } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index b5fecf51610..6f24912407d 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -902,4 +902,33 @@ public void aKotlinPackageMayEscapeAComponent() { assertTrue(CodenameOneSettings.declaresClass( "package com.example\nclass MyApp {\n}\n", "MyApp", "com.example")); } + + /// An import may escape a component too -- `import + /// com.codename1.annotations.`buildhints`.Ios` is legal Kotlin. Reading only + /// identifier characters recorded `com.codename1.annotations.`, so the + /// import went unrecognised, a live @Ios was read as somebody else's, and + /// Settings could write the duplicate the next build refuses. + @Test + public void aKotlinImportMayEscapeAComponent() { + assertTrue(CodenameOneSettings.importsAnnotation( + "package com.example\n" + + "import com.codename1.annotations.`buildhints`.Ios\n" + + "class MyApp\n", "Ios", true)); + // The type name itself, and the on-demand form. + assertTrue(CodenameOneSettings.importsAnnotation( + "package com.example\n" + + "import com.codename1.annotations.buildhints.`Ios`\n" + + "class MyApp\n", "Ios", true)); + // An escaped ALIAS is the name the file then uses -- read through the + // reader for aliases, which is what an aliased import belongs to. + assertEquals("when", CodenameOneSettings.kotlinImportAlias( + "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios as `when`\n" + + "class MyApp\n", "Ios", true)); + // Somebody else's package is still somebody else's. + assertFalse(CodenameOneSettings.importsAnnotation( + "package com.example\n" + + "import com.other.`buildhints`.Ios\n" + + "class MyApp\n", "Ios", true)); + } } From dd0fef623b43e821978ec2832f4c67a91c963b9f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:09:01 +0300 Subject: [PATCH 067/115] Judge the simulator's manifest by content, not by timestamps Zip records entry times to two seconds, and a build configured for reproducible output stamps every entry identically -- which makes the staleness comparison inert rather than merely coarse, so a manifest left behind by an earlier build reads as current and the simulator runs the previous values of hints it can actually see. The simulator has no bytecode reader and cannot recompute the annotation fingerprint, but hashing the class file needs none. The manifest now records cn1.buildHints.classDigest -- SHA-256 of the compiled main class -- and that decides, in a directory or inside a jar. Timestamps remain the fallback for a manifest written before the key existed, so an older one is not refused on a guess. Nothing rewrites the class after process-classes in a Codename One project; a project that added such a step would see its manifest reported stale, which is why an unreadable class is "cannot tell" rather than proof. Two migration findings alongside. An import whose package merely BEGINS with ours -- com.codename1.annotations.buildhintsExtra.Widget -- was read as "already imported" and aborted a migration with nothing to conflict with. And a blanked block comment keeps its newlines, so `import foo.Bar /* note\n */ ;` left the semicolon unconsumed and ended the declaration at that newline, inside the comment, where the generated import was written and stayed commented out. The package declaration had the same hazard and the same terminator, so both now share one step. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 124 +++++++++++++++++- .../maven/MigrateBuildHintsMojo.java | 47 ++++--- .../BuildHintAnnotationProcessor.java | 59 +++++++++ .../MigrateBuildHintsPropertyParsingTest.java | 40 ++++++ .../BuildHintAnnotationProcessorTest.java | 37 ++++++ .../SimulatorAnnotationManifestTest.java | 68 +++++++++- 6 files changed, 350 insertions(+), 25 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index 9a9d4335c3e..94474a674f9 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -510,11 +510,8 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat } // Sharing an archive does not mean sharing a build. Nothing deletes an // old manifest from target/classes, so a recompiled main class and last - // week's resource are packaged into the same jar -- with their own entry - // timestamps, which is what tells them apart. - String staleAgainst = found.jar != null - ? classNewerThanManifestInJar(p, found.jar) - : (f == null ? null : classNewerThanManifest(p, f)); + // week's resource are packaged into the same jar. + String staleAgainst = staleManifestReason(p, found); if (staleAgainst != null) { // Nothing removes target/classes between builds, so a project that ran // process-annotations once and then stopped -- goal unbound, skipped, @@ -531,8 +528,8 @@ private static void publishAnnotationBuildHints(File projectDir, String classPat // the direction that matters -- process-classes always follows compile // within a build, so a main class newer than the manifest cannot have // produced it. - System.err.println("Warning: " + found.where + " is older than " - + staleAgainst + ", so it was produced by an earlier build " + System.err.println("Warning: " + found.where + " " + staleAgainst + + ", so it was produced by an earlier build " + "and its build hints were NOT applied."); System.err.println(" Rebuild the project so the cn1 process-annotations " + "goal regenerates it."); @@ -857,6 +854,119 @@ private static java.util.Properties readJarEntry(File jar, String entryName) { * no class file for it, no readable timestamps -- so the manifest is taken at * face value rather than discarded on a guess.

*/ + /** + * Why the manifest was left behind by an earlier build, or null when it is + * current. + * + *

By the class file's own contents when the manifest records them, and + * only otherwise by timestamps. Timestamps are not always available to + * compare: a jar records entry times to two-second granularity, and a build + * configured for reproducible output stamps every entry identically, which + * makes the comparison inert rather than merely coarse. A manifest with no + * recorded digest is one an older plugin wrote, so it falls back rather than + * being refused.

+ */ + static String staleManifestReason(java.util.Properties manifest, + FoundManifest found) { + String main = manifest.getProperty("cn1.buildHints.mainClass"); + if (main == null || main.trim().length() == 0) { + return null; + } + String entry = main.trim().replace('.', '/') + ".class"; + String recorded = manifest.getProperty("cn1.buildHints.classDigest"); + if (recorded != null && recorded.length() > 0) { + String actual = found.jar != null + ? digestOfJarEntry(found.jar, entry) + : digestOfFile(classFileBeside(found.file, main)); + if (actual != null) { + return recorded.equals(actual) ? null + : "does not describe the compiled " + entry; + } + } + String older = found.jar != null + ? classNewerThanManifestInJar(manifest, found.jar) + : (found.file == null ? null : classNewerThanManifest(manifest, found.file)); + return older == null ? null : "is older than " + older; + } + + /** The compiled main class in the output directory the manifest sits in. */ + private static File classFileBeside(File manifestFile, String main) { + if (manifestFile == null) { + return null; + } + File classes = manifestFile.getParentFile(); // .../codenameone + classes = classes == null ? null : classes.getParentFile(); // .../META-INF + classes = classes == null ? null : classes.getParentFile(); // the output dir + if (classes == null) { + return null; + } + File f = new File(classes, main.trim().replace('.', File.separatorChar) + ".class"); + return f.isFile() ? f : null; + } + + /** SHA-256 of a file, hex, or null when it cannot be read. */ + private static String digestOfFile(File f) { + if (f == null) { + return null; + } + try { + java.io.InputStream in = new FileInputStream(f); + try { + return digestOf(in); + } finally { + in.close(); + } + } catch (IOException ex) { + return null; + } + } + + /** SHA-256 of one jar entry, hex, or null when it is absent or unreadable. */ + static String digestOfJarEntry(File jar, String entryName) { + java.util.zip.ZipFile zip = null; + try { + zip = new java.util.zip.ZipFile(jar); + java.util.zip.ZipEntry entry = zip.getEntry(entryName); + if (entry == null) { + return null; + } + java.io.InputStream in = zip.getInputStream(entry); + try { + return digestOf(in); + } finally { + in.close(); + } + } catch (IOException ex) { + return null; + } finally { + if (zip != null) { + try { + zip.close(); + } catch (IOException ignored) { + // read-only archive + } + } + } + } + + private static String digestOf(java.io.InputStream in) throws IOException { + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + byte[] buf = new byte[8192]; + for (int n = in.read(buf); n > 0; n = in.read(buf)) { + md.update(buf, 0, n); + } + StringBuilder hex = new StringBuilder(); + for (byte b : md.digest()) { + hex.append(Character.forDigit((b >> 4) & 0xF, 16)); + hex.append(Character.forDigit(b & 0xF, 16)); + } + return hex.toString(); + } catch (java.security.NoSuchAlgorithmException ex) { + return null; + } + } + /** * As above, for a manifest read out of a jar: the two entries' own * timestamps, since being in one archive proves nothing about which build diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 98302567e5e..f6bab1e75d0 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -912,12 +912,39 @@ private void insertAnnotations(File source, String annotations, String simpleNam * happens to appear first.

*/ /// Whether blanked `code` contains a live import of the build hint package. + /// The end of the declaration that has been read up to `i`: its terminating + /// semicolon if it has one, then the rest of that line. + /// + /// Any whitespace before the semicolon, newlines included -- a blanked block + /// comment keeps its newlines, so `import foo.Bar /* note\n */ ;` left the + /// semicolon unconsumed and ended the declaration at that newline, INSIDE + /// the comment. The generated import was written there and stayed commented + /// out, so the annotations failed verification and a valid migration was + /// rolled back. Only taken when a semicolon is what follows, so a Kotlin + /// declaration that has none still ends on its own line. + private static int endOfDeclarationLine(String code, int i) { + int probe = i; + while (probe < code.length() && Character.isWhitespace(code.charAt(probe))) { + probe++; + } + if (probe < code.length() && code.charAt(probe) == ';') { + i = probe + 1; + } + int eol = code.indexOf('\n', i); + return eol < 0 ? code.length() : eol + 1; + } + static boolean importsBuildHints(String code) { for (int at = importKeywordAt(code, 0); at >= 0; at = importKeywordAt(code, at + "import".length())) { String name = com.codename1.maven.processors.BuildHintAnnotationProcessor .qualifiedNameAt(code, at + "import".length()); - if (name.startsWith("com.codename1.annotations.buildhints")) { + // The PACKAGE, not any name that starts with its letters. An + // unrelated com.codename1.annotations.buildhintsExtra.Widget read as + // "already imported" and aborted a migration with nothing to + // conflict with. + if (name.equals("com.codename1.annotations.buildhints") + || name.startsWith("com.codename1.annotations.buildhints.")) { return true; } } @@ -988,14 +1015,7 @@ static int endOfImportDeclaration(String code, int importAt) { } i = probe + 2; } - while (i < code.length() && (code.charAt(i) == ' ' || code.charAt(i) == '\t')) { - i++; - } - if (i < code.length() && code.charAt(i) == ';') { - i++; - } - int eol = code.indexOf('\n', i); - return eol < 0 ? code.length() : eol + 1; + return endOfDeclarationLine(code, i); } /// The offset of the `package` keyword in already-blanked code, or -1. @@ -1034,14 +1054,7 @@ static int endOfPackageDeclaration(String code, int pkgAt) { // disagree about where a name ends. int i = com.codename1.maven.processors.BuildHintAnnotationProcessor .qualifiedNameEnd(code, pkgAt + "package".length()); - while (i < code.length() && (code.charAt(i) == ' ' || code.charAt(i) == '\t')) { - i++; - } - if (i < code.length() && code.charAt(i) == ';') { - i++; - } - int eol = code.indexOf('\n', i); - return eol < 0 ? code.length() : eol + 1; + return endOfDeclarationLine(code, i); } /// The index in `head` where the run of annotations immediately preceding the diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index f5e9d98fd25..8bed0d43ef9 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -88,6 +88,20 @@ public class BuildHintAnnotationProcessor extends AbstractAnnotationProcessor { /// shipping last week's configuration. public static final String SOURCE_DIGEST_KEY = "cn1.buildHints.sourceDigest"; + /// The compiled main class's own bytes, for a consumer that cannot read + /// bytecode. + /// + /// The simulator lives in the JavaSE port and has no bytecode reader, so it + /// cannot recompute [#SOURCE_DIGEST_KEY] and was left comparing file + /// timestamps. Those are not always available to compare: a jar records + /// entry times to two-second granularity, and a build configured for + /// reproducible output stamps every entry identically -- which makes the + /// comparison inert rather than merely coarse, so a manifest left behind by + /// an earlier build reads as current and the simulator runs the previous + /// values of hints it can actually see. Hashing the class file needs no + /// bytecode reader at all. + public static final String CLASS_DIGEST_KEY = "cn1.buildHints.classDigest"; + /// hint name to value, sorted so the emitted bytes are stable. private final Map hints = new TreeMap(); /// hint name to "@Ios(pods)". @@ -1257,6 +1271,47 @@ private static void renderForDigest(Object value, StringBuilder sb) { } } + /// SHA-256 of the compiled main class, hex, or null when it cannot be read. + /// + /// Nothing rewrites the class after `process-classes` in a Codename One + /// project, so this identifies the build that produced the manifest beside + /// it. A project that does add such a step would see the manifest reported + /// as stale, which is why the consumer treats a missing value as "cannot + /// tell" rather than as proof of anything. + private static String compiledClassDigest(ProcessorContext ctx, String main) { + if (main == null) { + return null; + } + File dir = ctx.getOutputClassDir(); + if (dir == null) { + return null; + } + File f = new File(dir, main.replace('.', File.separatorChar) + ".class"); + if (!f.isFile()) { + return null; + } + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + java.io.InputStream in = new java.io.FileInputStream(f); + try { + byte[] buf = new byte[8192]; + for (int n = in.read(buf); n > 0; n = in.read(buf)) { + md.update(buf, 0, n); + } + } finally { + in.close(); + } + StringBuilder hex = new StringBuilder(); + for (byte b : md.digest()) { + hex.append(Character.forDigit((b >> 4) & 0xF, 16)); + hex.append(Character.forDigit(b & 0xF, 16)); + } + return hex.toString(); + } catch (java.io.IOException | java.security.NoSuchAlgorithmException ex) { + return null; + } + } + private byte[] serialize(ProcessorContext ctx) throws ProcessingException { StringBuilder sb = new StringBuilder(); sb.append("# Generated from build hint annotations by the Codename One Maven plugin.\n"); @@ -1267,6 +1322,10 @@ private byte[] serialize(ProcessorContext ctx) throws ProcessingException { } sb.append(SOURCE_DIGEST_KEY).append('=') .append(sourceDigest(annotated.get(0))).append('\n'); + String compiled = compiledClassDigest(ctx, main); + if (compiled != null) { + sb.append(CLASS_DIGEST_KEY).append('=').append(compiled).append('\n'); + } for (Map.Entry e : hints.entrySet()) { sb.append(escape(BuildHints.ARG_PREFIX + e.getKey())).append('=') .append(escape(e.getValue())).append('\n'); diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 50faf2aea3e..80ec6940bd3 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -371,6 +371,46 @@ public void theDeclarationLocatorReadsAnEscapedKotlinName() { MigrateBuildHintsMojo.classDeclarationIndex(src, true, "Other")); } + /// The build hints PACKAGE, not any name that starts with its letters. An + /// unrelated `com.codename1.annotations.buildhintsExtra.Widget` read as + /// "already imported" and aborted a migration with nothing to conflict with. + @Test + public void anImportOfALongerPackageIsNotOurs() { + String other = com.codename1.maven.processors.BuildHintAnnotationProcessor.blankNonCode( + "import com.codename1.annotations.buildhintsExtra.Widget;\n" + + "public class MyApp {}\n", false); + assertFalse(MigrateBuildHintsMojo.importsBuildHints(other)); + + String ours = com.codename1.maven.processors.BuildHintAnnotationProcessor.blankNonCode( + "import com.codename1.annotations.buildhints.*;\npublic class MyApp {}\n", + false); + assertTrue(MigrateBuildHintsMojo.importsBuildHints(ours)); + } + + /// A blanked block comment keeps its newlines, so + /// `import foo.Bar /* note\n */ ;` left the semicolon unconsumed and ended + /// the declaration at that newline -- INSIDE the comment, where the + /// generated import was then written and stayed commented out. + @Test + public void theInsertionPointClearsAMultiLineTrailingComment() { + String head = "import foo.Bar /* note\n */ ;\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + int last = MigrateBuildHintsMojo.lastImportIndex(code); + assertEquals(head.length(), MigrateBuildHintsMojo.endOfImportDeclaration(code, last)); + } + + /// The package declaration has the same terminator and the same hazard. + @Test + public void theAnchorClearsAMultiLineTrailingComment() { + String head = "package com.example /* note\n */ ;\nclass X {}\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(head, false); + int pkg = MigrateBuildHintsMojo.livePackageIndex(code); + assertEquals(head.indexOf("class X"), + MigrateBuildHintsMojo.endOfPackageDeclaration(code, pkg)); + } + /// `static` is a modifier, not the imported name. Reading it as the name /// ended the declaration at the newline inside the REAL name, so the /// generated import was spliced into the middle of the static import and the diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 82bde84ed8d..6e4f8c90b33 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -582,6 +582,43 @@ public void aKotlinLocalClassPathIsInconclusiveNotAnOrphan() { false)); } + /// The simulator has no bytecode reader, so it cannot recompute the source + /// digest and was left comparing file timestamps -- which a jar records to + /// two seconds and a reproducible build stamps identically, making the + /// comparison inert rather than coarse. Hashing the class file needs no + /// bytecode reader, so the manifest records that instead. + @Test + public void theManifestRecordsTheCompiledClassesOwnDigest() throws Exception { + File classes = compile("@Ios(teamId = \"ABCDE12345\")"); + ProcessorContext ctx = run(classes, settings(), MAIN, true); + Properties p = new Properties(); + p.load(new ByteArrayInputStream(ctx.getEmittedResources() + .get(BuildHintAnnotationProcessor.MANIFEST_RESOURCE))); + + String recorded = p.getProperty(BuildHintAnnotationProcessor.CLASS_DIGEST_KEY); + assertTrue("no class digest was recorded", recorded != null); + assertEquals(sha256Of(new File(classes, "com/example/MyApp.class")), recorded); + } + + private static String sha256Of(File f) throws Exception { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + java.io.InputStream in = new java.io.FileInputStream(f); + try { + byte[] buf = new byte[8192]; + for (int n = in.read(buf); n > 0; n = in.read(buf)) { + md.update(buf, 0, n); + } + } finally { + in.close(); + } + StringBuilder hex = new StringBuilder(); + for (byte b : md.digest()) { + hex.append(Character.forDigit((b >> 4) & 0xF, 16)); + hex.append(Character.forDigit(b & 0xF, 16)); + } + return hex.toString(); + } + /// A qualified name may escape a COMPONENT: `package com.`when`` is legal /// Kotlin and the compiled class belongs to `com.when`. Stopping at the /// backtick recorded `com.`, so a live annotated class looked like it diff --git a/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java b/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java index a03d0236f24..a3a8351e78f 100644 --- a/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java +++ b/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java @@ -118,7 +118,7 @@ private static File jarWith(File dir, String name, long classTime, long manifest ZipEntry cls = new ZipEntry("com/example/MyApp.class"); cls.setTime(classTime); out.putNextEntry(cls); - out.write(new byte[] {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE}); + out.write(CLASS_BYTES); out.closeEntry(); ZipEntry res = new ZipEntry("META-INF/codenameone/build-hints.properties"); @@ -173,4 +173,70 @@ public void aJarWithoutTheMainClassIsNotJudged(@TempDir File tmp) throws Excepti assertNull(Simulator.classNewerThanManifestInJar( stampedFor("com.other.TheirApp"), jar)); } + + private static final byte[] CLASS_BYTES = + new byte[] {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE}; + + /** SHA-256 of what jarWith puts in the class entry, hex. */ + private static String classBytesDigest() throws Exception { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + md.update(CLASS_BYTES); + StringBuilder hex = new StringBuilder(); + for (byte b : md.digest()) { + hex.append(Character.forDigit((b >> 4) & 0xF, 16)); + hex.append(Character.forDigit(b & 0xF, 16)); + } + return hex.toString(); + } + + private static Properties stamped(String mainClass, String classDigest) { + Properties p = stampedFor(mainClass); + if (classDigest != null) { + p.setProperty("cn1.buildHints.classDigest", classDigest); + } + return p; + } + + /** + * Zip records entry times to two seconds, and a build configured for + * reproducible output stamps every entry identically -- so a stale manifest + * and the class it no longer describes can compare EQUAL, which made the + * timestamp rule inert rather than merely coarse. The recorded contents of + * the class settle it. + */ + @Test + public void aStaleManifestIsDetectedWhenTheTimestampsAreEqual(@TempDir File tmp) + throws Exception { + long same = 1600000000000L; + File jar = jarWith(tmp, "app-reproducible.jar", same, same); + + Simulator.FoundManifest found = new Simulator.FoundManifest( + stamped("com.example.MyApp", "0000deadbeef"), null, jar, "app-reproducible.jar"); + assertEquals("does not describe the compiled com/example/MyApp.class", + Simulator.staleManifestReason(found.hints, found)); + } + + /** ...and a digest that matches settles it the other way, timestamps aside. */ + @Test + public void aMatchingClassDigestOutranksTheTimestamps(@TempDir File tmp) throws Exception { + long manifest = 1600000000000L; + File jar = jarWith(tmp, "app-touched.jar", manifest + 60000L, manifest); + + Simulator.FoundManifest found = new Simulator.FoundManifest( + stamped("com.example.MyApp", classBytesDigest()), null, jar, "app-touched.jar"); + assertNull(Simulator.staleManifestReason(found.hints, found)); + } + + /** A manifest an older plugin wrote records no digest, so timestamps still decide. */ + @Test + public void withoutARecordedDigestTheTimestampsStillDecide(@TempDir File tmp) + throws Exception { + long manifest = 1600000000000L; + File jar = jarWith(tmp, "app-old.jar", manifest + 60000L, manifest); + + Simulator.FoundManifest found = new Simulator.FoundManifest( + stamped("com.example.MyApp", null), null, jar, "app-old.jar"); + assertEquals("is older than com/example/MyApp.class", + Simulator.staleManifestReason(found.hints, found)); + } } From 0e509f8f4b6003f556f723e4dd76061ee9b7724d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:14:51 +0300 Subject: [PATCH 068/115] Retry a fetch that never reached its host The wrapper added last commit did its job -- it declined to retry, because `UnknownHostException: github.com` while downloading the skins zip is not a Maven resolution shape. It is still an outage on the runner's network and not a fact about the branch, and Ant's own exhausted itself inside the same ten seconds. Added to the two steps that only download and build. Deliberately NOT to the test step, where retrying a network-dependent test would launder exactly the flake this repository requires to be root-caused. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ant.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index 8fc4f8b3192..5ce59f973b9 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -55,7 +55,13 @@ jobs: # failing on the first attempt, so a re-run cannot launder a flaky test # into a pass. env: - RETRY_ONLY_MATCHING: 'status( code)?: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not collect dependencies' + # These two steps only download and build, so a fetch that never reached + # its host is retried here as well -- the skins zip comes from github.com + # over the same runner network, and a DNS failure there killed the branch + # while Ant's own was still inside the outage. NOT added to + # the test step below, where retrying a network-dependent test would + # launder exactly the flake this repository requires to be root-caused. + RETRY_ONLY_MATCHING: 'status( code)?: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not collect dependencies|UnknownHostException|Unknown host|Connection timed out|Connection reset|Premature end of Content-Length|Error getting https' run: | # The cn1{app,lib}-archetype modules are part of the maven/ reactor, # so this single install also stages them into the local repo. @@ -66,7 +72,7 @@ jobs: - name: Refresh archetype catalog env: - RETRY_ONLY_MATCHING: 'status( code)?: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not collect dependencies' + RETRY_ONLY_MATCHING: 'status( code)?: (403|429|50[0-9])|Could not transfer artifact|Failed to read artifact descriptor|Unresolveable build extension|Non-resolvable import POM|Could not collect dependencies|UnknownHostException|Unknown host|Connection timed out|Connection reset|Premature end of Content-Length|Error getting https' run: | cd maven bash ../scripts/ci/retry.sh xvfb-run -a mvn archetype:update-local-catalog -Plocal-dev-javase From e909db92843b6d2ab5a56e0bda5904614e91748d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:19:47 +0300 Subject: [PATCH 069/115] Step over escaped identifiers when looking for a keyword blankNonCode deliberately leaves a Kotlin escaped identifier as the code it is, because the declared name has to stay readable -- so every scanner looking for a KEYWORD has to step over it, and none of them did. `fun `import`() {}` declares a function called import. Reading it as an import directive put the generated import after a top-level declaration, where Kotlin does not allow one, so verification failed and rolled back a valid migration. `val `class Main`` is a property, and reading it as a declaration made a class that belongs elsewhere look like it belonged here -- the orphan-filter identity bug one more time. One shared step now, used by both declaration scanners, both keyword lookups and the migration's own locator. It also keeps the brace count honest, since `{` is a legal character inside an escaped name. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 22 +++++++++++++ .../BuildHintAnnotationProcessor.java | 33 +++++++++++++++++++ .../MigrateBuildHintsPropertyParsingTest.java | 23 +++++++++++++ .../BuildHintAnnotationProcessorTest.java | 12 +++++++ 4 files changed, 90 insertions(+) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index f6bab1e75d0..8b4548630d1 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -964,6 +964,14 @@ static int lastImportIndex(String code) { private static int importKeywordAt(String code, int from) { int i = from; while (i < code.length()) { + // A Kotlin escaped identifier is code, not a keyword: `fun + // `import`() {}` declares a function called import. + int escaped = com.codename1.maven.processors.BuildHintAnnotationProcessor + .escapedIdentifierEnd(code, i); + if (escaped > i) { + i = escaped; + continue; + } char c = code.charAt(i); if (!Character.isJavaIdentifierStart(c) || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { @@ -1022,6 +1030,14 @@ static int endOfImportDeclaration(String code, int importAt) { static int livePackageIndex(String code) { int i = 0; while (i < code.length()) { + // A Kotlin escaped identifier is code, not a keyword: `fun + // `import`() {}` declares a function called import. + int escaped = com.codename1.maven.processors.BuildHintAnnotationProcessor + .escapedIdentifierEnd(code, i); + if (escaped > i) { + i = escaped; + continue; + } char c = code.charAt(i); if (!Character.isJavaIdentifierStart(c) || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { @@ -1127,6 +1143,12 @@ static int classDeclarationIndex(String text, boolean kotlin, String simpleName) int depth = 0; int i = 0; while (i < code.length()) { + int escaped = com.codename1.maven.processors.BuildHintAnnotationProcessor + .escapedIdentifierEnd(code, i); + if (escaped > i) { + i = escaped; + continue; + } char c = code.charAt(i); if (c == '{') { depth++; diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 8bed0d43ef9..bfa3920dc6e 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -437,6 +437,11 @@ private static int declarationOf(String code, String simple, int from, int end, int depth = 0; int i = from; while (i < end && i < code.length()) { + int escaped = escapedIdentifierEnd(code, i); + if (escaped > i) { + i = escaped; + continue; + } char c = code.charAt(i); if (c == '{') { depth++; @@ -472,6 +477,24 @@ private static int declarationOf(String code, String simple, int from, int end, return -1; } + /// The index just past a Kotlin escaped identifier at `i`, or -1 when there + /// is not one there. + /// + /// [#blankNonCode] leaves these as the code they are, because a declared + /// name has to stay readable -- so every scanner looking for a KEYWORD has + /// to step over them itself. `fun `import`() {}` declares a function called + /// import, not an import directive, and reading it as one put the generated + /// import after a top-level declaration where Kotlin does not allow it. + /// Stepping over the run also keeps the brace count honest, since `{` is a + /// legal character inside one. + public static int escapedIdentifierEnd(String code, int i) { + if (i < 0 || i >= code.length() || code.charAt(i) != '`') { + return -1; + } + int close = code.indexOf('`', i + 1); + return close < 0 ? -1 : close + 1; + } + /// The declared simple name at `n`, or null when the source runs out. /// /// Kotlin lets a declaration ESCAPE its name in backticks -- `class `when`` @@ -514,6 +537,11 @@ private static int companionObjectAt(String code, int from, int end) { int depth = 0; int i = from; while (i < end && i < code.length()) { + int escaped = escapedIdentifierEnd(code, i); + if (escaped > i) { + i = escaped; + continue; + } char c = code.charAt(i); if (c == '{') { depth++; @@ -570,6 +598,11 @@ private static int functionDeclarationOf(String code, String simple, int from, i int depth = 0; int i = from; while (i < end && i < code.length()) { + int escaped = escapedIdentifierEnd(code, i); + if (escaped > i) { + i = escaped; + continue; + } char c = code.charAt(i); if (c == '{') { depth++; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 80ec6940bd3..5ff40cb1aad 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -371,6 +371,29 @@ public void theDeclarationLocatorReadsAnEscapedKotlinName() { MigrateBuildHintsMojo.classDeclarationIndex(src, true, "Other")); } + /// `blankNonCode` leaves an escaped identifier as the code it is, so a + /// scanner looking for a KEYWORD has to step over it. `fun `import`() {}` + /// declares a function called import, and reading it as an import directive + /// put the generated import after a top-level declaration -- where Kotlin + /// does not allow one, so verification failed and rolled back a valid + /// migration. + @Test + public void anEscapedIdentifierIsNotAKeyword() { + String kt = "package com.example\n\nfun `import`() {}\n\nclass MyApp\n"; + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(kt, true); + assertEquals(-1, MigrateBuildHintsMojo.lastImportIndex(code)); + assertFalse(MigrateBuildHintsMojo.importsBuildHints(code)); + assertEquals(0, MigrateBuildHintsMojo.livePackageIndex(code)); + + // ...and the package keyword the same way. A file with no package + // declaration at all has none, whatever a function is called. + String noPackage = "fun `package`() {}\n\nclass MyApp\n"; + assertEquals(-1, MigrateBuildHintsMojo.livePackageIndex( + com.codename1.maven.processors.BuildHintAnnotationProcessor + .blankNonCode(noPackage, true))); + } + /// The build hints PACKAGE, not any name that starts with its letters. An /// unrelated `com.codename1.annotations.buildhintsExtra.Widget` read as /// "already imported" and aborted a migration with nothing to conflict with. diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 6e4f8c90b33..992e75cfcad 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -619,6 +619,18 @@ private static String sha256Of(File f) throws Exception { return hex.toString(); } + /// An escaped identifier may contain anything, spaces and keywords + /// included, and it is left as the code it is -- so a declaration scanner + /// has to step over it rather than read what is inside. `val `class Main`` + /// declares a property, and reading it as a declaration of Main made a class + /// that belongs elsewhere look like it belonged here. + @Test + public void anEscapedIdentifierIsNotADeclaration() { + String kt = "package com.example\nval `class Main` = 1\nclass Other\n"; + assertFalse(BuildHintAnnotationProcessor.declaresType(kt, "Main", true)); + assertTrue(BuildHintAnnotationProcessor.declaresType(kt, "Other", true)); + } + /// A qualified name may escape a COMPONENT: `package com.`when`` is legal /// Kotlin and the compiled class belongs to `com.when`. Stopping at the /// backtick recorded `com.`, so a live annotated class looked like it From 0f60f05c3d712cc3005ac7e175767935fe701d01 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:31:20 +0300 Subject: [PATCH 070/115] Stamp the class the build ships, not the one the compiler left A processor may REPLACE a class through emitClass, and the mojo flushes those only after every processor's finish() -- so the digest written during ours described the class as compiled, not as shipped. BindingAnnotationProcessor does exactly that for a main class with a two-way @Bindable setter, and processor order is whatever the service loader returns, so reading the queued bytes instead would only move the race. The stamp is corrected once the classes are written, which is the first moment the answer is stable; without it the simulator read a freshly generated manifest as stale and dropped every annotated hint under cn1:run. Two more source-reading corrections. The package scanner now steps over an escaped identifier like the declaration scanners already do: `fun `package helper`() {}` in a default-package file reported `helper` as the declared package, so a live annotated class in it looked like it belonged elsewhere. And `$` is a legal character in a Java type name, so a top-level `class Wrong$Type` really is called that. Reading every `$` as nesting looked for a `Wrong` that does not exist, dropped the live class as an orphan, and lost the placement error it should have raised. The name as spelled is now tried before it is read as a path. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/ProcessAnnotationsMojo.java | 15 +++ .../BuildHintAnnotationProcessor.java | 124 ++++++++++++++++-- .../BuildHintAnnotationProcessorTest.java | 88 +++++++++++++ 3 files changed, 213 insertions(+), 14 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java index fc211049bb3..88d6af4b840 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java @@ -227,6 +227,21 @@ index, getLog(), getCN1ProjectDir(), rawProjectSettings(), mainClassBinaryName() getLog().info("cn1: emitted " + resources.size() + " generated resource(s) under " + outputDirectory); } + + // The build hint manifest records the main class's own bytes so the + // simulator, which has no bytecode reader, can tell a current manifest + // from one an earlier build left behind. A processor may REPLACE that + // class through emitClass -- BindingAnnotationProcessor does, for a + // two-way @Bindable setter -- and those are flushed above, after every + // finish(). So the stamp is corrected here, which is the first moment + // the class on disk is final. A no-op when there is no manifest. + try { + com.codename1.maven.processors.BuildHintAnnotationProcessor + .restampClassDigest(outputDirectory); + } catch (IOException ioe) { + throw new MojoExecutionException( + "Could not stamp the build hint manifest under " + outputDirectory, ioe); + } } private static boolean intersects(Set a, Set b) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index bfa3920dc6e..edd088d53ea 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -31,9 +31,12 @@ import com.codename1.maven.annotations.ProcessorContext; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; @@ -254,6 +257,14 @@ public static boolean hasBackingSource(AnnotatedClass cls, List compileS String pkg = packageOf(cls.getBinaryName()); String simpleName = simpleNameOf(cls.getBinaryName()); String[] nestedName = nestedNameOf(cls.getBinaryName()); + // The name with its dollars intact. `$` is a legal character in a Java + // type name, so a top-level `class Wrong$Type` has binary name + // Wrong$Type and is not nested at all -- reading every `$` as nesting + // looked for a `Wrong` that does not exist, dropped the live class as an + // orphan, and lost the placement error it should have raised. + int lastDot = cls.getBinaryName().lastIndexOf('.'); + String wholeName = lastDot < 0 ? cls.getBinaryName() + : cls.getBinaryName().substring(lastDot + 1); boolean sawARoot = false; for (String root : roots) { File dir = new File(root); @@ -261,7 +272,7 @@ public static boolean hasBackingSource(AnnotatedClass cls, List compileS continue; } sawARoot = true; - if (declaresPackage(dir, sourceFile, pkg, simpleName, nestedName, 0)) { + if (declaresPackage(dir, sourceFile, pkg, simpleName, nestedName, wholeName, 0)) { return true; } } @@ -301,7 +312,7 @@ private static String simpleNameOf(String binaryName) { /// because Kotlin does not require the two to agree. Depth-limited: this runs /// per annotated class and a source tree is not a search index. private static boolean declaresPackage(File dir, String name, String pkg, String simple, - String[] nested, int depth) { + String[] nested, String whole, int depth) { if (depth > 24) { return false; } @@ -311,10 +322,11 @@ private static boolean declaresPackage(File dir, String name, String pkg, String } for (File f : children) { if (f.isFile()) { - if (f.getName().equals(name) && matches(f, pkg, simple, nested)) { + if (f.getName().equals(name) && matches(f, pkg, simple, nested, whole)) { return true; } - } else if (f.isDirectory() && declaresPackage(f, name, pkg, simple, nested, depth + 1)) { + } else if (f.isDirectory() + && declaresPackage(f, name, pkg, simple, nested, whole, depth + 1)) { return true; } } @@ -329,7 +341,8 @@ private static boolean declaresPackage(File dir, String name, String pkg, String /// class that used to be in it -- keeping a stale annotation owner and /// failing the placement check on every incremental build, which is the very /// thing this guard was added to stop. - private static boolean matches(File f, String pkg, String simple, String[] nested) { + private static boolean matches(File f, String pkg, String simple, String[] nested, + String whole) { String text = readHead(f); if (text == null) { // Unreadable: answer yes, as everywhere else here, because the only @@ -339,8 +352,16 @@ private static boolean matches(File f, String pkg, String simple, String[] neste // The file names its own language, and a triple-quoted literal is read // differently in each. boolean kotlin = f.getName().endsWith(".kt"); - if (!pkg.equals(declaredPackageIn(text, kotlin)) - || !declaresType(text, simple, kotlin)) { + if (!pkg.equals(declaredPackageIn(text, kotlin))) { + return false; + } + // The name as spelled, before it is read as a nesting path: `$` is legal + // in a Java type name, so a top-level `class Wrong$Type` really is + // called that. + if (whole != null && !whole.equals(simple) && declaresType(text, whole, kotlin)) { + return true; + } + if (!declaresType(text, simple, kotlin)) { return false; } // The whole nesting PATH has to be there, in order. Checking only the @@ -991,6 +1012,16 @@ public static String declaredPackageIn(String text, boolean kotlin) { String code = blankNonCode(text, kotlin); int i = 0; while (i < code.length()) { + // An escaped identifier is left as the code it is, so the scan has + // to step over it: `fun `package helper`() {}` declares a function, + // and reading into it reported `helper` as the declared package -- + // which made a live annotated class in that file look like it + // belonged elsewhere and dropped it as an orphan. + int escaped = escapedIdentifierEnd(code, i); + if (escaped > i) { + i = escaped; + continue; + } char c = code.charAt(i); if (!Character.isJavaIdentifierStart(c) || (i > 0 && Character.isJavaIdentifierPart(code.charAt(i - 1)))) { @@ -1304,6 +1335,70 @@ private static void renderForDigest(Object value, StringBuilder sb) { } } + /// Rewrites [#CLASS_DIGEST_KEY] in an emitted manifest to describe the class + /// that is actually on disk. + /// + /// A processor may REPLACE a class through `emitClass`, and the mojo flushes + /// those only after every processor's `finish()` -- so a manifest written + /// during ours records the class as the compiler left it, not as the build + /// ships it. `BindingAnnotationProcessor` does exactly that for a main class + /// with a two-way `@Bindable` setter, and processor order is whatever the + /// service loader returns, so reading the queued bytes instead would only + /// move the race. Called by the mojo once the classes are written, which is + /// the first moment the answer is stable. + /// + /// Silent when there is no manifest, no main class recorded, or no class + /// file: this only makes an existing stamp accurate. + public static void restampClassDigest(File outputDirectory) throws IOException { + File manifest = new File(outputDirectory, MANIFEST_RESOURCE); + if (!manifest.isFile()) { + return; + } + Properties p = new Properties(); + InputStream in = new FileInputStream(manifest); + try { + p.load(in); + } finally { + in.close(); + } + String main = p.getProperty(MAIN_CLASS_KEY); + String recorded = p.getProperty(CLASS_DIGEST_KEY); + if (main == null || recorded == null) { + return; + } + String actual = digestOfClassFile(outputDirectory, main); + if (actual == null || actual.equals(recorded)) { + return; + } + byte[] raw = readAllBytes(manifest); + String text = new String(raw, "ISO-8859-1"); + String replaced = text.replace(CLASS_DIGEST_KEY + "=" + recorded, + CLASS_DIGEST_KEY + "=" + actual); + if (replaced.equals(text)) { + return; + } + FileOutputStream out = new FileOutputStream(manifest); + try { + out.write(replaced.getBytes("ISO-8859-1")); + } finally { + out.close(); + } + } + + private static byte[] readAllBytes(File f) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + InputStream in = new FileInputStream(f); + try { + byte[] buf = new byte[8192]; + for (int n = in.read(buf); n > 0; n = in.read(buf)) { + bos.write(buf, 0, n); + } + } finally { + in.close(); + } + return bos.toByteArray(); + } + /// SHA-256 of the compiled main class, hex, or null when it cannot be read. /// /// Nothing rewrites the class after `process-classes` in a Codename One @@ -1312,11 +1407,12 @@ private static void renderForDigest(Object value, StringBuilder sb) { /// as stale, which is why the consumer treats a missing value as "cannot /// tell" rather than as proof of anything. private static String compiledClassDigest(ProcessorContext ctx, String main) { - if (main == null) { - return null; - } - File dir = ctx.getOutputClassDir(); - if (dir == null) { + return ctx.getOutputClassDir() == null ? null + : digestOfClassFile(ctx.getOutputClassDir(), main); + } + + private static String digestOfClassFile(File dir, String main) { + if (main == null || dir == null) { return null; } File f = new File(dir, main.replace('.', File.separatorChar) + ".class"); @@ -1325,7 +1421,7 @@ private static String compiledClassDigest(ProcessorContext ctx, String main) { } try { java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); - java.io.InputStream in = new java.io.FileInputStream(f); + InputStream in = new FileInputStream(f); try { byte[] buf = new byte[8192]; for (int n = in.read(buf); n > 0; n = in.read(buf)) { @@ -1340,7 +1436,7 @@ private static String compiledClassDigest(ProcessorContext ctx, String main) { hex.append(Character.forDigit(b & 0xF, 16)); } return hex.toString(); - } catch (java.io.IOException | java.security.NoSuchAlgorithmException ex) { + } catch (IOException | java.security.NoSuchAlgorithmException ex) { return null; } } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 992e75cfcad..9d2256a0ec7 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -619,6 +619,94 @@ private static String sha256Of(File f) throws Exception { return hex.toString(); } + /// A processor may REPLACE the main class through `emitClass`, and those are + /// flushed only after every processor's `finish()` -- so a manifest written + /// during ours records the class as the compiler left it, not as the build + /// ships it. The simulator would then read a freshly generated manifest as + /// stale and drop every annotated hint under `cn1:run`. + @Test + public void theStampIsCorrectedOnceTheClassesAreFlushed() throws Exception { + File classes = compile("@Ios(teamId = \"ABCDE12345\")"); + ProcessorContext ctx = run(classes, settings(), MAIN, true); + File manifest = new File(classes, BuildHintAnnotationProcessor.MANIFEST_RESOURCE); + manifest.getParentFile().mkdirs(); + java.nio.file.Files.write(manifest.toPath(), + ctx.getEmittedResources() + .get(BuildHintAnnotationProcessor.MANIFEST_RESOURCE)); + + // Stand in for the instrumented replacement a later processor writes. + File classFile = new File(classes, "com/example/MyApp.class"); + byte[] original = java.nio.file.Files.readAllBytes(classFile.toPath()); + byte[] replaced = new byte[original.length + 1]; + System.arraycopy(original, 0, replaced, 0, original.length); + java.nio.file.Files.write(classFile.toPath(), replaced); + + BuildHintAnnotationProcessor.restampClassDigest(classes); + + Properties after = new Properties(); + java.io.InputStream in = new java.io.FileInputStream(manifest); + try { + after.load(in); + } finally { + in.close(); + } + assertEquals(sha256Of(classFile), + after.getProperty(BuildHintAnnotationProcessor.CLASS_DIGEST_KEY)); + // Everything else is left exactly as it was. + assertEquals("ABCDE12345", after.getProperty("codename1.arg.ios.teamId")); + assertEquals(MAIN, after.getProperty("cn1.buildHints.mainClass")); + } + + /// Nothing to correct is not an error: a project with no build hint + /// annotations emits no manifest at all. + @Test + public void theStampStepIsSilentWithoutAManifest() throws Exception { + BuildHintAnnotationProcessor.restampClassDigest(tmp.newFolder()); + } + + /// The scan for the package keyword steps over an escaped identifier too. + /// `fun `package helper`() {}` in a default-package file reported `helper` + /// as the declared package, so a live annotated class in it looked like it + /// belonged elsewhere and was dropped as an orphan. + @Test + public void anEscapedIdentifierIsNotAPackageDeclaration() { + assertEquals("", BuildHintAnnotationProcessor.declaredPackageIn( + "fun `package helper`() {}\n\nclass MyApp\n", true)); + // The real one is still found when there is one. + assertEquals("com.example", BuildHintAnnotationProcessor.declaredPackageIn( + "package com.example\n\nfun `package helper`() {}\n", true)); + } + + /// `$` is a legal character in a Java type name, so a top-level + /// `class Wrong$Type` has binary name Wrong$Type and is not nested at all. + /// Reading every `$` as nesting looked for a `Wrong` that does not exist, + /// dropped the live class as an orphan, and lost the placement error it + /// should have raised. + @Test + public void aDollarInATopLevelJavaNameIsNotNesting() throws Exception { + File src = tmp.newFolder(); + File pkgDir = new File(src, "com/example"); + pkgDir.mkdirs(); + java.io.Writer w = new java.io.OutputStreamWriter( + new java.io.FileOutputStream(new File(pkgDir, "Wrong$Type.java")), "UTF-8"); + try { + w.write("package com.example;\npublic class Wrong$Type {\n}\n"); + } finally { + w.close(); + } + + File classes = tmp.newFolder(); + JavaSourceCompiler.compile( + JavaSourceCompiler.singleSource("com.example.Wrong$Type", + "package com.example;\npublic class Wrong$Type {\n}\n"), + classes, Arrays.asList(testClassesDir(), coreJar())); + AnnotatedClass cls = ClassScanner.scan(classes).get("com/example/Wrong$Type"); + assertTrue("the class under test must have been compiled", cls != null); + + assertTrue(BuildHintAnnotationProcessor.hasBackingSource(cls, + java.util.Collections.singletonList(src.getAbsolutePath()))); + } + /// An escaped identifier may contain anything, spaces and keywords /// included, and it is left as the code it is -- so a declaration scanner /// has to step over it rather than read what is inside. `val `class Main`` From 532784789962d35f7d0734f1ea0ea871ebc23629 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:41:47 +0300 Subject: [PATCH 071/115] Keep looking when the manifest found is the wrong build's The stamp says which application a manifest belongs to, not which build. A leftover output directory earlier on the classpath carries one stamped for this same main class, and taking it ended the search -- the caller then reported it stale and published nothing, while the current manifest sat in a later entry, so cn1:run dropped every annotated hint. Staleness is now decided during the search, and the first stale candidate is kept so that finding nothing current still says which file it was and why. Also, a Kotlin annotation may have an escaped name. A backtick is not an identifier character, so the backward walk over the leading annotations stopped on it and left `@`when`` out of the run; the generated import then went between the annotation and the class, where Kotlin does not allow one. The walk reads a component at a time now, so a qualified name with an escaped component is covered too. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/impl/javase/Simulator.java | 44 ++++++++++--- .../maven/MigrateBuildHintsMojo.java | 36 +++++++++-- .../MigrateBuildHintsPropertyParsingTest.java | 22 +++++++ .../SimulatorAnnotationManifestTest.java | 63 +++++++++++++++++++ 4 files changed, 152 insertions(+), 13 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java index 94474a674f9..b3bcadfdd27 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java @@ -723,6 +723,7 @@ static FoundManifest findAnnotationManifest(File projectDir, String classPathStr // two obsolete files against each other, finds them consistent, and // publishes last week's hints while the real ones sit on the classpath. FoundManifest fallback = null; + FoundManifest stale = null; if (classPathStr != null) { for (String entry : classPathStr.split(java.util.regex.Pattern.quote(File.pathSeparator))) { @@ -748,7 +749,21 @@ static FoundManifest findAnnotationManifest(File projectDir, String classPathStr new FoundManifest(loaded, candidate, null, candidate.toString()); if (expectedMain == null || expectedMain.equals(stamp)) { - return found; + // Right application, but possibly the wrong + // build: a leftover output directory earlier on + // the classpath carries a manifest stamped for + // this same main class, and taking it ended the + // search -- the caller then reported it stale and + // published nothing, while the current manifest + // sat in a later entry. The first stale one is + // kept so that finding NO current manifest still + // says why. + if (staleManifestReason(loaded, found) == null) { + return found; + } + if (stale == null) { + stale = found; + } } if (stamp == null && fallback == null) { // No stamp is not a foreign manifest, only an @@ -774,8 +789,14 @@ static FoundManifest findAnnotationManifest(File projectDir, String classPathStr java.util.Properties loaded = readJarEntry(dir, entryName); if (loaded != null && expectedMain.equals(loaded.getProperty("cn1.buildHints.mainClass"))) { - return new FoundManifest(loaded, null, dir, + FoundManifest found = new FoundManifest(loaded, null, dir, entryName + " in " + dir.getName()); + if (staleManifestReason(loaded, found) == null) { + return found; + } + if (stale == null) { + stale = found; + } } } } @@ -787,12 +808,21 @@ static FoundManifest findAnnotationManifest(File projectDir, String classPathStr // module's output directory at all still finds a conventional build. File conventional = new File(projectDir, "target" + File.separator + "classes" + File.separator + resource); - if (!conventional.isFile()) { - return null; + java.util.Properties loaded = conventional.isFile() ? readProperties(conventional) : null; + if (loaded != null) { + FoundManifest found = + new FoundManifest(loaded, conventional, null, conventional.toString()); + if (staleManifestReason(loaded, found) == null) { + return found; + } + if (stale == null) { + stale = found; + } } - java.util.Properties loaded = readProperties(conventional); - return loaded == null ? null - : new FoundManifest(loaded, conventional, null, conventional.toString()); + // Nothing current anywhere. The stale one is returned rather than + // nothing, so the caller can say which file it is and why it was not + // used instead of silently applying no hints at all. + return stale; } /** Loads a properties file, or null when it cannot be read. */ diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 8b4548630d1..dcac2760223 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1118,13 +1118,37 @@ static int startOfLeadingAnnotations(String original, boolean kotlin) { return at; } } - // The annotation's name, then its @. - int nameEnd = i + 1; - while (i >= 0 && (Character.isJavaIdentifierPart(head.charAt(i)) - || head.charAt(i) == '.')) { - i--; + // The annotation's name, then its @. A Kotlin component may be + // ESCAPED, and a backtick is not an identifier character -- so the + // scan stopped on it, read no name, and left `@`when`` out of the + // leading run. The generated import then went between the + // annotation and the class, where Kotlin does not allow one, and + // verification rolled back a valid migration. + boolean readAName = false; + while (i >= 0) { + if (head.charAt(i) == '`') { + int open = head.lastIndexOf('`', i - 1); + if (open < 0) { + return at; + } + i = open - 1; + readAName = true; + } else if (Character.isJavaIdentifierPart(head.charAt(i))) { + while (i >= 0 && Character.isJavaIdentifierPart(head.charAt(i))) { + i--; + } + readAName = true; + } else { + break; + } + // A qualified annotation name continues through its dots. + if (i >= 0 && head.charAt(i) == '.') { + i--; + continue; + } + break; } - if (i < 0 || head.charAt(i) != '@' || nameEnd == i + 1) { + if (i < 0 || head.charAt(i) != '@' || !readAName) { return at; } at = i; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 5ff40cb1aad..dbf2c5ddc93 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -241,6 +241,28 @@ public void theImportGoesAboveAnExistingAnnotation() { MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); } + /// A Kotlin annotation may have an ESCAPED name, and a backtick is not an + /// identifier character -- so the backward walk stopped on it, read no name, + /// and left `@`when`` out of the leading run. The generated import then went + /// between the annotation and the class, where Kotlin does not allow one. + @Test + public void anEscapedAnnotationNameIsPartOfTheLeadingRun() { + String head = "@`when`\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); + + String withArgs = "@`when`(\"x\")\n@Deprecated\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(withArgs)); + + // A qualified name may escape a component too. + String qualified = "@com.`when`.Ann\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(qualified)); + + // A lone backtick run that is not an annotation is still not one. + String notAnnotation = "`when`\n"; + assertEquals(notAnnotation.length(), + MigrateBuildHintsMojo.startOfLeadingAnnotations(notAnnotation)); + } + /// A parenthesis inside an annotation argument must not stop the walk. @Test public void anArgumentContainingAParenthesisDoesNotStopTheWalk() { diff --git a/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java b/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java index a3a8351e78f..3ce48681cea 100644 --- a/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java +++ b/maven/javase/src/test/java/com/codename1/impl/javase/SimulatorAnnotationManifestTest.java @@ -239,4 +239,67 @@ public void withoutARecordedDigestTheTimestampsStillDecide(@TempDir File tmp) assertEquals("is older than com/example/MyApp.class", Simulator.staleManifestReason(found.hints, found)); } + + /** A directory holding a class file and a manifest that does or does not describe it. */ + private static File outputDir(File parent, String name, String hint, boolean current) + throws Exception { + File dir = new File(parent, name); + File cls = new File(dir, "com/example/MyApp.class"); + cls.getParentFile().mkdirs(); + FileOutputStream cs = new FileOutputStream(cls); + try { + cs.write(CLASS_BYTES); + } finally { + cs.close(); + } + File out = new File(dir, "META-INF/codenameone"); + out.mkdirs(); + Properties p = new Properties(); + p.setProperty("cn1.buildHints.mainClass", "com.example.MyApp"); + p.setProperty("cn1.buildHints.classDigest", + current ? classBytesDigest() : "0000notthisbuild"); + p.setProperty("codename1.arg.desktop.titleBar", hint); + FileOutputStream os = new FileOutputStream(new File(out, "build-hints.properties")); + try { + p.store(os, null); + } finally { + os.close(); + } + return dir; + } + + /** + * A leftover output directory earlier on the classpath carries a manifest + * stamped for this same main class. Taking it ended the search, and the + * caller then reported it stale and published nothing -- while the current + * manifest sat in a later entry, so cn1:run dropped every + * annotated hint. + */ + @Test + public void aStaleManifestForThisApplicationIsPassedOver(@TempDir File tmp) throws Exception { + File old = outputDir(tmp, "stale-classes", "MINIMAL", false); + File now = outputDir(tmp, "current-classes", "NATIVE", true); + + String cp = old.getAbsolutePath() + File.pathSeparator + now.getAbsolutePath(); + Simulator.FoundManifest found = + Simulator.findAnnotationManifest(tmp, cp, "com.example.MyApp"); + assertNotNull(found); + assertEquals("NATIVE", found.hints.getProperty("codename1.arg.desktop.titleBar")); + } + + /** + * With nothing current anywhere the stale one is still returned, so the + * caller can say which file it is and why it was not used. + */ + @Test + public void withNothingCurrentTheStaleOneIsStillReported(@TempDir File tmp) throws Exception { + File old = outputDir(tmp, "only-stale", "MINIMAL", false); + + Simulator.FoundManifest found = + Simulator.findAnnotationManifest(tmp, old.getAbsolutePath(), "com.example.MyApp"); + assertNotNull(found); + assertEquals("MINIMAL", found.hints.getProperty("codename1.arg.desktop.titleBar")); + assertEquals("does not describe the compiled com/example/MyApp.class", + Simulator.staleManifestReason(found.hints, found)); + } } From c0a8abf079e2f08a6d6aabe802e2eb94451981a6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:52:30 +0300 Subject: [PATCH 072/115] Translate Java's unicode escapes, and follow a Kotlin typealias javac processes a unicode escape in the LEXICAL TRANSLATION step, before it tokenizes anything, so `package com.example;` really declares com.example and an escape works inside an identifier. Reading the text literally stopped the component at the backslash, so a live annotated class looked like it belonged elsewhere, was dropped as an orphan, and its placement error went unreported. Applied where the source is READ and nowhere else: offsets move, and the migration writes back at indices into the file as it is on disk. Kotlin has no such step. Kotlin can also rename a type in the file itself -- `typealias AppIos = Ios`, then `@AppIos(...)` -- with no import mentioning the alias. The compiled annotation is still ours, so the Settings ownership check missed it, left the hint editable, and Add wrote the duplicate declaration the next build refuses. That is the fourth spelling it now recognises, alongside the imported simple name, the qualified name and an import alias. Also halves two doc lines that an earlier edit had duplicated onto themselves. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 81 +++++++++++++++++++ .../BuildHintAnnotationProcessorTest.java | 43 ++++++++++ .../settings/CodenameOneSettings.java | 54 ++++++++++++- .../settings/BuildHintCatalogTest.java | 39 +++++++++ 4 files changed, 214 insertions(+), 3 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index edd088d53ea..d9ead9ab5f2 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -352,6 +352,12 @@ private static boolean matches(File f, String pkg, String simple, String[] neste // The file names its own language, and a triple-quoted literal is read // differently in each. boolean kotlin = f.getName().endsWith(".kt"); + // Java translates unicode escapes before tokenizing, so this has to as + // well or `package com.ex\u0061mple;` reads as com.ex. Only the answers + // below depend on the text, never an offset into the file. + if (!kotlin) { + text = decodeUnicodeEscapes(text); + } if (!pkg.equals(declaredPackageIn(text, kotlin))) { return false; } @@ -498,6 +504,81 @@ private static int declarationOf(String code, String simple, int from, int end, return -1; } + /// Java's unicode escapes, applied. + /// + /// javac processes `\\uXXXX` in the LEXICAL TRANSLATION step, before it + /// tokenizes anything -- so `package com.ex\\u0061mple;` really declares + /// com.example, and an escape works inside an identifier, a comment or + /// anywhere else. Reading the text literally stopped the package component + /// at the backslash and recorded com.ex, so a live annotated class looked + /// like it belonged elsewhere and was dropped as an orphan with its + /// placement error unreported. + /// + /// A backslash only starts an escape when an EVEN number of backslashes + /// precedes it, which is what keeps `"\\\\u0041"` the four characters it + /// looks like. Kotlin has no such step, so this is applied to Java only. + /// + /// Offsets move, so this is for the readers that answer questions about the + /// source -- never for the migration, which writes back at indices into the + /// text as it is on disk. + public static String decodeUnicodeEscapes(String text) { + if (text == null || text.indexOf('\\') < 0) { + return text; + } + StringBuilder out = new StringBuilder(text.length()); + int i = 0; + while (i < text.length()) { + char c = text.charAt(i); + if (c != '\\') { + out.append(c); + i++; + continue; + } + int j = i; + while (j < text.length() && text.charAt(j) == '\\') { + j++; + } + int run = j - i; + for (int pair = 0; pair < run / 2; pair++) { + out.append('\\').append('\\'); + } + if (run % 2 == 0) { + i = j; + continue; + } + // One backslash is left over, and only it can open an escape. + int u = j; + while (u < text.length() && text.charAt(u) == 'u') { + u++; + } + int value = u > j ? hexQuad(text, u) : -1; + if (value < 0) { + out.append('\\'); + i = j; + continue; + } + out.append((char) value); + i = u + 4; + } + return out.toString(); + } + + /// The four hex digits at `from`, or -1 when they are not four hex digits. + private static int hexQuad(String text, int from) { + if (from + 4 > text.length()) { + return -1; + } + int value = 0; + for (int i = from; i < from + 4; i++) { + int digit = Character.digit(text.charAt(i), 16); + if (digit < 0) { + return -1; + } + value = value * 16 + digit; + } + return value; + } + /// The index just past a Kotlin escaped identifier at `i`, or -1 when there /// is not one there. /// diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 9d2256a0ec7..56b74f725aa 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -677,6 +677,49 @@ public void anEscapedIdentifierIsNotAPackageDeclaration() { "package com.example\n\nfun `package helper`() {}\n", true)); } + /// javac processes `\\uXXXX` before it tokenizes anything, so + /// `package com.ex\\u0061mple;` really declares com.example -- and it + /// rejects an ill-formed one in a comment too, which is why the sequences + /// here are written with two backslashes. Reading the + /// text literally stopped the component at the backslash and recorded + /// `com.ex`, so a live annotated class looked like it belonged elsewhere and + /// was dropped as an orphan with its placement error unreported. + @Test + public void javaUnicodeEscapesAreTranslatedBeforeTheSourceIsRead() throws Exception { + // Through the orphan filter, which is where it decides anything: the + // source spells its package with an escape, the compiled class does not. + File src = tmp.newFolder(); + File pkgDir = new File(src, "com/example"); + pkgDir.mkdirs(); + java.io.Writer w = new java.io.OutputStreamWriter( + new java.io.FileOutputStream(new File(pkgDir, "Escaped.java")), "UTF-8"); + try { + w.write("package com.ex" + "\\u0061" + "mple;\npublic class Escaped {\n}\n"); + } finally { + w.close(); + } + File classes = tmp.newFolder(); + JavaSourceCompiler.compile( + JavaSourceCompiler.singleSource("com.example.Escaped", + "package com.example;\npublic class Escaped {\n}\n"), + classes, Arrays.asList(testClassesDir(), coreJar())); + AnnotatedClass cls = ClassScanner.scan(classes).get("com/example/Escaped"); + assertTrue("the class under test must have been compiled", cls != null); + assertTrue(BuildHintAnnotationProcessor.hasBackingSource(cls, + java.util.Collections.singletonList(src.getAbsolutePath()))); + + // A doubled backslash is not an escape, which is what keeps a string + // literal spelling one. + assertEquals("String s = \"\\\\u0041\";", + BuildHintAnnotationProcessor.decodeUnicodeEscapes( + "String s = \"\\\\u0041\";")); + + // Any number of u's is one escape, and a malformed one is left alone. + assertEquals("A", BuildHintAnnotationProcessor.decodeUnicodeEscapes("\\uuu0041")); + assertEquals("\\uZZZZ", BuildHintAnnotationProcessor.decodeUnicodeEscapes("\\uZZZZ")); + assertEquals("\\n", BuildHintAnnotationProcessor.decodeUnicodeEscapes("\\n")); + } + /// `$` is a legal character in a Java type name, so a top-level /// `class Wrong$Type` has binary name Wrong$Type and is not nested at all. /// Reading every `$` as nesting looked for a `Wrong` that does not exist, diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index d880a25f800..0eed76a0e3d 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2740,7 +2740,49 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin) { } - /// The name a Kotlin `import ... as Alias` gives an annotation, or null. /// The name a Kotlin `import ... as Alias` gives an annotation, or null. + /// The name a Kotlin `typealias Alias = Ios` gives an annotation, or null. + /// + /// Unlike an import alias this renames the type in the file itself, so the + /// annotation never appears under its own name and no import mentions the + /// alias at all. The right-hand side may be the simple name -- which only + /// counts when an import makes it ours -- or the fully qualified one, which + /// needs no import. + /// + /// One level: an alias of an alias is not followed, because the first is + /// what a file that renames our annotation actually writes. + static String kotlinTypeAlias(String source, String simple, boolean kotlin) { + if (!kotlin) { + return null; + } + String qualified = "com.codename1.annotations.buildhints." + simple; + boolean imported = importsAnnotation(source, simple, kotlin); + int at = nextMarker(source, "typealias", 0, kotlin); + while (at >= 0) { + int after = at + "typealias".length(); + boolean whole = (at == 0 || !continuesAName(source.charAt(at - 1))) + && after < source.length() && !continuesAName(source.charAt(after)); + if (whole) { + int n = nextLiveChar(source, after, kotlin); + if (n >= 0) { + int end = componentEnd(source, n, kotlin); + if (end > n) { + String name = componentText(source, n, end, kotlin); + int eq = nextLiveChar(source, end, kotlin); + if (eq >= 0 && source.charAt(eq) == '=') { + String target = qualifiedNameAt(source, eq + 1, kotlin); + if (target.equals(qualified) || (imported && target.equals(simple))) { + return name; + } + } + } + } + } + at = nextMarker(source, "typealias", after, kotlin); + } + return null; + } + + /// The name a Kotlin `import ... as Alias` gives an annotation, or null. /// /// Kotlin lets a file rename what it imports, and then the annotation never /// appears under its own name anywhere in the source. Missing that reads the @@ -2775,6 +2817,11 @@ static void collectAnnotationOwnedHints(String source, java.util.Map= 0 && source.charAt(open) == '(') { @@ -2903,7 +2951,7 @@ static int nextLiveChar(String source, int from, boolean kotlin) { return -1; } - /// The next occurrence of `marker` that is real code, or -1. /// The next occurrence of `marker` that is real code, or -1. + /// The next occurrence of `marker` that is real code, or -1. /// /// Comments and string literals are stepped over with the same scanner the /// argument reader uses, so what counts as code is one answer rather than diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 6f24912407d..b735c909eb2 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -931,4 +931,43 @@ public void aKotlinImportMayEscapeAComponent() { + "import com.other.`buildhints`.Ios\n" + "class MyApp\n", "Ios", true)); } + + /// Kotlin can rename a type in the FILE, with no import involved: + /// `typealias AppIos = Ios` and then `@AppIos(...)`. The compiled + /// annotation is still ours, so missing it left the hint editable and Add + /// wrote the duplicate declaration the next build refuses. + @Test + public void aKotlinTypeAliasStillOwnsTheHint() { + String src = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "typealias AppIos = Ios\n" + + "@AppIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // The fully qualified right-hand side needs no import. + String qualified = "package com.example\n" + + "typealias AppIos = com.codename1.annotations.buildhints.Ios\n" + + "@AppIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(qualified, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // Somebody else's annotation renamed to the same alias is not ours. + String theirs = "package com.example\n" + + "typealias AppIos = com.other.Ios\n" + + "@AppIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(theirs, owned, true); + assertNull(owned.get("ios.teamId")); + + // Java has no typealias, so the same text owns nothing there. + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned, false); + assertNull(owned.get("ios.teamId")); + } } From f1a00f950a2f25f55c613ffeda5b2598a7c116b1 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:03:49 +0300 Subject: [PATCH 073/115] Answer "cannot tell" when the search runs out, not "no source" The source-tree walk gave up at 24 directories by returning false, which reads as "this class has no source" -- so a live annotated class was dropped silently and its placement error lost, for the sake of a search bound. Everywhere else in this walk an unanswerable question keeps the class, because the only thing it decides is whether to IGNORE an annotation. Out of budget now says so, and the budget is 64. Modifiers and annotations interleave: `public @Deprecated final class Main` is legal, and the modifier walk from the keyword stops at the annotation. Backing up only over the annotation run left `public` above the insertion point, so the import went between it and the rest of its own declaration. A word that is not a modifier still ends the walk, or the anchor would climb out of the declaration entirely. And a file may name the same annotation twice -- two typealiases, or two imports with different aliases. Answering with the first left the one actually used unrecognised, so the hint read as unowned and Add wrote the duplicate the next build refuses. Both readers collect every name now. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 16 ++++++++ .../BuildHintAnnotationProcessor.java | 17 +++++++- .../MigrateBuildHintsPropertyParsingTest.java | 20 +++++++++ .../BuildHintAnnotationProcessorTest.java | 41 +++++++++++++++++++ .../settings/CodenameOneSettings.java | 38 +++++++++++++---- .../settings/BuildHintCatalogTest.java | 25 +++++++++++ 6 files changed, 146 insertions(+), 11 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index dcac2760223..8712b7361b7 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1099,6 +1099,22 @@ static int startOfLeadingAnnotations(String original, boolean kotlin) { if (i < 0) { return at; } + // A MODIFIER may sit between the annotations and the keyword, or + // before them: `public @Deprecated final class Main` is legal, and + // the modifier walk from the keyword stops at the annotation. Backing + // up only over the annotation run left `public` above the insertion + // point, so the import went between it and the rest of its own + // declaration and verification rolled the migration back. + int wordStart = i + 1; + while (wordStart > 0 && (Character.isJavaIdentifierPart(head.charAt(wordStart - 1)) + || head.charAt(wordStart - 1) == '-')) { + wordStart--; + } + if (wordStart <= i && (wordStart == 0 || head.charAt(wordStart - 1) != '@') + && isModifier(head.substring(wordStart, i + 1))) { + at = wordStart; + continue; + } if (head.charAt(i) == ')') { int depth = 0; while (i >= 0) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index d9ead9ab5f2..351b89fa9f9 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -300,6 +300,13 @@ private static String simpleNameOf(String binaryName) { return nested < 0 ? simple : simple.substring(0, nested); } + /// How deep the source tree is walked before the answer is given up on. + /// + /// Generous rather than tight: a package with this many components is not + /// something anyone writes, and the cost of guessing wrong is a live class + /// dropped without a word. + private static final int MAX_SOURCE_TREE_DEPTH = 64; + /// Whether a file called `name` declaring package `pkg` exists under `dir`. /// /// The package matters as well as the name: moving a class to another package @@ -313,8 +320,14 @@ private static String simpleNameOf(String binaryName) { /// per annotated class and a source tree is not a search index. private static boolean declaresPackage(File dir, String name, String pkg, String simple, String[] nested, String whole, int depth) { - if (depth > 24) { - return false; + if (depth > MAX_SOURCE_TREE_DEPTH) { + // Out of budget is "cannot tell", not "no such source". Answering no + // here dropped a live annotated class -- silently, and with its + // placement error lost -- for the sake of a search bound, which is + // the wrong way round: everywhere else in this walk an unanswerable + // question keeps the class, because the only thing it decides is + // whether to IGNORE an annotation. + return true; } File[] children = dir.listFiles(); if (children == null) { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index dbf2c5ddc93..cc63d9a5f13 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -241,6 +241,26 @@ public void theImportGoesAboveAnExistingAnnotation() { MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); } + /// Modifiers and annotations may INTERLEAVE: `public @Deprecated final + /// class Main` is legal. The modifier walk from the keyword stops at the + /// annotation, so backing up only over the annotation run left `public` + /// above the insertion point -- the import went between it and the rest of + /// its own declaration, and verification rolled the migration back. + @Test + public void theAnchorClearsModifiersInterleavedWithAnnotations() { + String head = "public @Deprecated "; + assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); + + // A modifier alone, with no annotation at all. + assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations("public ")); + + // A word that is NOT a modifier still ends the walk, or the anchor would + // climb out of the declaration and into whatever precedes it. + String other = "int x = 1;\n@Deprecated\n"; + assertEquals(other.indexOf("@Deprecated"), + MigrateBuildHintsMojo.startOfLeadingAnnotations(other)); + } + /// A Kotlin annotation may have an ESCAPED name, and a backtick is not an /// identifier character -- so the backward walk stopped on it, read no name, /// and left `@`when`` out of the leading run. The generated import then went diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 56b74f725aa..d348585f1b7 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -677,6 +677,47 @@ public void anEscapedIdentifierIsNotAPackageDeclaration() { "package com.example\n\nfun `package helper`() {}\n", true)); } + /// Running out of search budget is "cannot tell", not "no such source". + /// Answering no dropped a live annotated class silently, with its placement + /// error lost, for the sake of a bound -- which is the wrong way round: + /// everywhere else in this walk an unanswerable question keeps the class. + @Test + public void aDeepSourceTreeIsNotProofOfAnOrphan() throws Exception { + File classes = tmp.newFolder(); + JavaSourceCompiler.compile( + JavaSourceCompiler.singleSource("com.example.Deep", + "package com.example;\npublic class Deep {\n}\n"), + classes, Arrays.asList(testClassesDir(), coreJar())); + AnnotatedClass cls = ClassScanner.scan(classes).get("com/example/Deep"); + assertTrue("the class under test must have been compiled", cls != null); + + // Deeper than the old cutoff, and the file is genuinely there. + assertTrue(BuildHintAnnotationProcessor.hasBackingSource(cls, + java.util.Collections.singletonList(nest(30).getAbsolutePath()))); + + // Deeper than the budget: unanswerable, so the class is kept. + assertTrue(BuildHintAnnotationProcessor.hasBackingSource(cls, + java.util.Collections.singletonList(nest(70).getAbsolutePath()))); + } + + /// A source root with Deep.java `levels` directories down. + private File nest(int levels) throws Exception { + File root = tmp.newFolder(); + File at = root; + for (int i = 0; i < levels; i++) { + at = new File(at, "d" + i); + } + at.mkdirs(); + java.io.Writer w = new java.io.OutputStreamWriter( + new java.io.FileOutputStream(new File(at, "Deep.java")), "UTF-8"); + try { + w.write("package com.example;\npublic class Deep {\n}\n"); + } finally { + w.close(); + } + return root; + } + /// javac processes `\\uXXXX` before it tokenizes anything, so /// `package com.ex\\u0061mple;` really declares com.example -- and it /// rejects an ill-formed one in a comment too, which is why the sequences diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 0eed76a0e3d..555dfc9fe5f 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2751,8 +2751,18 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin) { /// One level: an alias of an alias is not followed, because the first is /// what a file that renames our annotation actually writes. static String kotlinTypeAlias(String source, String simple, boolean kotlin) { + java.util.List all = kotlinTypeAliases(source, simple, kotlin); + return all.isEmpty() ? null : all.get(0); + } + + /// EVERY such name, for the same reason the import form collects them all: + /// a file may declare `typealias First = Ios` and `typealias AppIos = Ios` + /// and use only the second. + static java.util.List kotlinTypeAliases(String source, String simple, + boolean kotlin) { + java.util.List out = new java.util.ArrayList(); if (!kotlin) { - return null; + return out; } String qualified = "com.codename1.annotations.buildhints." + simple; boolean imported = importsAnnotation(source, simple, kotlin); @@ -2771,7 +2781,7 @@ static String kotlinTypeAlias(String source, String simple, boolean kotlin) { if (eq >= 0 && source.charAt(eq) == '=') { String target = qualifiedNameAt(source, eq + 1, kotlin); if (target.equals(qualified) || (imported && target.equals(simple))) { - return name; + out.add(name); } } } @@ -2779,7 +2789,7 @@ static String kotlinTypeAlias(String source, String simple, boolean kotlin) { } at = nextMarker(source, "typealias", after, kotlin); } - return null; + return out; } /// The name a Kotlin `import ... as Alias` gives an annotation, or null. @@ -2790,13 +2800,24 @@ static String kotlinTypeAlias(String source, String simple, boolean kotlin) { /// line, and the next `process-annotations` fails on the duplicate the tool /// itself created. static String kotlinImportAlias(String source, String simple, boolean kotlin) { + java.util.List all = kotlinImportAliases(source, simple, kotlin); + return all.isEmpty() ? null : all.get(0); + } + + /// EVERY such name. A file may import the same annotation twice under + /// different aliases, and answering with the first left the other + /// unrecognised -- so the hint read as unowned, Settings offered Add, and + /// the next build failed on the duplicate the tool had just written. + static java.util.List kotlinImportAliases(String source, String simple, + boolean kotlin) { String needle = "com.codename1.annotations.buildhints." + simple; + java.util.List out = new java.util.ArrayList(); for (Imported imported : importsIn(source, kotlin)) { if (imported.alias != null && needle.equals(imported.name)) { - return imported.alias; + out.add(imported.alias); } } - return null; + return out; } /// Maps every `@Group(attr = ...)` on the main class to the hints it sets. @@ -2816,12 +2837,12 @@ static void collectAnnotationOwnedHints(String source, java.util.Map aliases = kotlinImportAliases(source, simple, kotlin); // A fourth: Kotlin can rename a type in the FILE, with no import // involved -- `typealias AppIos = Ios` and then `@AppIos(...)`. The // compiled annotation is still ours, so missing it left the hint // editable and Add wrote the duplicate the next build refuses. - String typeAlias = kotlinTypeAlias(source, simple, kotlin); + aliases.addAll(kotlinTypeAliases(source, simple, kotlin)); // The simple name only counts when an import makes it OUR annotation. // @Build and @Android are ordinary enough names that another library's // annotation with a matching attribute would otherwise be read as @@ -2843,8 +2864,7 @@ static void collectAnnotationOwnedHints(String source, java.util.Map= 0 && source.charAt(open) == '(') { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index b735c909eb2..aa146869d81 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -970,4 +970,29 @@ public void aKotlinTypeAliasStillOwnsTheHint() { CodenameOneSettings.collectAnnotationOwnedHints(src, owned, false); assertNull(owned.get("ios.teamId")); } + + /// A file may name the same annotation twice. Answering with the first + /// alias left the one actually used unrecognised, so the hint read as + /// unowned and Add wrote the duplicate the next build refuses. + @Test + public void everyKotlinAliasCounts() { + String twoTypeAliases = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "typealias First = Ios\n" + + "typealias AppIos = Ios\n" + + "@AppIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(twoTypeAliases, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + String twoImportAliases = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios as First\n" + + "import com.codename1.annotations.buildhints.Ios as AppIos\n" + + "@AppIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(twoImportAliases, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + } } From 481ae5b0ff94f319d9c8f0d038766d2c99c977cd Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:15:19 +0300 Subject: [PATCH 074/115] Resolve a typealias declared in another file A Kotlin typealias is a top-level declaration, not a file-scoped one, so a project may write `typealias AppIos = Ios` in one file and `@AppIos(...)` on the main class in another. Settings looked only at the main source, read the hint as unowned, and Add wrote the duplicate declaration the next build refuses. It now sweeps the project's other Kotlin sources for that form -- and only that form: an IMPORT alias applies to the file that writes it and is deliberately not collected this way. The sweep is bounded in files read and directories walked, since it runs when the tool opens a project. Also records, in the code, why the migration's declaration locator does NOT translate Java's unicode escapes: every index there is written back into the file as it is on disk, so decoding would need an offset map threaded through the import scan, the package scan, the annotation walk and the insertion. What it costs is a refusal that names what it could not do and changes nothing, on a spelling no project writes; a migration that guessed an offset wrong would corrupt the source instead. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 15 ++++ .../settings/CodenameOneSettings.java | 81 ++++++++++++++++++- .../settings/BuildHintCatalogTest.java | 32 ++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 8712b7361b7..f20be84e6e4 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1171,6 +1171,21 @@ && isModifier(head.substring(wordStart, i + 1))) { } } + /// One thing this deliberately does NOT do: translate Java's unicode + /// escapes. `public cl\\u0061ss Main` is a legal spelling of the keyword + /// (written with two backslashes here because javac would translate a real + /// one even inside this comment), and + /// the processor-side reader decodes it -- so the source lookup accepts a + /// file this locator then cannot find a declaration in, and the goal refuses + /// with "Could not find the class declaration". + /// + /// That is the outcome we want from it. Every index here is written back + /// into the file as it is on disk, so decoding would need an offset map + /// threaded through the import scan, the package scan, the annotation walk + /// and the insertion itself -- and the failure being avoided is a refusal + /// that names what it could not do and changes nothing, on a spelling no + /// project writes. A migration that guessed an offset wrong would corrupt + /// the source instead. Refusing is the safe half of the trade. static int classDeclarationIndex(String text, boolean kotlin, String simpleName) { // Top level means brace depth zero, not column zero. Anchoring the // pattern to the start of a line refused ` public class MyApp`, which diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 555dfc9fe5f..df728912a79 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2316,7 +2316,8 @@ private java.util.Map annotationOwnedHintsFromSource() { if (text == null || !declaresClass(text, main, pkg, ext.equals(".kt"))) { continue; } - collectAnnotationOwnedHints(text, out, ext.equals(".kt")); + collectAnnotationOwnedHints(text, out, ext.equals(".kt"), + ext.equals(".kt") ? otherKotlinSources(binding.projectDir(), path) : null); return out; } } @@ -2328,7 +2329,9 @@ private java.util.Map annotationOwnedHintsFromSource() { // properly before giving up. String found = findMainClassSource(binding.projectDir(), main, pkg); if (found != null) { - collectAnnotationOwnedHints(found, out, lastSourceWasKotlin); + collectAnnotationOwnedHints(found, out, lastSourceWasKotlin, + lastSourceWasKotlin + ? otherKotlinSources(binding.projectDir(), lastSourcePath) : null); return out; } // Genuinely no source. Distinct from "found and declares nothing", and @@ -2340,6 +2343,60 @@ private java.util.Map annotationOwnedHintsFromSource() { /// Set by findMainClassSource, since it decides the language by what it finds. private boolean lastSourceWasKotlin; + /// The path findMainClassSource read, so it can be left out of the sweep for + /// declarations made elsewhere. + private String lastSourcePath; + + /// The text of every OTHER Kotlin source in the project, bounded. + /// + /// Only for declarations that are not file-scoped -- a `typealias` naming one + /// of our annotations. Bounded in files read and in directories walked, + /// because this runs when the tool opens a project and a source tree is not + /// a search index; a project past the bound simply keeps the previous + /// behaviour for an alias declared in a file nobody reached. + private java.util.List otherKotlinSources(String projectDir, String exclude) { + java.util.List out = new java.util.ArrayList<>(); + if (projectDir == null) { + return out; + } + java.util.List queue = new java.util.ArrayList<>(); + queue.add(projectDir); + for (int i = 0; i < queue.size() && i < 4000 && out.size() < 200; i++) { + String dir = queue.get(i); + String[] children; + try { + children = FileSystemStorage.getInstance().listFiles(ProjectIO.fsUrl(dir)); + } catch (Exception ex) { + continue; + } + if (children == null) { + continue; + } + for (String child : children) { + String name = child.endsWith("/") ? child.substring(0, child.length() - 1) : child; + String path = dir + "/" + name; + if (FileSystemStorage.getInstance().isDirectory(ProjectIO.fsUrl(path))) { + // Same rule as the main-class search: an output directory + // holds copies, unless `build` is a package name under src/. + boolean output = ("target".equals(name) || "build".equals(name)) + && !insideSourceTree(dir); + if (!output && !name.startsWith(".")) { + queue.add(path); + } + continue; + } + if (!name.endsWith(".kt") || path.equals(exclude) || out.size() >= 200) { + continue; + } + String text = readIfPresent(path); + if (text != null) { + out.add(text); + } + } + } + return out; + } + /// The text of the file declaring `main` in `pkg`, found by searching, or null. /// /// Walks the project for a `.java` or `.kt` file that declares the class, so @@ -2426,6 +2483,7 @@ private String firstDeclaring(java.util.List paths, String main, String continue; } lastSourceWasKotlin = path.endsWith(".kt"); + lastSourcePath = path; return text; } return null; @@ -2828,6 +2886,20 @@ static void collectAnnotationOwnedHints(String source, java.util.Map out, boolean kotlin) { + collectAnnotationOwnedHints(source, out, kotlin, null); + } + + /// As above, also resolving `typealias` declarations made in OTHER files. + /// + /// A typealias is a top-level declaration, not a file-scoped one: a project + /// may declare `typealias AppIos = Ios` in one file and write `@AppIos(...)` + /// on the main class in another. Looking only at the main source read the + /// hint as unowned, so Add wrote the duplicate declaration the next build + /// refuses. An import alias is NOT collected this way -- that one applies + /// only to the file that writes it. + static void collectAnnotationOwnedHints(String source, java.util.Map out, + boolean kotlin, + java.util.List otherSources) { for (com.codename1.build.shared.BuildHints.Hint h : com.codename1.build.shared.BuildHints.entries()) { if (!h.isAnnotated()) { continue; @@ -2843,6 +2915,11 @@ static void collectAnnotationOwnedHints(String source, java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, true, + java.util.Collections.singletonList(sibling)); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // Without the sibling there is nothing to resolve the name to. + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, true, null); + assertNull(owned.get("ios.teamId")); + + // An IMPORT alias is file-scoped, so another file's does not apply. + String importAliasElsewhere = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios as AppIos\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, true, + java.util.Collections.singletonList(importAliasElsewhere)); + assertNull(owned.get("ios.teamId")); + } } From a8264aab742de21929885d77c85ca2b6838a9b4c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:25:13 +0300 Subject: [PATCH 075/115] Let a name have a character outside ASCII MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both languages allow a non-ASCII identifier -- `package com.应用` is valid Java and Kotlin -- and the Settings tool's hand-rolled name predicate stopped at the first such character. It read a short name, so the real main source was rejected, nothing knew which hints an annotation already owns, and the tool could offer one that is owned. Everything outside ASCII that is not whitespace counts, because Character.isJavaIdentifierPart is outside the Codename One API subset this class compiles against. That is wider than the language rule, but only by characters that cannot legally sit next to an identifier in source the compiler has already accepted -- and the alternative, rejecting all of them, is wrong for every name that has one. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 18 ++++++++++++++++-- .../settings/BuildHintCatalogTest.java | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index df728912a79..7b036e6e3c0 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -3022,8 +3022,22 @@ private static boolean declaresAttribute(String args, String attr, boolean kotli /// Hand-rolled because Character.isJavaIdentifierPart is outside the /// Codename One API subset, and this class is compiled as app code. private static boolean continuesAName(char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') || c == '_' || c == '$'; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') || c == '_' || c == '$') { + return true; + } + // Both languages allow a non-ASCII identifier -- `package com.应用` is + // valid Java and Kotlin -- and stopping at the first such character read + // a short name, so the real main source was rejected and Settings could + // offer a hint an annotation already owns. + // + // Everything outside ASCII that is not whitespace counts, since + // Character.isJavaIdentifierPart is outside the Codename One API subset + // this class is compiled against. That is wider than the language rule, + // but only by characters that cannot legally sit next to an identifier + // in source the compiler has already accepted -- and the alternative, + // rejecting all of them, is wrong for every name that has one. + return c >= 0x80 && !Character.isWhitespace(c); } /// The index of the next character that is neither whitespace nor part of a diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 681ae0a7862..0b72daec962 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1027,4 +1027,22 @@ public void aTypeAliasFromAnotherFileStillOwnsTheHint() { java.util.Collections.singletonList(importAliasElsewhere)); assertNull(owned.get("ios.teamId")); } + + /// Both languages allow a non-ASCII identifier. Stopping at the first such + /// character read a short name, so the real main source was rejected and + /// Settings could offer a hint an annotation already owns. + @Test + public void aNonAsciiNameIsStillAName() { + assertTrue(CodenameOneSettings.declaresClass( + "package com.\u5e94\u7528;\npublic class MyApp {}\n", "MyApp", "com.\u5e94\u7528")); + assertFalse(CodenameOneSettings.declaresClass( + "package com.\u5e94\u7528;\npublic class MyApp {}\n", "MyApp", "com")); + // The class name too. + assertTrue(CodenameOneSettings.declaresClass( + "package com.example;\npublic class \u5e94\u7528 {}\n", "\u5e94\u7528", + "com.example")); + // An ASCII name is unchanged, and a separator still separates. + assertTrue(CodenameOneSettings.declaresClass( + "package com.example;\npublic class MyApp {}\n", "MyApp", "com.example")); + } } From 91220d1fbe0dd07847d262ab7f97133c6f40496f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:34:38 +0300 Subject: [PATCH 076/115] Translate Java's unicode escapes in the Settings reader too The processor-side reader decodes them; this one still read the raw spelling, so `package com.example;` written with an escaped character recorded a short name, the real main source was rejected, nothing knew which hints an annotation already owns, and Add could write the duplicate declaration the next build refuses. Safe to decode here, unlike in the migration goal: this tool never writes a source file back -- it edits codenameone_settings.properties and the POM -- so nothing depends on an offset into the text as it is on disk. The hex reader is hand-rolled for the same reason the name predicate is, since this class compiles against the Codename One API subset. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 88 +++++++++++++++++++ .../settings/BuildHintCatalogTest.java | 21 +++++ 2 files changed, 109 insertions(+) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 7b036e6e3c0..e9c19096142 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2307,6 +2307,9 @@ private java.util.Map annotationOwnedHintsFromSource() { for (String root : new String[]{"/src/main/java/", "/src/main/kotlin/", "/src/"}) { String path = binding.projectDir() + root + rel + ext; String text = readIfPresent(path); + if (text != null && ext.equals(".java")) { + text = decodeUnicodeEscapes(text); + } // The conventional path has to DECLARE the class, not merely // exist. Moving a Kotlin main class into a differently named file // can leave the old Main.kt behind holding something else, and @@ -2479,6 +2482,9 @@ private String firstDeclaring(java.util.List paths, String main, String return null; } String text = readIfPresent(path); + if (text != null && !path.endsWith(".kt")) { + text = decodeUnicodeEscapes(text); + } if (text == null || !declaresClass(text, main, pkg, path.endsWith(".kt"))) { continue; } @@ -3021,6 +3027,88 @@ private static boolean declaresAttribute(String args, String attr, boolean kotli /// /// Hand-rolled because Character.isJavaIdentifierPart is outside the /// Codename One API subset, and this class is compiled as app code. + /// Java's unicode escapes, applied. + /// + /// javac processes `\\uXXXX` in the LEXICAL TRANSLATION step, before it + /// tokenizes anything, so `package com.ex\\u0061mple;` really declares + /// com.example and an escape works inside an identifier. Reading the text + /// literally recorded `com.ex`, so the real main source was rejected, + /// nothing knew which hints an annotation already owns, and Add could write + /// the duplicate declaration the next build refuses. + /// + /// A backslash only opens an escape when an EVEN number of backslashes + /// precedes it, which is what keeps a string literal spelling one. Kotlin + /// has no such step, so this is applied to Java only. + /// + /// Safe here because this tool never writes a source file back -- it edits + /// codenameone_settings.properties and the POM -- so nothing depends on an + /// offset into the text as it is on disk. + static String decodeUnicodeEscapes(String text) { + if (text == null || text.indexOf('\\') < 0) { + return text; + } + StringBuilder out = new StringBuilder(text.length()); + int i = 0; + while (i < text.length()) { + char c = text.charAt(i); + if (c != '\\') { + out.append(c); + i++; + continue; + } + int j = i; + while (j < text.length() && text.charAt(j) == '\\') { + j++; + } + int run = j - i; + for (int pair = 0; pair < run / 2; pair++) { + out.append('\\').append('\\'); + } + if (run % 2 == 0) { + i = j; + continue; + } + int u = j; + while (u < text.length() && text.charAt(u) == 'u') { + u++; + } + int value = u > j ? hexQuad(text, u) : -1; + if (value < 0) { + out.append('\\'); + i = j; + continue; + } + out.append((char) value); + i = u + 4; + } + return out.toString(); + } + + /// The four hex digits at `from`, or -1. Hand-rolled for the same reason + /// [#continuesAName] is: this class compiles against the Codename One API + /// subset. + private static int hexQuad(String text, int from) { + if (from + 4 > text.length()) { + return -1; + } + int value = 0; + for (int i = from; i < from + 4; i++) { + char c = text.charAt(i); + int digit; + if (c >= '0' && c <= '9') { + digit = c - '0'; + } else if (c >= 'a' && c <= 'f') { + digit = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + digit = c - 'A' + 10; + } else { + return -1; + } + value = value * 16 + digit; + } + return value; + } + private static boolean continuesAName(char c) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$') { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 0b72daec962..e9229f85500 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1045,4 +1045,25 @@ public void aNonAsciiNameIsStillAName() { assertTrue(CodenameOneSettings.declaresClass( "package com.example;\npublic class MyApp {}\n", "MyApp", "com.example")); } + + /// javac translates a unicode escape before it tokenizes anything, so + /// `package com.ex` + an escaped `a` + `mple;` really declares com.example. + /// Reading the text literally recorded `com.ex`, so the real main source was + /// rejected and Settings could offer a hint an annotation already owns. + @Test + public void javaUnicodeEscapesAreTranslatedBeforeTheSourceIsRead() { + String escaped = "package com.ex" + "\\u0061" + "mple;\npublic class MyApp {}\n"; + assertTrue(CodenameOneSettings.declaresClass( + CodenameOneSettings.decodeUnicodeEscapes(escaped), "MyApp", "com.example")); + + // A doubled backslash is not an escape, which is what keeps a string + // literal spelling one. + String literal = "String s = \"" + "\\\\u0041" + "\";"; + assertEquals(literal, CodenameOneSettings.decodeUnicodeEscapes(literal)); + + // Any number of u's is one escape, and a malformed one is left alone. + assertEquals("A", CodenameOneSettings.decodeUnicodeEscapes("\\uuu0041")); + assertEquals("\\uZZZZ", CodenameOneSettings.decodeUnicodeEscapes("\\uZZZZ")); + assertEquals("\\n", CodenameOneSettings.decodeUnicodeEscapes("\\n")); + } } From eb97bb3799a2ff3974b62c6da3eb14e20372b27c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:45:17 +0300 Subject: [PATCH 077/115] Leave the line endings the file came with removeMigratedLines promises to delete the migrated declarations and leave every other line byte for byte as it was, and readLine() broke that promise quietly: it discards each terminator, so appending a newline to every retained line rewrote a CRLF checkout end to end. A goal that should have touched three lines produced a whole-file diff -- damage this repository has taken before, where it also cost the blame on every line it rewrote. Each physical line now carries its own terminator through the filter, so CRLF, LF and a lone CR all survive, a mixed file keeps each line as it found it, and a file that does not end in a newline does not acquire one. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 84 +++++++++++++++---- .../MigrateBuildHintsPropertyParsingTest.java | 69 +++++++++++++++ 2 files changed, 137 insertions(+), 16 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index f20be84e6e4..1ef4fe97b5c 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -37,6 +37,7 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import java.io.BufferedReader; +import java.io.Reader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -1338,18 +1339,13 @@ private static boolean isModifier(String word) { * {@code codename1.displayName}, say -- into mojibake, even though it has * nothing to do with the hint being migrated.

*/ - private void removeMigratedLines(File settingsFile, List keys) throws IOException { - List lines = new ArrayList(); - BufferedReader r = new BufferedReader( - new InputStreamReader(new FileInputStream(settingsFile), PROPERTIES_ENCODING)); - try { - String line; - while ((line = r.readLine()) != null) { - lines.add(line); - } - } finally { - r.close(); - } + static void removeMigratedLines(File settingsFile, List keys) throws IOException { + // Each entry keeps its own terminator. readLine() discards it, and + // appending '\n' to every retained line rewrote a CRLF checkout end to + // end -- a whole-file diff from a goal that promises to delete some + // lines and touch nothing else, and one this repository has been bitten + // by before. + List lines = physicalLines(readAll(settingsFile)); Map wanted = new LinkedHashMap(); for (String k : keys) { @@ -1361,10 +1357,10 @@ private void removeMigratedLines(File settingsFile, List keys) throws IO // Gather the whole logical line: continuations belong to the same // declaration and have to go with it. int last = i; - StringBuilder logical = new StringBuilder(lines.get(i)); - while (continues(lines.get(last)) && last + 1 < lines.size()) { + StringBuilder logical = new StringBuilder(withoutTerminator(lines.get(i))); + while (continues(withoutTerminator(lines.get(last))) && last + 1 < lines.size()) { last++; - logical.append(lines.get(last).replaceFirst("^\\s+", "")); + logical.append(withoutTerminator(lines.get(last)).replaceFirst("^\\s+", "")); } String key = propertyKeyOf(logical.toString()); if (key != null && wanted.containsKey(key)) { @@ -1372,13 +1368,69 @@ private void removeMigratedLines(File settingsFile, List keys) throws IO continue; } for (int j = i; j <= last; j++) { - out.append(lines.get(j)).append('\n'); + out.append(lines.get(j)); } i = last; } writeProperties(settingsFile, out.toString()); } + /** The whole file, as the encoding {@code Properties.load} would use. */ + private static String readAll(File f) throws IOException { + Reader r = new InputStreamReader(new FileInputStream(f), PROPERTIES_ENCODING); + try { + StringBuilder sb = new StringBuilder(); + char[] buf = new char[8192]; + for (int n = r.read(buf); n > 0; n = r.read(buf)) { + sb.append(buf, 0, n); + } + return sb.toString(); + } finally { + r.close(); + } + } + + /** + * The physical lines of {@code text}, each WITH the terminator it ended on. + * + *

All three of CRLF, LF and CR end a line for {@code Properties.load}, and + * a file need not end with one at all -- so the terminators travel with their + * lines rather than being normalised away and guessed at on the way out.

+ */ + private static List physicalLines(String text) { + List out = new ArrayList(); + int start = 0; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c != '\n' && c != '\r') { + continue; + } + int end = i + 1; + if (c == '\r' && end < text.length() && text.charAt(end) == '\n') { + end++; + } + out.add(text.substring(start, end)); + start = end; + i = end - 1; + } + if (start < text.length()) { + out.add(text.substring(start)); + } + return out; + } + + /** That line without its terminator, which is what the parsing reads. */ + private static String withoutTerminator(String line) { + int end = line.length(); + if (end > 0 && line.charAt(end - 1) == '\n') { + end--; + } + if (end > 0 && line.charAt(end - 1) == '\r') { + end--; + } + return line.substring(0, end); + } + /** Whether a physical line ends in an odd number of backslashes. */ private static boolean continues(String line) { int backslashes = 0; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index cc63d9a5f13..4850cf0abc8 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -24,6 +24,8 @@ import org.junit.Test; +import java.io.File; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -531,4 +533,71 @@ public void theAnchorClearsAPackageNameThatSpansLines() { assertEquals(head.indexOf("class X"), MigrateBuildHintsMojo.endOfPackageDeclaration(code, pkg)); } + + /// The goal promises to delete the migrated declarations and leave every + /// other line byte for byte as it was. Reading with readLine() discarded + /// each terminator, and appending a newline to every retained line rewrote a + /// CRLF checkout end to end -- a whole-file diff from a goal that should + /// have touched three lines. + @Test + public void removingLinesKeepsTheFilesOwnLineEndings() throws Exception { + File f = File.createTempFile("cn1-settings", ".properties"); + f.deleteOnExit(); + String before = "# a comment\r\n" + + "codename1.displayName=Demo\r\n" + + "codename1.arg.ios.pods=Alamofire\r\n" + + "codename1.arg.android.min_sdk_version=24\r\n"; + write(f, before); + + MigrateBuildHintsMojo.removeMigratedLines(f, + java.util.Arrays.asList("codename1.arg.ios.pods")); + + assertEquals("# a comment\r\n" + + "codename1.displayName=Demo\r\n" + + "codename1.arg.android.min_sdk_version=24\r\n", + read(f)); + } + + /// A file that does not end in a newline must not acquire one, and a mixed + /// file keeps each line as it found it. + @Test + public void removingLinesInventsNoTerminator() throws Exception { + File f = File.createTempFile("cn1-settings", ".properties"); + f.deleteOnExit(); + write(f, "codename1.arg.ios.pods=Alamofire\n" + + "codename1.displayName=Demo\r\n" + + "codename1.arg.ios.teamId=ABCDE12345"); + MigrateBuildHintsMojo.removeMigratedLines(f, + java.util.Arrays.asList("codename1.arg.ios.pods")); + assertEquals("codename1.displayName=Demo\r\n" + + "codename1.arg.ios.teamId=ABCDE12345", read(f)); + } + + /// A continuation belongs to its declaration, terminators included. + @Test + public void removingAContinuedDeclarationTakesEveryLineOfIt() throws Exception { + File f = File.createTempFile("cn1-settings", ".properties"); + f.deleteOnExit(); + write(f, "codename1.arg.ios.pods=Alamofire,\\\r\n" + + " SwiftyJSON\r\n" + + "codename1.displayName=Demo\r\n"); + MigrateBuildHintsMojo.removeMigratedLines(f, + java.util.Arrays.asList("codename1.arg.ios.pods")); + assertEquals("codename1.displayName=Demo\r\n", read(f)); + } + + private static void write(File f, String text) throws Exception { + java.io.Writer w = new java.io.OutputStreamWriter( + new java.io.FileOutputStream(f), "ISO-8859-1"); + try { + w.write(text); + } finally { + w.close(); + } + } + + private static String read(File f) throws Exception { + byte[] all = java.nio.file.Files.readAllBytes(f.toPath()); + return new String(all, "ISO-8859-1"); + } } From e40398107de8ef145fe56db613c045a0bd0c1276 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:58:04 +0300 Subject: [PATCH 078/115] Anchor the import above every top-level declaration The default-package anchor walked backwards from the MAIN class over its annotations and modifiers, which answers the wrong question. An import goes above every top-level declaration, so a `fun helper() {}` written before the main class left the import below it -- illegal in both languages, so verification rolled back a valid migration. The anchor is now the first declaration in the file, whatever kind it is. That subsumes the backward walk rather than extending it: with nothing above the anchor there is nothing to back over, so the annotation runs, interleaved modifiers and escaped annotation names it used to handle need no handling at all. Those cases keep their tests, against the new answer. Kotlin's FILE annotations are the one exception, and the grammar is why: they sit above the package header and the imports both, so the leading `@file:` run is stepped over rather than displaced. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 163 +++++++++--------- .../MigrateBuildHintsPropertyParsingTest.java | 95 +++++----- 2 files changed, 129 insertions(+), 129 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 1ef4fe97b5c..d3dc9232605 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -894,7 +894,7 @@ private void insertAnnotations(File source, String annotations, String simpleNam // rolled back a migration that was otherwise correct. int pkg = livePackageIndex(blankedHead); int anchor = pkg >= 0 ? endOfPackageDeclaration(blankedHead, pkg) - : startOfLeadingAnnotations(head, kotlin); + : startOfFirstDeclaration(head, kotlin); head = head.substring(0, anchor) + (pkg >= 0 ? "\n" : "") + importLine + "\n" + (pkg >= 0 ? "" : "\n") + head.substring(anchor); } @@ -1074,102 +1074,95 @@ static int endOfPackageDeclaration(String code, int pkgAt) { return endOfDeclarationLine(code, i); } - /// The index in `head` where the run of annotations immediately preceding the - /// declaration begins, or `head.length()` when there is none. + /// The offset in `original` where an import may be inserted: before the + /// FIRST top-level declaration, whatever it is. /// - /// Walks back over whitespace and complete `@Name(...)` forms, matching - /// parentheses so a multi-line annotation does not stop the walk early. + /// Only for a file with no package declaration and no existing import -- + /// otherwise the anchor is the end of one of those. This replaced a backward + /// walk from the main class over its annotations and modifiers, which + /// answered the wrong question: an import goes above EVERY top-level + /// declaration, so a `fun helper() {}` written before the main class left + /// the import below it and the verification build rejected the file. There + /// is nothing to back over once the anchor is the first declaration in the + /// file, which is also why the annotation and modifier cases it used to + /// handle now need no handling. /// - /// Over blanked code, because an argument may contain a parenthesis inside a - /// string -- `@SuppressWarnings("a(b")` -- and counting that one leaves the - /// walk stranded in the middle of a literal. Positions are preserved by the - /// blanking, so the index returned indexes the original text. - static int startOfLeadingAnnotations(String original) { - return startOfLeadingAnnotations(original, false); + /// Kotlin's FILE annotations are the exception, and the only one: the + /// grammar puts them above the package header and the imports both, so the + /// leading `@file:` run is stepped over rather than displaced. + static int startOfFirstDeclaration(String original) { + return startOfFirstDeclaration(original, false); } - static int startOfLeadingAnnotations(String original, boolean kotlin) { - String head = com.codename1.maven.processors.BuildHintAnnotationProcessor + static int startOfFirstDeclaration(String original, boolean kotlin) { + String code = com.codename1.maven.processors.BuildHintAnnotationProcessor .blankNonCode(original, kotlin); - int at = head.length(); + int i = 0; while (true) { - int i = at - 1; - while (i >= 0 && Character.isWhitespace(head.charAt(i))) { - i--; + while (i < code.length() && Character.isWhitespace(code.charAt(i))) { + i++; } - if (i < 0) { - return at; - } - // A MODIFIER may sit between the annotations and the keyword, or - // before them: `public @Deprecated final class Main` is legal, and - // the modifier walk from the keyword stops at the annotation. Backing - // up only over the annotation run left `public` above the insertion - // point, so the import went between it and the rest of its own - // declaration and verification rolled the migration back. - int wordStart = i + 1; - while (wordStart > 0 && (Character.isJavaIdentifierPart(head.charAt(wordStart - 1)) - || head.charAt(wordStart - 1) == '-')) { - wordStart--; - } - if (wordStart <= i && (wordStart == 0 || head.charAt(wordStart - 1) != '@') - && isModifier(head.substring(wordStart, i + 1))) { - at = wordStart; - continue; + if (i >= code.length()) { + return code.length(); } - if (head.charAt(i) == ')') { - int depth = 0; - while (i >= 0) { - char c = head.charAt(i); - if (c == ')') { - depth++; - } else if (c == '(') { - depth--; - if (depth == 0) { - i--; - break; - } - } - i--; - } - if (depth != 0) { - return at; - } + if (!kotlin || !fileAnnotationAt(code, i)) { + return i; } - // The annotation's name, then its @. A Kotlin component may be - // ESCAPED, and a backtick is not an identifier character -- so the - // scan stopped on it, read no name, and left `@`when`` out of the - // leading run. The generated import then went between the - // annotation and the class, where Kotlin does not allow one, and - // verification rolled back a valid migration. - boolean readAName = false; - while (i >= 0) { - if (head.charAt(i) == '`') { - int open = head.lastIndexOf('`', i - 1); - if (open < 0) { - return at; - } - i = open - 1; - readAName = true; - } else if (Character.isJavaIdentifierPart(head.charAt(i))) { - while (i >= 0 && Character.isJavaIdentifierPart(head.charAt(i))) { - i--; - } - readAName = true; - } else { - break; - } - // A qualified annotation name continues through its dots. - if (i >= 0 && head.charAt(i) == '.') { - i--; - continue; - } - break; + int after = endOfAnnotation(code, i); + if (after <= i) { + return i; } - if (i < 0 || head.charAt(i) != '@' || !readAName) { - return at; + i = after; + } + } + + /// Whether `@file:` starts at `at`. Kotlin allows space around the colon. + private static boolean fileAnnotationAt(String code, int at) { + if (at >= code.length() || code.charAt(at) != '@') { + return false; + } + int i = at + 1; + while (i < code.length() && Character.isWhitespace(code.charAt(i))) { + i++; + } + if (!code.startsWith("file", i)) { + return false; + } + i += 4; + while (i < code.length() && Character.isWhitespace(code.charAt(i))) { + i++; + } + return i < code.length() && code.charAt(i) == ':'; + } + + /// The offset just past the annotation starting at `at`, or `at` when it + /// cannot be read. Blanked code, so a parenthesis inside a literal is gone. + private static int endOfAnnotation(String code, int at) { + int i = at + 1; + while (i < code.length() && (Character.isWhitespace(code.charAt(i)) + || code.charAt(i) == ':' || code.charAt(i) == '.' + || Character.isJavaIdentifierPart(code.charAt(i)))) { + i++; + } + int probe = i; + while (probe < code.length() && Character.isWhitespace(code.charAt(probe))) { + probe++; + } + if (probe >= code.length() || code.charAt(probe) != '(') { + return i; + } + int depth = 0; + for (int j = probe; j < code.length(); j++) { + if (code.charAt(j) == '(') { + depth++; + } else if (code.charAt(j) == ')') { + depth--; + if (depth == 0) { + return j + 1; + } } - at = i; } + return at; } /// One thing this deliberately does NOT do: translate Java's unicode diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 4850cf0abc8..582375ba1c3 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -233,6 +233,20 @@ public void aNestedTypeIsNotTheInsertionPoint() { MigrateBuildHintsMojo.classDeclarationIndex(src, false, "MyApp")); } + /// An import goes above EVERY top-level declaration, so the anchor for a + /// file with no package and no import is the first declaration in it -- + /// whatever kind it is. Anchoring on the MAIN class instead put the import + /// below a `fun helper()` written before it, which neither language allows, + /// and verification rolled back a valid migration. + @Test + public void theAnchorIsTheFirstDeclarationNotTheMainClass() { + String kt = "fun helper() {}\n\nclass MyApp\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration(kt, true)); + + String java = "class Helper {}\n\n@Deprecated\nclass MyApp {}\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration(java)); + } + /// A default-package class that already carries an annotation: the import /// must go ABOVE it. Anchoring on the declaration put the import between the /// annotation and the class, which is not valid in either language. @@ -240,63 +254,56 @@ public void aNestedTypeIsNotTheInsertionPoint() { public void theImportGoesAboveAnExistingAnnotation() { String head = "/* c */\n@SuppressWarnings(\"unchecked\")\n"; assertEquals(head.indexOf("@SuppressWarnings"), - MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); + MigrateBuildHintsMojo.startOfFirstDeclaration(head)); } - /// Modifiers and annotations may INTERLEAVE: `public @Deprecated final - /// class Main` is legal. The modifier walk from the keyword stops at the - /// annotation, so backing up only over the annotation run left `public` - /// above the insertion point -- the import went between it and the rest of - /// its own declaration, and verification rolled the migration back. + /// A parenthesis inside an annotation argument must not stop the walk. @Test - public void theAnchorClearsModifiersInterleavedWithAnnotations() { - String head = "public @Deprecated "; - assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); - - // A modifier alone, with no annotation at all. - assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations("public ")); - - // A word that is NOT a modifier still ends the walk, or the anchor would - // climb out of the declaration and into whatever precedes it. - String other = "int x = 1;\n@Deprecated\n"; - assertEquals(other.indexOf("@Deprecated"), - MigrateBuildHintsMojo.startOfLeadingAnnotations(other)); + public void anArgumentContainingAParenthesisDoesNotStopTheWalk() { + String head = "@Deprecated\n@SuppressWarnings(\"a(b\")\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration(head)); } - /// A Kotlin annotation may have an ESCAPED name, and a backtick is not an - /// identifier character -- so the backward walk stopped on it, read no name, - /// and left `@`when`` out of the leading run. The generated import then went - /// between the annotation and the class, where Kotlin does not allow one. + /// With no declaration there is nothing to go above. @Test - public void anEscapedAnnotationNameIsPartOfTheLeadingRun() { - String head = "@`when`\n"; - assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); - - String withArgs = "@`when`(\"x\")\n@Deprecated\n"; - assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(withArgs)); - - // A qualified name may escape a component too. - String qualified = "@com.`when`.Ann\n"; - assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(qualified)); + public void withNoDeclarationTheAnchorIsTheEnd() { + String head = "/* copyright */\n\n"; + assertEquals(head.length(), MigrateBuildHintsMojo.startOfFirstDeclaration(head)); + } - // A lone backtick run that is not an annotation is still not one. - String notAnnotation = "`when`\n"; - assertEquals(notAnnotation.length(), - MigrateBuildHintsMojo.startOfLeadingAnnotations(notAnnotation)); + /// Modifiers and annotations may interleave: `public @Deprecated final + /// class Main` is legal, and the whole run is below the import. + @Test + public void theAnchorClearsModifiersInterleavedWithAnnotations() { + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration("public @Deprecated ")); + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration("public ")); } - /// A parenthesis inside an annotation argument must not stop the walk. + /// A Kotlin annotation may have an ESCAPED name, which is code the scan has + /// to read rather than stop on. @Test - public void anArgumentContainingAParenthesisDoesNotStopTheWalk() { - String head = "@Deprecated\n@SuppressWarnings(\"a(b\")\n"; - assertEquals(0, MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); + public void anEscapedAnnotationNameIsPartOfTheLeadingRun() { + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration("@`when`\n", true)); + assertEquals(0, + MigrateBuildHintsMojo.startOfFirstDeclaration("@`when`(\"x\")\n@Deprecated\n", true)); + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration("@com.`when`.Ann\n", true)); } - /// With no annotation there is nothing to move above. + /// Kotlin's FILE annotations sit above the package header and the imports + /// both, so the import goes BELOW them -- the one thing the anchor steps + /// over rather than displaces. @Test - public void withNoAnnotationTheAnchorIsTheEnd() { - String head = "/* copyright */\n\n"; - assertEquals(head.length(), MigrateBuildHintsMojo.startOfLeadingAnnotations(head)); + public void aKotlinFileAnnotationStaysAboveTheImport() { + String kt = "@file:Suppress(\"unchecked\")\n@file:JvmName(\"X\")\n\nclass MyApp\n"; + assertEquals(kt.indexOf("class MyApp"), + MigrateBuildHintsMojo.startOfFirstDeclaration(kt, true)); + + // An ordinary annotation is not a file annotation. + String ordinary = "@Suppress(\"unchecked\")\nclass MyApp\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration(ordinary, true)); + + // Java has no such form, so `@file` there is an ordinary annotation. + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration(kt, false)); } /// The migration's source lookup must require a TOP-LEVEL declaration. A From ccac36142ac84106ba0b90c8363acd01db666db0 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 03:05:01 +0300 Subject: [PATCH 079/115] Consume a bracketed Kotlin file-annotation list One use-site target may carry several annotations -- `@file:[JvmName("X") Suppress("unchecked")]` -- and the anchor read only the parenthesised form, so it stopped at the `[`, took the bracket for the first declaration, and put the import between `@file:` and its own list. Both delimiters go through one balanced scan now. A bracketed list with NO target is not a file annotation, so the import still goes above it like any other annotation. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 27 +++++++++++++++---- .../MigrateBuildHintsPropertyParsingTest.java | 10 +++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index d3dc9232605..29ec20518fd 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1148,21 +1148,38 @@ private static int endOfAnnotation(String code, int at) { while (probe < code.length() && Character.isWhitespace(code.charAt(probe))) { probe++; } - if (probe >= code.length() || code.charAt(probe) != '(') { + if (probe >= code.length()) { return i; } + char c = code.charAt(probe); + // Arguments, or a BRACKETED list: Kotlin lets one use-site target carry + // several annotations -- `@file:[JvmName("X") Suppress("unchecked")]` -- + // and stopping at the `[` read the bracket as the first declaration, so + // the import went between `@file:` and its own list. + if (c == '(') { + return balancedEnd(code, probe, '(', ')', at); + } + if (c == '[') { + return balancedEnd(code, probe, '[', ']', at); + } + return i; + } + + /// The offset just past the run opened at `from`, or `fallback` when it never + /// closes. Blanked code, so a delimiter inside a literal is already gone. + private static int balancedEnd(String code, int from, char open, char close, int fallback) { int depth = 0; - for (int j = probe; j < code.length(); j++) { - if (code.charAt(j) == '(') { + for (int j = from; j < code.length(); j++) { + if (code.charAt(j) == open) { depth++; - } else if (code.charAt(j) == ')') { + } else if (code.charAt(j) == close) { depth--; if (depth == 0) { return j + 1; } } } - return at; + return fallback; } /// One thing this deliberately does NOT do: translate Java's unicode diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 582375ba1c3..bb156a57195 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -298,6 +298,16 @@ public void aKotlinFileAnnotationStaysAboveTheImport() { assertEquals(kt.indexOf("class MyApp"), MigrateBuildHintsMojo.startOfFirstDeclaration(kt, true)); + // One target may carry a BRACKETED list of annotations. + String grouped = "@file:[JvmName(\"X\") Suppress(\"unchecked\")]\n\nclass MyApp\n"; + assertEquals(grouped.indexOf("class MyApp"), + MigrateBuildHintsMojo.startOfFirstDeclaration(grouped, true)); + + // ...and a bracketed list with no target is not a FILE annotation, so + // the import goes above it like any other. + String untargeted = "@[JvmName(\"X\") Suppress(\"unchecked\")]\nclass MyApp\n"; + assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration(untargeted, true)); + // An ordinary annotation is not a file annotation. String ordinary = "@Suppress(\"unchecked\")\nclass MyApp\n"; assertEquals(0, MigrateBuildHintsMojo.startOfFirstDeclaration(ordinary, true)); From f428d3f5f012366b8c82be7371b3cee4d03eba8c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 03:16:15 +0300 Subject: [PATCH 080/115] Match a non-ASCII name the way the source spells it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The source is read byte for byte, deliberately, so a raw byte in a comment or a literal survives whatever the project's real encoding is. The names it is matched against come from a properties file and are real Unicode. For an ASCII name the two spellings are identical; for `package com.应用` in a UTF-8 file they are not, so the comparison failed and the goal refused a valid migration saying it could not find the main source. The expected name is now compared in both spellings rather than the file being reinterpreted. That assumes nothing about its encoding: an ASCII file matches either way, a UTF-8 one matches the byte spelling, and a source genuinely written in a single-byte encoding still matches the plain one. Decoding the file instead would decide its encoding for it, which is what the byte-transparent read exists to avoid. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 38 ++++++++++++++++--- .../MigrateBuildHintsPropertyParsingTest.java | 24 ++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 29ec20518fd..486e84dc3af 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -831,10 +831,37 @@ private boolean declares(File f, String pkg, String simple) { // `class Outer { class Main }` -- the annotations were then inserted on // Outer, and the verification build rejected the placement and rolled the // migration back. - return pkg.equals(com.codename1.maven.processors.BuildHintAnnotationProcessor - .declaredPackageIn(text, kotlin)) - && com.codename1.maven.processors.BuildHintAnnotationProcessor - .declaresNestedPath(text, new String[] {simple}, kotlin); + String declaredPkg = com.codename1.maven.processors.BuildHintAnnotationProcessor + .declaredPackageIn(text, kotlin); + return (pkg.equals(declaredPkg) || asWrittenInSource(pkg).equals(declaredPkg)) + && (com.codename1.maven.processors.BuildHintAnnotationProcessor + .declaresNestedPath(text, new String[] {simple}, kotlin) + || com.codename1.maven.processors.BuildHintAnnotationProcessor + .declaresNestedPath(text, new String[] {asWrittenInSource(simple)}, kotlin)); + } + + /// `name` spelled the way a UTF-8 source file reads under the + /// byte-transparent scheme, or `name` itself when it is ASCII. + /// + /// The source is read byte for byte -- see [#read(File)] -- while + /// `codename1.packageName` and `codename1.mainName` come from a properties + /// file, which is real Unicode. For an ASCII name the two are identical, but + /// `package com.应用` in a UTF-8 file reads as its individual bytes, so the + /// comparison failed and the goal refused a valid migration saying it could + /// not find the main source. + /// + /// Compared as an ALTERNATIVE rather than a replacement, so nothing is + /// assumed about the file's encoding: an ASCII file matches either way, a + /// UTF-8 one matches this spelling, and a source genuinely written in a + /// single-byte encoding still matches the plain one. Reinterpreting the file + /// instead would decide its encoding for it, which is the thing the + /// byte-transparent read exists to avoid. + static String asWrittenInSource(String name) { + try { + return new String(name.getBytes("UTF-8"), SOURCE_BYTE_TRANSPARENT_ENCODING); + } catch (java.io.UnsupportedEncodingException ex) { + return name; + } } /** @@ -1264,7 +1291,8 @@ static int classDeclarationIndex(String text, boolean kotlin, String simpleName) // the annotations have to go before those. int start = startOfModifiers(code, i); if (simpleName != null && simpleName.length() > 0 - && declared.equals(simpleName)) { + && (declared.equals(simpleName) + || declared.equals(asWrittenInSource(simpleName)))) { return start; } if (first < 0) { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index bb156a57195..8bdf9292015 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -617,4 +617,28 @@ private static String read(File f) throws Exception { byte[] all = java.nio.file.Files.readAllBytes(f.toPath()); return new String(all, "ISO-8859-1"); } + + /// The source is read byte for byte, while `codename1.packageName` and + /// `codename1.mainName` come from a properties file and are real Unicode. + /// For an ASCII name the two spellings are identical; for + /// `package com.\u5e94\u7528` in a UTF-8 file they are not, and comparing + /// only the Unicode one made the goal refuse a valid migration saying it + /// could not find the main source. + @Test + public void aNonAsciiNameIsMatchedAsTheSourceSpellsIt() throws Exception { + String pkg = "com.\u5e94\u7528"; + String asWritten = MigrateBuildHintsMojo.asWrittenInSource(pkg); + // The bytes of the UTF-8 file, read one per character. + assertEquals(new String(pkg.getBytes("UTF-8"), "ISO-8859-1"), asWritten); + assertTrue("a non-ASCII name must read differently", !asWritten.equals(pkg)); + + // An ASCII name is untouched, so nothing about the common case changes. + assertEquals("com.example", MigrateBuildHintsMojo.asWrittenInSource("com.example")); + + // And the declaration locator accepts either spelling of the class name. + String source = "package " + asWritten + ";\n\nclass " + + MigrateBuildHintsMojo.asWrittenInSource("\u5e94\u7528") + " {\n}\n"; + assertEquals(source.indexOf("class "), + MigrateBuildHintsMojo.classDeclarationIndex(source, false, "\u5e94\u7528")); + } } From e22ab605e5a3f4514caf2d601f4888c7b65b48b9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 03:27:42 +0300 Subject: [PATCH 081/115] Drop the continuation marker, and follow an alias chain A properties declaration may put its separator on the continuation line. Properties.load drops the trailing backslash, and the reconstruction here kept it -- so `codename1.arg.ios.teamId\` + ` =ABCDE` read as an escaped `=`, the key never matched the one being migrated, the declaration stayed behind, and the verification build failed on the duplicate the goal had just created. And `typealias AppIos = Ios` then `typealias CustomIos = AppIos` is legal, with `@CustomIos(...)` still compiling to our annotation. I had declined to follow that chain and was wrong: it costs a closure, not a guess. Resolved by closure rather than recursion so a cycle -- which the compiler rejects but this reader must not hang on -- simply stops adding names, and over every source at once, since a chain may cross files with the link that names the annotation in one and the link the main class writes in another. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 7 ++ .../MigrateBuildHintsPropertyParsingTest.java | 29 +++++++ .../settings/CodenameOneSettings.java | 76 ++++++++++++++++--- .../settings/BuildHintCatalogTest.java | 44 +++++++++++ 4 files changed, 146 insertions(+), 10 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 486e84dc3af..53a622ad2d2 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1397,6 +1397,13 @@ static void removeMigratedLines(File settingsFile, List keys) throws IOE int last = i; StringBuilder logical = new StringBuilder(withoutTerminator(lines.get(i))); while (continues(withoutTerminator(lines.get(last))) && last + 1 < lines.size()) { + // The continuation backslash is a MARKER, not part of the value: + // Properties.load drops it, so leaving it in made + // `codename1.arg.ios.teamId\` + ` =ABCDE` read as an escaped `=` + // and the key never matched the one being migrated. The + // declaration stayed behind, and the verification build then + // failed on the duplicate the goal had just created. + logical.setLength(logical.length() - 1); last++; logical.append(withoutTerminator(lines.get(last)).replaceFirst("^\\s+", "")); } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 8bdf9292015..e194823a895 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -641,4 +641,33 @@ public void aNonAsciiNameIsMatchedAsTheSourceSpellsIt() throws Exception { assertEquals(source.indexOf("class "), MigrateBuildHintsMojo.classDeclarationIndex(source, false, "\u5e94\u7528")); } + + /// The continuation backslash is a MARKER, not part of the value. + /// `Properties.load` drops it, so leaving it in made `key\` + ` =value` read + /// as an escaped `=`; the key never matched the one being migrated, the + /// declaration stayed behind, and the verification build failed on the + /// duplicate the goal had just created. + @Test + public void aSeparatorOnAContinuationLineStillNamesItsKey() throws Exception { + File f = File.createTempFile("cn1-settings", ".properties"); + f.deleteOnExit(); + write(f, "codename1.arg.ios.teamId\\\n" + + " =ABCDE12345\n" + + "codename1.displayName=Demo\n"); + + // Exactly what Properties.load calls the key, so the plan and the + // removal cannot disagree about it. + java.util.Properties loaded = new java.util.Properties(); + java.io.InputStream in = new java.io.FileInputStream(f); + try { + loaded.load(in); + } finally { + in.close(); + } + assertEquals("ABCDE12345", loaded.getProperty("codename1.arg.ios.teamId")); + + MigrateBuildHintsMojo.removeMigratedLines(f, + java.util.Arrays.asList("codename1.arg.ios.teamId")); + assertEquals("codename1.displayName=Demo\n", read(f)); + } } diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index e9c19096142..e4285244d2c 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2824,12 +2824,68 @@ static String kotlinTypeAlias(String source, String simple, boolean kotlin) { /// and use only the second. static java.util.List kotlinTypeAliases(String source, String simple, boolean kotlin) { + return kotlinTypeAliases(java.util.Collections.singletonList(source), simple, kotlin); + } + + /// Every name that resolves to `simple`, across all of `sources`, following + /// a CHAIN of aliases. + /// + /// `typealias AppIos = Ios` then `typealias CustomIos = AppIos` is legal, + /// and `@CustomIos(...)` still compiles to our annotation. Accepting only a + /// right-hand side that names the annotation directly left the hint reading + /// as unowned, so Add wrote the duplicate declaration the next build + /// refuses. Resolved by closure rather than by recursion so that a cycle -- + /// which the compiler rejects, but this reader must not hang on -- simply + /// stops adding names. + static java.util.List kotlinTypeAliases(java.util.List sources, String simple, + boolean kotlin) { java.util.List out = new java.util.ArrayList(); - if (!kotlin) { + if (!kotlin || sources == null) { return out; } String qualified = "com.codename1.annotations.buildhints." + simple; - boolean imported = importsAnnotation(source, simple, kotlin); + java.util.List declarations = new java.util.ArrayList(); + for (String source : sources) { + if (source == null) { + continue; + } + // The bare name counts only where an import makes it ours, and that + // import is file-scoped -- so it is decided per source, here, rather + // than once for the whole sweep. + boolean imported = importsAnnotation(source, simple, kotlin); + for (String[] declared : typeAliasDeclarations(source, kotlin)) { + if (declared[1].equals(qualified) || (imported && declared[1].equals(simple))) { + if (!out.contains(declared[0])) { + out.add(declared[0]); + } + } else { + declarations.add(declared); + } + } + } + // Each pass can only resolve one more link, so the number of passes is + // bounded by the number of declarations left over. + for (int pass = 0; pass < declarations.size(); pass++) { + boolean grew = false; + for (String[] declared : declarations) { + if (!out.contains(declared[0]) && out.contains(declared[1])) { + out.add(declared[0]); + grew = true; + } + } + if (!grew) { + break; + } + } + return out; + } + + /// Every `typealias Name = Target` in `source`, as {name, target}. + static java.util.List typeAliasDeclarations(String source, boolean kotlin) { + java.util.List out = new java.util.ArrayList(); + if (!kotlin || source == null) { + return out; + } int at = nextMarker(source, "typealias", 0, kotlin); while (at >= 0) { int after = at + "typealias".length(); @@ -2843,10 +2899,7 @@ static java.util.List kotlinTypeAliases(String source, String simple, String name = componentText(source, n, end, kotlin); int eq = nextLiveChar(source, end, kotlin); if (eq >= 0 && source.charAt(eq) == '=') { - String target = qualifiedNameAt(source, eq + 1, kotlin); - if (target.equals(qualified) || (imported && target.equals(simple))) { - out.add(name); - } + out.add(new String[] {name, qualifiedNameAt(source, eq + 1, kotlin)}); } } } @@ -2920,12 +2973,15 @@ static void collectAnnotationOwnedHints(String source, java.util.Map forAliases = new java.util.ArrayList(); + forAliases.add(source); if (otherSources != null) { - for (String other : otherSources) { - aliases.addAll(kotlinTypeAliases(other, simple, kotlin)); - } + forAliases.addAll(otherSources); } + aliases.addAll(kotlinTypeAliases(forAliases, simple, kotlin)); // The simple name only counts when an import makes it OUR annotation. // @Build and @Android are ordinary enough names that another library's // annotation with a matching attribute would otherwise be read as diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index e9229f85500..624f8bcfe71 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1066,4 +1066,48 @@ public void javaUnicodeEscapesAreTranslatedBeforeTheSourceIsRead() { assertEquals("\\uZZZZ", CodenameOneSettings.decodeUnicodeEscapes("\\uZZZZ")); assertEquals("\\n", CodenameOneSettings.decodeUnicodeEscapes("\\n")); } + + /// `typealias AppIos = Ios` then `typealias CustomIos = AppIos` is legal, + /// and `@CustomIos(...)` still compiles to our annotation. Accepting only a + /// right-hand side that names the annotation directly left the hint reading + /// as unowned, so Add wrote the duplicate the next build refuses. + @Test + public void aChainOfTypeAliasesIsFollowed() { + String main = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "typealias AppIos = Ios\n" + + "typealias CustomIos = AppIos\n" + + "@CustomIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // The chain may cross files: the link naming our annotation in one, the + // link the main class writes in another. + String usesIt = "package com.example\n" + + "@CustomIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + String declaresIt = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "typealias AppIos = Ios\n" + + "typealias CustomIos = AppIos\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(usesIt, owned, true, + java.util.Collections.singletonList(declaresIt)); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // A chain that never reaches our annotation is not ours, and a cycle + // must not hang the reader. + String theirs = "package com.example\n" + + "typealias AppIos = com.other.Ios\n" + + "typealias CustomIos = AppIos\n" + + "typealias A = B\n" + + "typealias B = A\n" + + "@CustomIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(theirs, owned, true); + assertNull(owned.get("ios.teamId")); + } } From 1bf4691ef7689abcf3ebfea534fff607d94c024f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 03:39:43 +0300 Subject: [PATCH 082/115] Read a Kotlin string template as the nesting it is Inside `${ ... }` the first quote starts a NEW literal rather than closing the outer one, so `"${"@Ios(teamId = x)"}"` ended the string early and exposed its contents as live code. In Settings that read as an annotation nobody wrote, hiding the editor for a hint nothing owns -- indistinguishable from the tool being broken. In the processor the same text read as a declaration nobody wrote. Both scanners step over a template expression whole, matching braces so a `}` inside the nested literal does not close it early, and recursing through nested strings. Raw strings carry templates too, so a `"""` inside one is a nested literal rather than the terminator. Fixed in both parsers at once: this is the shape that has come back repeatedly in this review, where one of them learns a language rule and the other does not. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 77 +++++++++++++++++++ .../BuildHintAnnotationProcessorTest.java | 28 +++++++ .../settings/CodenameOneSettings.java | 73 +++++++++++++++++- .../settings/BuildHintCatalogTest.java | 38 +++++++++ 4 files changed, 215 insertions(+), 1 deletion(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 351b89fa9f9..7101037b82a 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -1000,6 +1000,21 @@ public static String blankNonCode(String text, boolean kotlin) { out[i++] = ' '; continue; } + // A Kotlin template expression opens a fresh nesting level, + // and the first quote inside it starts a NEW literal rather + // than closing this one -- so `"${"@Ios(teamId = x)"}"` ended + // the string early and exposed its contents as code, which + // read as a declaration nobody wrote. + int template = kotlin ? endOfKotlinTemplate(out, i) : -1; + if (template > i) { + while (i < template) { + if (out[i] != '\n') { + out[i] = ' '; + } + i++; + } + continue; + } boolean closing = out[i] == '"'; if (out[i] != '\n') { out[i] = ' '; @@ -1035,9 +1050,71 @@ private static int endOfJavaTextBlock(char[] c, int i) { /// Index just past a Kotlin raw string opening at `i`. No escapes, and a run /// of quotes closes at its LAST three, so the extra ones belong to the value. + /// The offset just past a `${ ... }` template expression at `i`, or -1 when + /// one does not start there. + /// + /// Braces are matched, and a nested literal inside the expression is stepped + /// over so that a `}` inside it does not close the expression early. + private static int endOfKotlinTemplate(char[] c, int i) { + if (i + 1 >= c.length || c[i] != '$' || c[i + 1] != '{') { + return -1; + } + int depth = 0; + int j = i + 1; + while (j < c.length) { + char ch = c[j]; + if (ch == '"') { + int run = j; + while (run < c.length && c[run] == '"') { + run++; + } + j = run - j >= 3 ? endOfKotlinRawString(c, j) : endOfKotlinString(c, j); + continue; + } + if (ch == '{') { + depth++; + } else if (ch == '}') { + depth--; + if (depth == 0) { + return j + 1; + } + } + j++; + } + return -1; + } + + /// The offset just past an ordinary Kotlin string starting at `i`. + private static int endOfKotlinString(char[] c, int i) { + int j = i + 1; + while (j < c.length) { + if (c[j] == '\\') { + j += 2; + continue; + } + int template = endOfKotlinTemplate(c, j); + if (template > j) { + j = template; + continue; + } + if (c[j] == '"') { + return j + 1; + } + j++; + } + return c.length; + } + private static int endOfKotlinRawString(char[] c, int i) { int j = i + 3; while (j < c.length) { + // A template expression here too: a `"""` inside one is a nested + // literal, not this string's terminator. + int template = endOfKotlinTemplate(c, j); + if (template > j) { + j = template; + continue; + } if (c[j] != '"') { j++; continue; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index d348585f1b7..7102d7caadb 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -677,6 +677,34 @@ public void anEscapedIdentifierIsNotAPackageDeclaration() { "package com.example\n\nfun `package helper`() {}\n", true)); } + /// Inside a Kotlin template expression the first quote starts a NEW literal + /// rather than closing the outer one, so a `class` written inside one was + /// exposed as live code and read as a declaration nobody wrote. + @Test + public void aStringInsideAKotlinTemplateIsStillAString() { + String kt = "package com.example\n" + + "class Real {\n" + + " val note = \"${\"class Fake\"}\"\n" + + "}\n"; + assertFalse(BuildHintAnnotationProcessor.declaresType(kt, "Fake", true)); + assertTrue(BuildHintAnnotationProcessor.declaresType(kt, "Real", true)); + + // A brace inside the nested literal must not close the expression early, + // or the nesting scan loses its place from there on. + String braced = "package com.example\n" + + "class Real {\n" + + " val note = \"${\"} class Fake\"}\"\n" + + "}\n"; + assertFalse(BuildHintAnnotationProcessor.declaresType(braced, "Fake", true)); + + // A raw string carries templates too. + String raw = "package com.example\n" + + "class Real {\n" + + " val note = \"\"\"${\"class Fake\"}\"\"\"\n" + + "}\n"; + assertFalse(BuildHintAnnotationProcessor.declaresType(raw, "Fake", true)); + } + /// Running out of search budget is "cannot tell", not "no such source". /// Answering no dropped a live annotated class silently, with its placement /// error lost, for the sake of a bound -- which is the wrong way round: diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index e4285244d2c..4b39d1adcc7 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -3252,9 +3252,67 @@ private static int endOfJavaTextBlock(String s, int i) { /// Index just past a Kotlin raw string opening at `i`. No escapes, and a run /// of quotes closes at its last three, so the extra ones belong to the value. + /// The offset just past a `${ ... }` template expression at `i`, or -1 when + /// one does not start there. + /// + /// Braces are matched, and a nested literal inside the expression is stepped + /// over so that a `}` inside it does not close the expression early. + private static int endOfKotlinTemplate(String s, int i) { + if (i + 1 >= s.length() || s.charAt(i) != '$' || s.charAt(i + 1) != '{') { + return -1; + } + int depth = 0; + int j = i + 1; + while (j < s.length()) { + char ch = s.charAt(j); + if (ch == '"') { + j = s.startsWith("\"\"\"", j) ? endOfKotlinRawString(s, j) : endOfKotlinString(s, j); + continue; + } + if (ch == '{') { + depth++; + } else if (ch == '}') { + depth--; + if (depth == 0) { + return j + 1; + } + } + j++; + } + return -1; + } + + /// The offset just past an ordinary Kotlin string starting at `i`. + private static int endOfKotlinString(String s, int i) { + int j = i + 1; + while (j < s.length()) { + if (s.charAt(j) == '\\') { + j += 2; + continue; + } + int template = endOfKotlinTemplate(s, j); + if (template > j) { + j = template; + continue; + } + if (s.charAt(j) == '"') { + return j + 1; + } + j++; + } + return s.length(); + } + private static int endOfKotlinRawString(String s, int i) { int j = i + 3; while (j < s.length()) { + // A template expression here too: a `"""` inside one is a nested + // literal, not this string's terminator. + int template = endOfKotlinTemplate(s, j); + if (template > j) { + j = template; + continue; + } if (s.charAt(j) != '"') { j++; continue; @@ -3291,7 +3349,20 @@ private static int skipNonCode(String s, int i, boolean kotlin) { for (int j = i + 1; j < s.length(); j++) { if (s.charAt(j) == '\\') { j++; - } else if (s.charAt(j) == '"') { + continue; + } + // A Kotlin template expression opens a fresh nesting level, and + // the first quote inside it starts a NEW literal rather than + // closing this one -- so `"${"@Ios(teamId = x)"}"` ended the + // string early and exposed its contents as live code, which read + // as an annotation nobody wrote and hid the editor for a hint + // nothing owns. + int template = kotlin ? endOfKotlinTemplate(s, j) : -1; + if (template > j) { + j = template - 1; + continue; + } + if (s.charAt(j) == '"') { return j + 1; } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 624f8bcfe71..4dbfe535288 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1110,4 +1110,42 @@ public void aChainOfTypeAliasesIsFollowed() { CodenameOneSettings.collectAnnotationOwnedHints(theirs, owned, true); assertNull(owned.get("ios.teamId")); } + + /// Inside a Kotlin template expression the first quote starts a NEW literal + /// rather than closing the outer one, so `"${"@Ios(teamId = x)"}"` ended the + /// string early and exposed its contents as live code -- an annotation + /// nobody wrote, which hid the editor for a hint nothing owns. + @Test + public void aStringInsideATemplateIsStillAString() { + String src = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "class MyApp {\n" + + " val note = \"${\"@Ios(teamId = fake)\"}\"\n" + + "}\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned, true); + assertNull(owned.get("ios.teamId")); + + // A real annotation in the same file is still found. + String real = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp {\n" + + " val note = \"${\"@Ios(pods = fake)\"}\"\n" + + "}\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(real, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + assertNull(owned.get("ios.pods")); + + // A brace inside the nested literal must not close the expression early. + String braced = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "class MyApp {\n" + + " val note = \"${\"} @Ios(teamId = fake)\"}\"\n" + + "}\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(braced, owned, true); + assertNull(owned.get("ios.teamId")); + } } From 2a64e926b91ef1129a754860226c82710f9fc903 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 03:48:26 +0300 Subject: [PATCH 083/115] A template expression holds ordinary code, comments included The nesting fix taught both scanners that a quote inside `${ ... }` opens a nested string, but the expression is ordinary code and so it also holds comments and char literals -- where a quote is neither. `${ /* " */ 1 }` was read as opening a nested string, so the rest of the file was swallowed: in the processor a live declaration after it was blanked and its class dropped as an orphan, and in Settings every annotation after it disappeared. Comments nest in Kotlin, so the block form is matched by depth, and a brace inside a comment no longer closes the expression either. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 59 +++++++++++++++++++ .../BuildHintAnnotationProcessorTest.java | 24 ++++++++ .../settings/CodenameOneSettings.java | 11 ++++ .../settings/BuildHintCatalogTest.java | 13 ++++ 4 files changed, 107 insertions(+) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 7101037b82a..5cd0c2a2c12 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -1071,6 +1071,16 @@ private static int endOfKotlinTemplate(char[] c, int i) { j = run - j >= 3 ? endOfKotlinRawString(c, j) : endOfKotlinString(c, j); continue; } + // The expression is ordinary code, so it holds ordinary comments and + // char literals -- and a quote inside one of those is not a nested + // string. Reading `${ /* " */ 1 }` as if it were swallowed the rest + // of the file, so a live declaration after it was blanked and its + // class dropped as an orphan. + int nonCode = endOfKotlinNonCode(c, j); + if (nonCode > j) { + j = nonCode; + continue; + } if (ch == '{') { depth++; } else if (ch == '}') { @@ -1084,6 +1094,55 @@ private static int endOfKotlinTemplate(char[] c, int i) { return -1; } + /// The offset just past a comment or char literal at `i`, or `i` when there + /// is neither. + private static int endOfKotlinNonCode(char[] c, int i) { + if (i + 1 < c.length && c[i] == '/' && c[i + 1] == '/') { + int j = i; + while (j < c.length && c[j] != '\n') { + j++; + } + return j; + } + if (i + 1 < c.length && c[i] == '/' && c[i + 1] == '*') { + // Kotlin block comments NEST. + int depth = 0; + int j = i; + while (j < c.length) { + if (c[j] == '/' && j + 1 < c.length && c[j + 1] == '*') { + depth++; + j += 2; + continue; + } + if (c[j] == '*' && j + 1 < c.length && c[j + 1] == '/') { + depth--; + j += 2; + if (depth == 0) { + return j; + } + continue; + } + j++; + } + return c.length; + } + if (c[i] == '\'') { + int j = i + 1; + while (j < c.length) { + if (c[j] == '\\') { + j += 2; + continue; + } + if (c[j] == '\'') { + return j + 1; + } + j++; + } + return c.length; + } + return i; + } + /// The offset just past an ordinary Kotlin string starting at `i`. private static int endOfKotlinString(char[] c, int i) { int j = i + 1; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 7102d7caadb..3b2c5da1fee 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -697,6 +697,30 @@ public void aStringInsideAKotlinTemplateIsStillAString() { + "}\n"; assertFalse(BuildHintAnnotationProcessor.declaresType(braced, "Fake", true)); + // The expression is ordinary code, so it holds ordinary comments and + // char literals, and a quote inside one of those is not a nested string. + String commented = "package com.example\n" + + "class Real {\n" + + " val note = \"${ /* \\\" */ 1 }\"\n" + + "}\n" + + "class After\n"; + assertTrue(BuildHintAnnotationProcessor.declaresType(commented, "After", true)); + + String charLiteral = "package com.example\n" + + "class Real {\n" + + " val note = \"${ if (c == '\\\"') 1 else 2 }\"\n" + + "}\n" + + "class After\n"; + assertTrue(BuildHintAnnotationProcessor.declaresType(charLiteral, "After", true)); + + // A brace inside a comment there must not close the expression either. + String bracedComment = "package com.example\n" + + "class Real {\n" + + " val note = \"${ /* } */ 1 }\"\n" + + "}\n" + + "class After\n"; + assertTrue(BuildHintAnnotationProcessor.declaresType(bracedComment, "After", true)); + // A raw string carries templates too. String raw = "package com.example\n" + "class Real {\n" diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 4b39d1adcc7..d1d1a404f29 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -3269,6 +3269,17 @@ private static int endOfKotlinTemplate(String s, int i) { j = s.startsWith("\"\"\"", j) ? endOfKotlinRawString(s, j) : endOfKotlinString(s, j); continue; } + // The expression is ordinary code, so it holds ordinary comments and + // char literals -- and a quote inside one of those is not a nested + // string. Reading `${ /* " */ 1 }` as if it were swallowed the rest + // of the file, hiding every annotation after it. + if (ch == '\'' || ch == '/') { + int skipped = skipNonCode(s, j, true); + if (skipped > j) { + j = skipped; + continue; + } + } if (ch == '{') { depth++; } else if (ch == '}') { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 4dbfe535288..4368ae94c94 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1138,6 +1138,19 @@ public void aStringInsideATemplateIsStillAString() { assertEquals("@Ios(teamId)", owned.get("ios.teamId")); assertNull(owned.get("ios.pods")); + // The expression is ordinary code, so it holds ordinary comments and + // char literals, and a quote inside one of those is not a nested string. + String commented = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "class Helper {\n" + + " val note = \"${ /* \\\" */ 1 }\"\n" + + "}\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(commented, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + // A brace inside the nested literal must not close the expression early. String braced = "package com.example\n" + "import com.codename1.annotations.buildhints.Ios\n" From 3269a927bc031eafb31c3b620a769dd406fbf683 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 03:57:50 +0300 Subject: [PATCH 084/115] Seed the alias closure with imports, and read an escaped name as a name `import ...Ios as Base` then `typealias AppIos = Base` is legal and still compiles to our annotation, but the two kinds of alias were collected into one list rather than composed: the typealias named the IMPORT alias, which the closure had never heard of, so the hint read as unowned and Add wrote the duplicate the next build refuses. The import aliases now seed the closure -- per source, since an import applies only to the file that writes it, so a typealias elsewhere naming the same word is not this one. And inside a template expression an escaped identifier is a NAME: everything between the backticks belongs to it, so a quote there does not open a string and a brace does not close the expression. `${ `"` }` left the template looking unterminated, and the rest of the file was blanked -- a live declaration after it dropped as an orphan in the processor, and every annotation after it disappeared in Settings. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 16 +++++++ .../BuildHintAnnotationProcessorTest.java | 16 +++++++ .../settings/CodenameOneSettings.java | 17 ++++++- .../settings/BuildHintCatalogTest.java | 45 +++++++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index 5cd0c2a2c12..c3e94bcb3f2 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -1081,6 +1081,22 @@ private static int endOfKotlinTemplate(char[] c, int i) { j = nonCode; continue; } + // An escaped identifier is code, and everything inside it is part of + // the name -- a quote there does not open a string and a brace does + // not close the expression. `${ `"` }` left the template looking + // unterminated, so the rest of the file was blanked and a live + // declaration after it dropped as an orphan. + if (c[j] == '`') { + int close = j + 1; + while (close < c.length && c[close] != '`') { + close++; + } + if (close >= c.length) { + return -1; + } + j = close + 1; + continue; + } if (ch == '{') { depth++; } else if (ch == '}') { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 3b2c5da1fee..025b7116322 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -721,6 +721,22 @@ public void aStringInsideAKotlinTemplateIsStillAString() { + "class After\n"; assertTrue(BuildHintAnnotationProcessor.declaresType(bracedComment, "After", true)); + // An escaped identifier inside the expression is a NAME: a quote in it + // does not open a string and a brace does not close the expression. + String escapedName = "package com.example\n" + + "class Real {\n" + + " val note = \"${ `\\\"` }\"\n" + + "}\n" + + "class After\n"; + assertTrue(BuildHintAnnotationProcessor.declaresType(escapedName, "After", true)); + + String bracedName = "package com.example\n" + + "class Real {\n" + + " val note = \"${ `}` }\"\n" + + "}\n" + + "class After\n"; + assertTrue(BuildHintAnnotationProcessor.declaresType(bracedName, "After", true)); + // A raw string carries templates too. String raw = "package com.example\n" + "class Real {\n" diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index d1d1a404f29..1b025facbf2 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2853,8 +2853,18 @@ static java.util.List kotlinTypeAliases(java.util.List sources, // import is file-scoped -- so it is decided per source, here, rather // than once for the whole sweep. boolean imported = importsAnnotation(source, simple, kotlin); + // `import ...Ios as Base` then `typealias AppIos = Base` is legal, + // and the compiled annotation is still ours. Collecting the two + // kinds of alias into one list left the typealias unresolved, + // because its right-hand side names the IMPORT alias rather than the + // annotation -- so the closure is seeded with them instead. + // + // Per source, since an import applies only to the file that writes + // it: a typealias elsewhere naming the same word is not this one. + java.util.List importedAs = kotlinImportAliases(source, simple, kotlin); for (String[] declared : typeAliasDeclarations(source, kotlin)) { - if (declared[1].equals(qualified) || (imported && declared[1].equals(simple))) { + if (declared[1].equals(qualified) || (imported && declared[1].equals(simple)) + || importedAs.contains(declared[1])) { if (!out.contains(declared[0])) { out.add(declared[0]); } @@ -3273,7 +3283,10 @@ private static int endOfKotlinTemplate(String s, int i) { // char literals -- and a quote inside one of those is not a nested // string. Reading `${ /* " */ 1 }` as if it were swallowed the rest // of the file, hiding every annotation after it. - if (ch == '\'' || ch == '/') { + // An escaped identifier belongs here too: everything inside it is + // part of the name, so a quote there does not open a string and a + // brace does not close the expression. + if (ch == '\'' || ch == '/' || ch == '`') { int skipped = skipNonCode(s, j, true); if (skipped > j) { j = skipped; diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 4368ae94c94..d95dea723c8 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1161,4 +1161,49 @@ public void aStringInsideATemplateIsStillAString() { CodenameOneSettings.collectAnnotationOwnedHints(braced, owned, true); assertNull(owned.get("ios.teamId")); } + + /// `import ...Ios as Base` then `typealias AppIos = Base` is legal, and the + /// compiled annotation is still ours. Collecting the two kinds of alias into + /// one list left the typealias unresolved, because its right-hand side names + /// the IMPORT alias rather than the annotation. + @Test + public void aTypeAliasOfAnImportAliasIsFollowed() { + String src = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios as Base\n" + + "typealias AppIos = Base\n" + + "@AppIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // An import alias applies only to the file that writes it, so a + // typealias in ANOTHER file naming the same word is not this one. + String usesIt = "package com.example\n" + + "typealias AppIos = Base\n" + + "@AppIos(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + String importsIt = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios as Base\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(usesIt, owned, true, + java.util.Collections.singletonList(importsIt)); + assertNull(owned.get("ios.teamId")); + } + + /// An escaped identifier inside a template expression is a NAME: a quote in + /// it does not open a string and a brace does not close the expression. + @Test + public void anEscapedIdentifierInsideATemplateIsNotAString() { + String src = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "class Helper {\n" + + " val note = \"${ `\\\"` }\"\n" + + "}\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + } } From 0ea601ba83aa04c3939ff296ea0d08f4339ac232 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:06:09 +0300 Subject: [PATCH 085/115] Keep a typealias inside the scope it is declared in A Kotlin typealias is top-level but it is not global, and the sweep over the project took every Kotlin file regardless of package. An alias declared in an unrelated package could then vouch for a same-named annotation the main class really does write itself, so the hint read as owned when nothing owns it -- which hides the editor and is indistinguishable from the tool being broken. A source counts now only where the main file can actually see it: the same package, or a package it imports from, by name or on demand. The package reader that decides it was already written inline inside declaresClass and is now shared, so the two cannot disagree about what a file's package is. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 68 ++++++++++++++++++- .../settings/BuildHintCatalogTest.java | 43 ++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 1b025facbf2..f73fbe7c34b 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2320,7 +2320,8 @@ private java.util.Map annotationOwnedHintsFromSource() { continue; } collectAnnotationOwnedHints(text, out, ext.equals(".kt"), - ext.equals(".kt") ? otherKotlinSources(binding.projectDir(), path) : null); + ext.equals(".kt") + ? visibleKotlinSources(binding.projectDir(), path, text) : null); return out; } } @@ -2334,7 +2335,8 @@ private java.util.Map annotationOwnedHintsFromSource() { if (found != null) { collectAnnotationOwnedHints(found, out, lastSourceWasKotlin, lastSourceWasKotlin - ? otherKotlinSources(binding.projectDir(), lastSourcePath) : null); + ? visibleKotlinSources(binding.projectDir(), lastSourcePath, found) + : null); return out; } // Genuinely no source. Distinct from "found and declares nothing", and @@ -2350,6 +2352,54 @@ private java.util.Map annotationOwnedHintsFromSource() { /// declarations made elsewhere. private String lastSourcePath; + /// The other Kotlin sources whose top-level declarations the main source can + /// actually SEE: its own package, or a package it imports from. + /// + /// A `typealias` is top-level, not file-scoped, but it is not global either. + /// Taking every Kotlin file in the project let an alias declared in an + /// unrelated package vouch for a same-named annotation the main class + /// really does write itself -- and the hint then read as owned when nothing + /// owns it, which hides the editor and is indistinguishable from the tool + /// being broken. + private java.util.List visibleKotlinSources(String projectDir, String exclude, + String mainSource) { + java.util.List out = new java.util.ArrayList<>(); + for (String text : otherKotlinSources(projectDir, exclude)) { + if (visibleTo(mainSource, text)) { + out.add(text); + } + } + return out; + } + + /// Whether `mainSource` can see `other`'s top-level declarations: same + /// package, or a package it imports from. + static boolean visibleTo(String mainSource, String other) { + String pkg = declaredPackageIn(other, true); + return pkg.equals(declaredPackageIn(mainSource, true)) + || importsFromPackage(mainSource, pkg); + } + + /// Whether `source` imports anything from `pkg` -- a named type or the + /// package on demand. An explicit import is how a declaration from another + /// package becomes visible at all. + private static boolean importsFromPackage(String source, String pkg) { + if (pkg == null || pkg.isEmpty()) { + return false; + } + String prefix = pkg + "."; + for (Imported imported : importsIn(source, true)) { + if (!imported.name.startsWith(prefix)) { + continue; + } + String rest = imported.name.substring(prefix.length()); + if (rest.indexOf('.') < 0) { + return true; + } + } + return false; + } + /// The text of every OTHER Kotlin source in the project, bounded. /// /// Only for declarations that are not file-scoped -- a `typealias` naming one @@ -2508,6 +2558,20 @@ static boolean declaresClass(String text, String main, String pkg) { /// inside a string, would otherwise make an unrelated file answer for the /// main class -- ownership then reads as empty and Settings offers Add for a /// hint the real main class already annotates. + /// The package `source` declares, or "" for the default package. + static String declaredPackageIn(String text, boolean kotlin) { + int pkgAt = nextMarker(text, "package", 0, kotlin); + while (pkgAt >= 0) { + int after = pkgAt + "package".length(); + if (after < text.length() && !continuesAName(text.charAt(after)) + && (pkgAt == 0 || !continuesAName(text.charAt(pkgAt - 1)))) { + return qualifiedNameAt(text, after, kotlin); + } + pkgAt = nextMarker(text, "package", after, kotlin); + } + return ""; + } + static boolean declaresClass(String text, String main, String pkg, boolean kotlin) { String declaredPkg = ""; int pkgAt = nextMarker(text, "package", 0, kotlin); diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index d95dea723c8..b765eb05b10 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1206,4 +1206,47 @@ public void anEscapedIdentifierInsideATemplateIsNotAString() { CodenameOneSettings.collectAnnotationOwnedHints(src, owned, true); assertEquals("@Ios(teamId)", owned.get("ios.teamId")); } + + /// A `typealias` is top-level but not global: another package's declaration + /// is invisible unless imported. Taking every Kotlin file in the project let + /// an alias declared in an unrelated package vouch for a same-named + /// annotation the main class really does write itself, so the hint read as + /// owned when nothing owns it -- which hides the editor and is + /// indistinguishable from the tool being broken. + @Test + public void anAliasInAnotherPackageIsNotVisible() { + String main = "package com.example\n@AppIos(teamId = \"X\")\nclass MyApp\n"; + + // Same package: visible. + assertTrue(CodenameOneSettings.visibleTo(main, + "package com.example\ntypealias AppIos = Ios\n")); + + // Another package, not imported: not visible. + assertFalse(CodenameOneSettings.visibleTo(main, + "package com.other\ntypealias AppIos = Ios\n")); + + // Another package, imported by name: visible. + String importing = "package com.example\n" + + "import com.other.AppIos\n" + + "@AppIos(teamId = \"X\")\nclass MyApp\n"; + assertTrue(CodenameOneSettings.visibleTo(importing, + "package com.other\ntypealias AppIos = Ios\n")); + + // ...and on demand. + String wildcard = "package com.example\n" + + "import com.other.*\n" + + "@AppIos(teamId = \"X\")\nclass MyApp\n"; + assertTrue(CodenameOneSettings.visibleTo(wildcard, + "package com.other\ntypealias AppIos = Ios\n")); + + // An import from a DIFFERENT package does not make com.other visible. + String elsewhere = "package com.example\n" + + "import com.third.Thing\n" + + "@AppIos(teamId = \"X\")\nclass MyApp\n"; + assertFalse(CodenameOneSettings.visibleTo(elsewhere, + "package com.other\ntypealias AppIos = Ios\n")); + + // Two default-package files see each other. + assertTrue(CodenameOneSettings.visibleTo("class MyApp\n", "typealias AppIos = Ios\n")); + } } From 3ac9d2df5b713c0d5e27348a5bba1d1f3f89a86d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:16:03 +0300 Subject: [PATCH 086/115] Resolve an alias by symbol, under the name the importer gives it Visibility is per symbol, not per package. `import com.other.Unrelated` exposes nothing else from com.other, and `import com.other.AppIos as Custom` exposes that one under Custom. The package-level answer was wrong in both directions: an unrelated import let an alias hide the editor for a hint nothing owns, and a renamed one lost its local name so a real annotation went unrecognised and Add wrote the duplicate. Each declaration now carries the name its own file uses, the name the main file sees it under, and the package it lives in. The chain follows the local name within that package -- which is the scope a top-level declaration resolves in, so a chain may span files of one package -- and only the visible end reaches the main file, under whatever name the import gives it. An intermediate link the main file cannot name no longer leaks out of its package. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 228 +++++++++++------- .../settings/BuildHintCatalogTest.java | 107 +++++--- 2 files changed, 211 insertions(+), 124 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index f73fbe7c34b..77b531dc77f 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2321,7 +2321,7 @@ private java.util.Map annotationOwnedHintsFromSource() { } collectAnnotationOwnedHints(text, out, ext.equals(".kt"), ext.equals(".kt") - ? visibleKotlinSources(binding.projectDir(), path, text) : null); + ? otherKotlinSources(binding.projectDir(), path) : null); return out; } } @@ -2335,7 +2335,7 @@ private java.util.Map annotationOwnedHintsFromSource() { if (found != null) { collectAnnotationOwnedHints(found, out, lastSourceWasKotlin, lastSourceWasKotlin - ? visibleKotlinSources(binding.projectDir(), lastSourcePath, found) + ? otherKotlinSources(binding.projectDir(), lastSourcePath) : null); return out; } @@ -2352,54 +2352,6 @@ private java.util.Map annotationOwnedHintsFromSource() { /// declarations made elsewhere. private String lastSourcePath; - /// The other Kotlin sources whose top-level declarations the main source can - /// actually SEE: its own package, or a package it imports from. - /// - /// A `typealias` is top-level, not file-scoped, but it is not global either. - /// Taking every Kotlin file in the project let an alias declared in an - /// unrelated package vouch for a same-named annotation the main class - /// really does write itself -- and the hint then read as owned when nothing - /// owns it, which hides the editor and is indistinguishable from the tool - /// being broken. - private java.util.List visibleKotlinSources(String projectDir, String exclude, - String mainSource) { - java.util.List out = new java.util.ArrayList<>(); - for (String text : otherKotlinSources(projectDir, exclude)) { - if (visibleTo(mainSource, text)) { - out.add(text); - } - } - return out; - } - - /// Whether `mainSource` can see `other`'s top-level declarations: same - /// package, or a package it imports from. - static boolean visibleTo(String mainSource, String other) { - String pkg = declaredPackageIn(other, true); - return pkg.equals(declaredPackageIn(mainSource, true)) - || importsFromPackage(mainSource, pkg); - } - - /// Whether `source` imports anything from `pkg` -- a named type or the - /// package on demand. An explicit import is how a declaration from another - /// package becomes visible at all. - private static boolean importsFromPackage(String source, String pkg) { - if (pkg == null || pkg.isEmpty()) { - return false; - } - String prefix = pkg + "."; - for (Imported imported : importsIn(source, true)) { - if (!imported.name.startsWith(prefix)) { - continue; - } - String rest = imported.name.substring(prefix.length()); - if (rest.indexOf('.') < 0) { - return true; - } - } - return false; - } - /// The text of every OTHER Kotlin source in the project, bounded. /// /// Only for declarations that are not file-scoped -- a `typealias` naming one @@ -2888,7 +2840,7 @@ static String kotlinTypeAlias(String source, String simple, boolean kotlin) { /// and use only the second. static java.util.List kotlinTypeAliases(String source, String simple, boolean kotlin) { - return kotlinTypeAliases(java.util.Collections.singletonList(source), simple, kotlin); + return kotlinTypeAliases(visibleTypeAliases(source, null), simple, kotlin); } /// Every name that resolves to `simple`, across all of `sources`, following @@ -2901,49 +2853,143 @@ static java.util.List kotlinTypeAliases(String source, String simple, /// refuses. Resolved by closure rather than by recursion so that a cycle -- /// which the compiler rejects, but this reader must not hang on -- simply /// stops adding names. - static java.util.List kotlinTypeAliases(java.util.List sources, String simple, - boolean kotlin) { - java.util.List out = new java.util.ArrayList(); - if (!kotlin || sources == null) { + /// A `typealias`, with where it was written and the name the main file sees + /// it under. + /// + /// The two names differ because an import may rename it: `import + /// com.other.AppIos as Custom` makes `com.other`'s `AppIos` usable only as + /// `Custom`, so the file that declares it and the file that writes the + /// annotation disagree about what it is called. + static final class AliasDeclaration { + /// The name in the file that declares it, which its own chain uses. + final String local; + /// The name the main file writes, or null when it cannot see this one. + final String visible; + final String target; + /// The package it is declared in, which is the scope its chain resolves + /// in -- a chain may span files, but only within one package. + final String scope; + /// The text that declares it, whose imports decide what its target names. + final String owner; + + AliasDeclaration(String local, String visible, String target, String scope, String owner) { + this.local = local; + this.visible = visible; + this.target = target; + this.scope = scope; + this.owner = owner; + } + } + + /// Every `typealias` `mainSource` can see, with the name it sees it under. + /// + /// Visibility is per SYMBOL, not per package: `import com.other.Unrelated` + /// exposes nothing else from `com.other`, and `import com.other.AppIos as + /// Custom` exposes that one under `Custom`. Reducing this to "does the main + /// file import anything from that package" was wrong in both directions -- + /// it let an unrelated import expose an alias that hides the editor for a + /// hint nothing owns, and it lost the local name of a renamed one so a real + /// annotation went unrecognised and Add wrote the duplicate. + static java.util.List visibleTypeAliases(String mainSource, + java.util.List others) { + java.util.List out = new java.util.ArrayList<>(); + if (mainSource == null) { return out; } - String qualified = "com.codename1.annotations.buildhints." + simple; - java.util.List declarations = new java.util.ArrayList(); - for (String source : sources) { - if (source == null) { + String mainPkg = declaredPackageIn(mainSource, true); + for (String[] declared : typeAliasDeclarations(mainSource, true)) { + out.add(new AliasDeclaration(declared[0], declared[0], declared[1], mainPkg, + mainSource)); + } + if (others == null) { + return out; + } + java.util.List imports = importsIn(mainSource, true); + for (String other : others) { + if (other == null) { continue; } + String pkg = declaredPackageIn(other, true); + boolean samePackage = pkg.equals(mainPkg); + for (String[] declared : typeAliasDeclarations(other, true)) { + String visible = samePackage ? declared[0] + : importedNameOf(imports, pkg, declared[0]); + // Kept even when invisible: it may still be a LINK in a chain + // whose visible end is imported, and that chain resolves in the + // package it is written in. + out.add(new AliasDeclaration(declared[0], visible, declared[1], pkg, other)); + } + } + return out; + } + + /// The name `imports` gives `pkg`.`simple`, or null when none of them does. + /// + /// A named import wins over an on-demand one, since it is the more specific + /// statement about that symbol and may rename it. + private static String importedNameOf(java.util.List imports, String pkg, + String simple) { + String qualified = pkg == null || pkg.isEmpty() ? simple : pkg + "." + simple; + String onDemand = null; + for (Imported imported : imports) { + if (imported.name.equals(qualified)) { + return imported.alias != null ? imported.alias : simple; + } + if (imported.name.equals(pkg + ".*")) { + onDemand = simple; + } + } + return onDemand; + } + + /// Every name that resolves to `simple`, following a CHAIN of aliases. + /// + /// `typealias AppIos = Ios` then `typealias CustomIos = AppIos` is legal, + /// and `@CustomIos(...)` still compiles to our annotation. Accepting only a + /// right-hand side that names the annotation directly left the hint reading + /// as unowned, so Add wrote the duplicate declaration the next build + /// refuses. Resolved by closure rather than by recursion so that a cycle -- + /// which the compiler rejects, but this reader must not hang on -- simply + /// stops adding names. + /// + /// The chain is followed by the LOCAL name within one package, which is the + /// scope a top-level declaration resolves in, and only names the main file + /// can actually see are returned. + static java.util.List kotlinTypeAliases(java.util.List declarations, + String simple, boolean kotlin) { + java.util.List out = new java.util.ArrayList(); + if (!kotlin || declarations == null) { + return out; + } + String qualified = "com.codename1.annotations.buildhints." + simple; + java.util.List resolved = new java.util.ArrayList(); + java.util.List pending = new java.util.ArrayList<>(); + for (AliasDeclaration declared : declarations) { // The bare name counts only where an import makes it ours, and that - // import is file-scoped -- so it is decided per source, here, rather - // than once for the whole sweep. - boolean imported = importsAnnotation(source, simple, kotlin); - // `import ...Ios as Base` then `typealias AppIos = Base` is legal, - // and the compiled annotation is still ours. Collecting the two - // kinds of alias into one list left the typealias unresolved, - // because its right-hand side names the IMPORT alias rather than the - // annotation -- so the closure is seeded with them instead. - // - // Per source, since an import applies only to the file that writes - // it: a typealias elsewhere naming the same word is not this one. - java.util.List importedAs = kotlinImportAliases(source, simple, kotlin); - for (String[] declared : typeAliasDeclarations(source, kotlin)) { - if (declared[1].equals(qualified) || (imported && declared[1].equals(simple)) - || importedAs.contains(declared[1])) { - if (!out.contains(declared[0])) { - out.add(declared[0]); - } - } else { - declarations.add(declared); - } + // import is file-scoped -- so it is decided per owner, here, rather + // than once for the whole sweep. `import ...Ios as Base` then + // `typealias AppIos = Base` is the same point one step along. + boolean imported = importsAnnotation(declared.owner, simple, kotlin); + java.util.List importedAs = kotlinImportAliases(declared.owner, simple, kotlin); + if (declared.target.equals(qualified) + || (imported && declared.target.equals(simple)) + || importedAs.contains(declared.target)) { + add(resolved, declared.scope + "\u0000" + declared.local); + add(out, declared.visible); + } else { + pending.add(declared); } } // Each pass can only resolve one more link, so the number of passes is // bounded by the number of declarations left over. - for (int pass = 0; pass < declarations.size(); pass++) { + for (int pass = 0; pass < pending.size(); pass++) { boolean grew = false; - for (String[] declared : declarations) { - if (!out.contains(declared[0]) && out.contains(declared[1])) { - out.add(declared[0]); + for (AliasDeclaration declared : pending) { + String key = declared.scope + "\u0000" + declared.local; + if (!resolved.contains(key) + && resolved.contains(declared.scope + "\u0000" + declared.target)) { + resolved.add(key); + add(out, declared.visible); grew = true; } } @@ -2954,6 +3000,12 @@ static java.util.List kotlinTypeAliases(java.util.List sources, return out; } + private static void add(java.util.List out, String value) { + if (value != null && !out.contains(value)) { + out.add(value); + } + } + /// Every `typealias Name = Target` in `source`, as {name, target}. static java.util.List typeAliasDeclarations(String source, boolean kotlin) { java.util.List out = new java.util.ArrayList(); @@ -3050,12 +3102,8 @@ static void collectAnnotationOwnedHints(String source, java.util.Map forAliases = new java.util.ArrayList(); - forAliases.add(source); - if (otherSources != null) { - forAliases.addAll(otherSources); - } - aliases.addAll(kotlinTypeAliases(forAliases, simple, kotlin)); + aliases.addAll(kotlinTypeAliases(visibleTypeAliases(source, otherSources), + simple, kotlin)); // The simple name only counts when an import makes it OUR annotation. // @Build and @Android are ordinary enough names that another library's // annotation with a matching attribute would otherwise be read as diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index b765eb05b10..729073496ef 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1207,46 +1207,85 @@ public void anEscapedIdentifierInsideATemplateIsNotAString() { assertEquals("@Ios(teamId)", owned.get("ios.teamId")); } - /// A `typealias` is top-level but not global: another package's declaration - /// is invisible unless imported. Taking every Kotlin file in the project let - /// an alias declared in an unrelated package vouch for a same-named - /// annotation the main class really does write itself, so the hint read as - /// owned when nothing owns it -- which hides the editor and is - /// indistinguishable from the tool being broken. - @Test - public void anAliasInAnotherPackageIsNotVisible() { - String main = "package com.example\n@AppIos(teamId = \"X\")\nclass MyApp\n"; - - // Same package: visible. - assertTrue(CodenameOneSettings.visibleTo(main, - "package com.example\ntypealias AppIos = Ios\n")); - - // Another package, not imported: not visible. - assertFalse(CodenameOneSettings.visibleTo(main, - "package com.other\ntypealias AppIos = Ios\n")); - - // Another package, imported by name: visible. - String importing = "package com.example\n" - + "import com.other.AppIos\n" + /// A `typealias` is top-level but not global, and visibility is per SYMBOL + /// rather than per package: `import com.other.Unrelated` exposes nothing + /// else from `com.other`, and `import com.other.AppIos as Custom` exposes + /// that one under `Custom`. A package-level answer was wrong both ways -- + /// it let an unrelated import expose an alias, hiding the editor for a hint + /// nothing owns, and it lost the renamed name so a real annotation went + /// unrecognised and Add wrote the duplicate. + @Test + public void aliasVisibilityIsPerSymbol() { + String elsewhere = "package com.other\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "typealias AppIos = Ios\n"; + java.util.List others = java.util.Collections.singletonList(elsewhere); + + // Not imported at all: invisible. + String plain = "package com.example\n@AppIos(teamId = \"X\")\nclass MyApp\n"; + assertTrue(CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(plain, others), "Ios", true).isEmpty()); + + // Another symbol from the same package: still invisible. + String unrelated = "package com.example\n" + + "import com.other.Unrelated\n" + "@AppIos(teamId = \"X\")\nclass MyApp\n"; - assertTrue(CodenameOneSettings.visibleTo(importing, - "package com.other\ntypealias AppIos = Ios\n")); + assertTrue(CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(unrelated, others), "Ios", true).isEmpty()); - // ...and on demand. + // Named: visible under its own name. + String named = "package com.example\n" + + "import com.other.AppIos\n" + + "@AppIos(teamId = \"X\")\nclass MyApp\n"; + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(named, others), "Ios", true)); + + // Renamed: visible under the NEW name, and not under the old one. + String renamed = "package com.example\n" + + "import com.other.AppIos as Custom\n" + + "@Custom(teamId = \"X\")\nclass MyApp\n"; + assertEquals(java.util.Collections.singletonList("Custom"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(renamed, others), "Ios", true)); + + // On demand: visible under its own name. String wildcard = "package com.example\n" + "import com.other.*\n" + "@AppIos(teamId = \"X\")\nclass MyApp\n"; - assertTrue(CodenameOneSettings.visibleTo(wildcard, - "package com.other\ntypealias AppIos = Ios\n")); + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(wildcard, others), "Ios", true)); - // An import from a DIFFERENT package does not make com.other visible. - String elsewhere = "package com.example\n" - + "import com.third.Thing\n" - + "@AppIos(teamId = \"X\")\nclass MyApp\n"; - assertFalse(CodenameOneSettings.visibleTo(elsewhere, - "package com.other\ntypealias AppIos = Ios\n")); + // Same package needs no import. + String samePackage = "package com.other\n@AppIos(teamId = \"X\")\nclass MyApp\n"; + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(samePackage, others), "Ios", true)); + } - // Two default-package files see each other. - assertTrue(CodenameOneSettings.visibleTo("class MyApp\n", "typealias AppIos = Ios\n")); + /// A chain resolves in the package it is written in, and only its visible + /// end reaches the main file -- under whatever name the import gives it. + @Test + public void aChainResolvesInItsOwnScope() { + String elsewhere = "package com.other\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "typealias Base = Ios\n" + + "typealias AppIos = Base\n"; + String main = "package com.example\n" + + "import com.other.AppIos as Custom\n" + + "@Custom(teamId = \"ABCDE12345\")\nclass MyApp\n"; + + // Custom resolves; Base, which the main file cannot name, does not leak. + assertEquals(java.util.Collections.singletonList("Custom"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, + java.util.Collections.singletonList(elsewhere)), + "Ios", true)); + + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, true, + java.util.Collections.singletonList(elsewhere)); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); } } From 4b8351d18a155446b13c4813af28cc7722946bb3 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:23:49 +0300 Subject: [PATCH 087/115] Follow a chain link across a package boundary A link may be imported: package `a` declares `typealias Base = Ios`, package `b` imports `a.Base` and declares `typealias AppIos = Base`. Looking the target up only in the declaring file's own package stopped the chain there, so the hint read as unowned and Add wrote the duplicate declaration the next build refuses. Each target is now resolved through the file that writes it, most specific first: a qualified spelling names its package outright, a named import -- under its own name or an `as` name -- says where it comes from, and failing both it is the declaring package's own or comes from a package imported on demand. Worked out once per declaration rather than on every pass of the closure. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 65 +++++++++++++++++-- .../settings/BuildHintCatalogTest.java | 48 ++++++++++++++ 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 77b531dc77f..837c311726e 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2964,6 +2964,9 @@ static java.util.List kotlinTypeAliases(java.util.List String qualified = "com.codename1.annotations.buildhints." + simple; java.util.List resolved = new java.util.ArrayList(); java.util.List pending = new java.util.ArrayList<>(); + // The key each pending declaration's target might name, worked out once + // rather than on every pass. + java.util.List> pendingTargets = new java.util.ArrayList<>(); for (AliasDeclaration declared : declarations) { // The bare name counts only where an import makes it ours, and that // import is file-scoped -- so it is decided per owner, here, rather @@ -2978,19 +2981,26 @@ static java.util.List kotlinTypeAliases(java.util.List add(out, declared.visible); } else { pending.add(declared); + pendingTargets.add(targetKeys(declared)); } } // Each pass can only resolve one more link, so the number of passes is // bounded by the number of declarations left over. for (int pass = 0; pass < pending.size(); pass++) { boolean grew = false; - for (AliasDeclaration declared : pending) { + for (int i = 0; i < pending.size(); i++) { + AliasDeclaration declared = pending.get(i); String key = declared.scope + "\u0000" + declared.local; - if (!resolved.contains(key) - && resolved.contains(declared.scope + "\u0000" + declared.target)) { - resolved.add(key); - add(out, declared.visible); - grew = true; + if (resolved.contains(key)) { + continue; + } + for (String candidate : pendingTargets.get(i)) { + if (resolved.contains(candidate)) { + resolved.add(key); + add(out, declared.visible); + grew = true; + break; + } } } if (!grew) { @@ -3000,6 +3010,49 @@ static java.util.List kotlinTypeAliases(java.util.List return out; } + /// The chain links `declared`'s target might name, most specific first. + /// + /// A link may cross a package boundary: package `a` declares + /// `typealias Base = Ios`, package `b` imports `a.Base` and declares + /// `typealias AppIos = Base`. Looking only in the declaring file's own + /// package missed that, so the chain stopped there, the hint read as unowned + /// and Add wrote the duplicate declaration the next build refuses. + /// + /// A qualified target names its package outright. Otherwise a named import + /// -- under its own name or an `as` name -- says where it comes from, and + /// failing that it is the declaring package's own, or any package imported + /// on demand. + private static java.util.List targetKeys(AliasDeclaration declared) { + java.util.List out = new java.util.ArrayList(); + String target = declared.target; + int dot = target.lastIndexOf('.'); + if (dot > 0) { + out.add(target.substring(0, dot) + "\u0000" + target.substring(dot + 1)); + return out; + } + java.util.List onDemand = new java.util.ArrayList(); + for (Imported imported : importsIn(declared.owner, true)) { + int at = imported.name.lastIndexOf('.'); + if (at <= 0) { + continue; + } + String pkg = imported.name.substring(0, at); + String simpleName = imported.name.substring(at + 1); + if ("*".equals(simpleName)) { + onDemand.add(pkg + "\u0000" + target); + continue; + } + String visibleAs = imported.alias != null ? imported.alias : simpleName; + if (visibleAs.equals(target)) { + out.add(pkg + "\u0000" + simpleName); + return out; + } + } + out.add(declared.scope + "\u0000" + target); + out.addAll(onDemand); + return out; + } + private static void add(java.util.List out, String value) { if (value != null && !out.contains(value)) { out.add(value); diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 729073496ef..ee8ddc2e56c 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1288,4 +1288,52 @@ public void aChainResolvesInItsOwnScope() { java.util.Collections.singletonList(elsewhere)); assertEquals("@Ios(teamId)", owned.get("ios.teamId")); } + + /// A chain link may cross a package boundary: `a` declares + /// `typealias Base = Ios`, `b` imports `a.Base` and declares + /// `typealias AppIos = Base`. Looking only in the declaring file's own + /// package stopped the chain there, so the hint read as unowned and Add + /// wrote the duplicate the next build refuses. + @Test + public void aChainLinkMayBeImportedFromAnotherPackage() { + String a = "package a\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "typealias Base = Ios\n"; + String b = "package b\n" + + "import a.Base\n" + + "typealias AppIos = Base\n"; + String main = "package com.example\n" + + "import b.AppIos\n" + + "@AppIos(teamId = \"ABCDE12345\")\nclass MyApp\n"; + java.util.List others = java.util.Arrays.asList(a, b); + + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, others), "Ios", true)); + + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, true, others); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // The renamed form of the link, and the qualified spelling. + String renamedLink = "package b\n" + + "import a.Base as Root\n" + + "typealias AppIos = Root\n"; + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, + java.util.Arrays.asList(a, renamedLink)), "Ios", true)); + + String qualifiedLink = "package b\ntypealias AppIos = a.Base\n"; + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, + java.util.Arrays.asList(a, qualifiedLink)), "Ios", true)); + + // A link that names nothing reachable is still not ours. + String broken = "package b\ntypealias AppIos = Base\n"; + assertTrue(CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, + java.util.Arrays.asList(a, broken)), "Ios", true).isEmpty()); + } } From 0952896dab190a85fab17251cd8613ec5c11bf52 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:32:56 +0300 Subject: [PATCH 088/115] A private typealias belongs to its file On a top-level Kotlin declaration `private` means visible in that FILE, not in the package -- so another file's private alias is not a name the main source can write. Exposing it let it vouch for an unrelated annotation of the same name, so the hint read as owned when nothing owns it and the editor was hidden, which is indistinguishable from the tool being broken. The modifier is read backwards over the run that may precede the keyword and stops at anything that is not one, so a `private` belonging to whatever came before is not taken for this declaration's. `internal` is module-wide and so is not this file's alone; a private alias in the main file itself is in the file it belongs to and still counts. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 51 +++++++++++++++++- .../settings/BuildHintCatalogTest.java | 53 +++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 837c311726e..61238a86862 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2881,6 +2881,40 @@ static final class AliasDeclaration { } } + /// Whether the declaration at `at` carries the `private` modifier. + /// + /// Read backwards over the modifiers that may precede the keyword, stopping + /// at anything that is not one -- so a `private` belonging to whatever came + /// before this declaration is not read as this one's. + private static boolean declaredPrivate(String source, int at) { + int i = at; + for (int word = 0; word < 4; word++) { + int end = i; + while (end > 0 && (source.charAt(end - 1) == ' ' || source.charAt(end - 1) == '\t' + || source.charAt(end - 1) == '\n' || source.charAt(end - 1) == '\r')) { + end--; + } + int start = end; + while (start > 0 && continuesAName(source.charAt(start - 1))) { + start--; + } + if (start == end) { + return false; + } + String modifier = source.substring(start, end); + if ("private".equals(modifier)) { + return true; + } + if (!"public".equals(modifier) && !"internal".equals(modifier) + && !"protected".equals(modifier) && !"actual".equals(modifier) + && !"expect".equals(modifier)) { + return false; + } + i = start; + } + return false; + } + /// Every `typealias` `mainSource` can see, with the name it sees it under. /// /// Visibility is per SYMBOL, not per package: `import com.other.Unrelated` @@ -2912,6 +2946,14 @@ static java.util.List visibleTypeAliases(String mainSource, String pkg = declaredPackageIn(other, true); boolean samePackage = pkg.equals(mainPkg); for (String[] declared : typeAliasDeclarations(other, true)) { + if ("private".equals(declared[2])) { + // On a top-level Kotlin declaration `private` means this FILE + // only, not this package -- so another file's is not a name + // the main source can write, and treating it as one let it + // vouch for an unrelated annotation of the same name and hide + // the editor for a hint nothing owns. + continue; + } String visible = samePackage ? declared[0] : importedNameOf(imports, pkg, declared[0]); // Kept even when invisible: it may still be a LINK in a chain @@ -3059,7 +3101,11 @@ private static void add(java.util.List out, String value) { } } - /// Every `typealias Name = Target` in `source`, as {name, target}. + /// Every `typealias Name = Target` in `source`, as {name, target, private}. + /// + /// The third element is "private" when the declaration carries that + /// modifier, which on a top-level Kotlin declaration means visible in this + /// FILE only -- not in the package. static java.util.List typeAliasDeclarations(String source, boolean kotlin) { java.util.List out = new java.util.ArrayList(); if (!kotlin || source == null) { @@ -3078,7 +3124,8 @@ static java.util.List typeAliasDeclarations(String source, boolean kot String name = componentText(source, n, end, kotlin); int eq = nextLiveChar(source, end, kotlin); if (eq >= 0 && source.charAt(eq) == '=') { - out.add(new String[] {name, qualifiedNameAt(source, eq + 1, kotlin)}); + out.add(new String[] {name, qualifiedNameAt(source, eq + 1, kotlin), + declaredPrivate(source, at) ? "private" : ""}); } } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index ee8ddc2e56c..fa45861be3d 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1336,4 +1336,57 @@ public void aChainLinkMayBeImportedFromAnotherPackage() { CodenameOneSettings.visibleTypeAliases(main, java.util.Arrays.asList(a, broken)), "Ios", true).isEmpty()); } + + /// On a top-level Kotlin declaration `private` means this FILE only, not + /// this package. Exposing another file's private alias let it vouch for an + /// unrelated annotation of the same name, hiding the editor for a hint + /// nothing owns. + @Test + public void aPrivateAliasBelongsToItsFile() { + String sibling = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "private typealias AppIos = Ios\n"; + String main = "package com.example\n@AppIos(teamId = \"X\")\nclass MyApp\n"; + java.util.List others = java.util.Collections.singletonList(sibling); + + assertTrue(CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, others), "Ios", true).isEmpty()); + + // Without the modifier the same declaration is visible in the package. + String shared = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "typealias AppIos = Ios\n"; + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, + java.util.Collections.singletonList(shared)), "Ios", true)); + + // A private alias in the MAIN file is the file it belongs to. + String privateHere = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "private typealias AppIos = Ios\n" + + "@AppIos(teamId = \"ABCDE12345\")\nclass MyApp\n"; + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(privateHere, owned, true); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // `internal` is module-wide, so it is not this file's alone. + String internal = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "internal typealias AppIos = Ios\n"; + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, + java.util.Collections.singletonList(internal)), "Ios", true)); + + // A `private` belonging to whatever came before is not this one's. + String before = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios\n" + + "private val x = 1\n" + + "typealias AppIos = Ios\n"; + assertEquals(java.util.Collections.singletonList("AppIos"), + CodenameOneSettings.kotlinTypeAliases( + CodenameOneSettings.visibleTypeAliases(main, + java.util.Collections.singletonList(before)), "Ios", true)); + } } From 8e3fbdafce840dc917e82c95b8ebc405f4c869c5 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:43:36 +0300 Subject: [PATCH 089/115] A marker at the end of the file, and a comment before a modifier A continuation marker with nothing after it is still a marker: a file whose last byte is that backslash reads as an empty value to Properties.load, while leaving it in produced a key ending in `\` that matched nothing -- so the declaration stayed behind and the verification build failed on the duplicate the goal had just created. Stripped after the continuation loop as well as inside it. And `private /* note */ typealias AppIos = Ios` is legal, while the backward walk for the modifier skips only whitespace and stopped at the comment, reporting the declaration as public. It reads over a blanked copy now -- offsets and line breaks preserved -- since the forward scanner cannot help a backward walk. The alias survey moved out of the per-hint loop while doing that: which aliases exist, and what the main file calls them, does not depend on which hint is being asked about. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 8 ++++ .../MigrateBuildHintsPropertyParsingTest.java | 26 ++++++++++++ .../settings/CodenameOneSettings.java | 42 +++++++++++++++++-- .../settings/BuildHintCatalogTest.java | 10 +++++ 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 53a622ad2d2..f81d189caec 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1407,6 +1407,14 @@ static void removeMigratedLines(File settingsFile, List keys) throws IOE last++; logical.append(withoutTerminator(lines.get(last)).replaceFirst("^\\s+", "")); } + // A marker with nothing after it is still a marker: a file whose + // last byte is that backslash reads as an empty value to + // Properties.load, while leaving it in produced a key ending in `\` + // that matched nothing -- so the declaration stayed and the + // verification build failed on the duplicate. + if (continues(logical.toString())) { + logical.setLength(logical.length() - 1); + } String key = propertyKeyOf(logical.toString()); if (key != null && wanted.containsKey(key)) { i = last; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index e194823a895..abf1c8609ef 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -670,4 +670,30 @@ public void aSeparatorOnAContinuationLineStillNamesItsKey() throws Exception { java.util.Arrays.asList("codename1.arg.ios.teamId")); assertEquals("codename1.displayName=Demo\n", read(f)); } + + /// A continuation marker with nothing after it is still a marker: a file + /// whose last byte is that backslash reads as an empty value to + /// `Properties.load`, while leaving it in produced a key ending in `\` that + /// matched nothing -- so the declaration stayed and the verification build + /// failed on the duplicate. + @Test + public void aContinuationMarkerAtTheEndOfTheFileIsStillAMarker() throws Exception { + File f = File.createTempFile("cn1-settings", ".properties"); + f.deleteOnExit(); + write(f, "codename1.displayName=Demo\n" + + "codename1.arg.ios.teamId\\"); + + java.util.Properties loaded = new java.util.Properties(); + java.io.InputStream in = new java.io.FileInputStream(f); + try { + loaded.load(in); + } finally { + in.close(); + } + assertEquals("", loaded.getProperty("codename1.arg.ios.teamId")); + + MigrateBuildHintsMojo.removeMigratedLines(f, + java.util.Arrays.asList("codename1.arg.ios.teamId")); + assertEquals("codename1.displayName=Demo\n", read(f)); + } } diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 61238a86862..4b3a2208f0f 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2881,6 +2881,37 @@ static final class AliasDeclaration { } } + /// `source` with its comments and literals replaced by spaces, offsets and + /// line breaks preserved. + /// + /// For reading BACKWARDS, which the forward scanner cannot help with: + /// `private /* note */ typealias AppIos = Ios` is legal, and a backward walk + /// that skips only whitespace stops at the comment and reports the + /// declaration as public. + private static String blanked(String source, boolean kotlin) { + char[] out = source.toCharArray(); + int i = 0; + while (i < out.length) { + char c = out[i]; + if (c != '"' && c != '\'' && c != '/' && c != '`') { + i++; + continue; + } + int end = skipNonCode(source, i, kotlin); + if (end <= i) { + i++; + continue; + } + while (i < end) { + if (out[i] != '\n' && out[i] != '\r') { + out[i] = ' '; + } + i++; + } + } + return new String(out); + } + /// Whether the declaration at `at` carries the `private` modifier. /// /// Read backwards over the modifiers that may precede the keyword, stopping @@ -3125,7 +3156,8 @@ static java.util.List typeAliasDeclarations(String source, boolean kot int eq = nextLiveChar(source, end, kotlin); if (eq >= 0 && source.charAt(eq) == '=') { out.add(new String[] {name, qualifiedNameAt(source, eq + 1, kotlin), - declaredPrivate(source, at) ? "private" : ""}); + declaredPrivate(blanked(source, kotlin), at) + ? "private" : ""}); } } } @@ -3185,6 +3217,11 @@ static void collectAnnotationOwnedHints(String source, java.util.Map out, boolean kotlin, java.util.List otherSources) { + // Once, not once per hint: which aliases exist and what the main file + // calls them does not depend on which hint is being asked about. + java.util.List declaredAliases = + kotlin ? visibleTypeAliases(source, otherSources) + : new java.util.ArrayList(); for (com.codename1.build.shared.BuildHints.Hint h : com.codename1.build.shared.BuildHints.entries()) { if (!h.isAnnotated()) { continue; @@ -3202,8 +3239,7 @@ static void collectAnnotationOwnedHints(String source, java.util.Map Date: Tue, 25 Aug 2026 05:00:54 +0300 Subject: [PATCH 090/115] A type in your own package beats a wildcard import Both languages resolve a simple name to a same-package type before an on-demand import, so a project that declares its own `Ios` and wildcard-imports ours writes its own. Reading that as ours hid the editor for a hint the processor never emits, which is indistinguishable from the tool being broken. A NAMED import still wins, since it is the more specific statement and a file may not both import a name and declare it. The type lookup is wider than the main-class one on purpose: an annotation is `annotation class` in Kotlin and `@interface` in Java, and any top-level type of that name shadows the import. The shadowing sources are this file and the rest of its package as already read. The sweep exists for typealiases and so is Kotlin-only, which leaves a same-named type in a file nobody opened reading as owned -- exactly as it did before this check existed, and recorded where the list is built rather than left to be discovered. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 95 ++++++++++++++++++- .../settings/BuildHintCatalogTest.java | 56 +++++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 4b3a2208f0f..6995597ed98 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2802,6 +2802,18 @@ static java.util.List importsIn(String source, boolean kotlin) { /// import shadows an on-demand one, so their `Ios` beats our wildcard. That /// is the language's rule, not a preference. static boolean importsAnnotation(String source, String simple, boolean kotlin) { + return importsAnnotation(source, simple, kotlin, false); + } + + /// As above; `shadowed` says the same package declares a type of that name. + /// + /// A same-package type beats an ON-DEMAND import in both languages, so a + /// project with its own `Ios` and a wildcard import of ours writes its own + /// -- and reading that as ours hid the editor for a hint the processor never + /// emits. A NAMED import still wins, since it is the more specific statement + /// and a file may not both import a name and declare it. + static boolean importsAnnotation(String source, String simple, boolean kotlin, + boolean shadowed) { String pkg = "com.codename1.annotations.buildhints."; boolean ours = false; for (Imported imported : importsIn(source, kotlin)) { @@ -2810,8 +2822,11 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin) { // simple spelling nor shadows it. continue; } - if (imported.name.equals(pkg + simple) || imported.name.equals(pkg + "*")) { - ours = true; + if (imported.name.equals(pkg + simple)) { + return true; + } + if (imported.name.equals(pkg + "*")) { + ours = !shadowed; } else if (imported.name.endsWith("." + simple)) { return false; } @@ -2819,6 +2834,58 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin) { return ours; } + /// Whether a top-level type named `simple` is declared in `text`. + /// + /// Wider than the main-class lookup on purpose: an annotation is declared + /// with `annotation class` in Kotlin and `@interface` in Java, and any of + /// those shadows an on-demand import of the same name. + static boolean declaresTypeNamed(String text, String simple, boolean kotlin) { + int depth = 0; + int i = 0; + while (i < text.length()) { + char c = text.charAt(i); + if (c == '"' || c == '\'' || c == '/' || c == '`') { + int skipped = skipNonCode(text, i, kotlin); + if (skipped > i) { + i = skipped; + continue; + } + } + if (c == '{') { + depth++; + i++; + continue; + } + if (c == '}') { + depth--; + i++; + continue; + } + if (depth != 0 || !continuesAName(c) + || (i > 0 && continuesAName(text.charAt(i - 1)))) { + i++; + continue; + } + int wordEnd = i; + while (wordEnd < text.length() && continuesAName(text.charAt(wordEnd))) { + wordEnd++; + } + String word = text.substring(i, wordEnd); + if ("class".equals(word) || "object".equals(word) || "interface".equals(word) + || "enum".equals(word) || "record".equals(word)) { + int n = nextLiveChar(text, wordEnd, kotlin); + if (n >= 0) { + int end = componentEnd(text, n, kotlin); + if (end > n && componentText(text, n, end, kotlin).equals(simple)) { + return true; + } + } + } + i = wordEnd; + } + return false; + } + /// The name a Kotlin `typealias Alias = Ios` gives an annotation, or null. /// @@ -3222,6 +3289,21 @@ static void collectAnnotationOwnedHints(String source, java.util.Map declaredAliases = kotlin ? visibleTypeAliases(source, otherSources) : new java.util.ArrayList(); + // The sources whose top-level types could shadow an on-demand import: + // this file, and the rest of its package. Limited to what has been read + // -- the sweep exists for typealiases and so is Kotlin-only -- which + // costs a false "owned" for a same-named type declared in a file nobody + // opened, exactly as before this check existed. + java.util.List samePackage = new java.util.ArrayList<>(); + samePackage.add(source); + if (otherSources != null) { + String mainPkg = declaredPackageIn(source, kotlin); + for (String other : otherSources) { + if (other != null && declaredPackageIn(other, kotlin).equals(mainPkg)) { + samePackage.add(other); + } + } + } for (com.codename1.build.shared.BuildHints.Hint h : com.codename1.build.shared.BuildHints.entries()) { if (!h.isAnnotated()) { continue; @@ -3246,7 +3328,14 @@ static void collectAnnotationOwnedHints(String source, java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, false, + java.util.Collections.singletonList(ownAnnotation)); + assertNull(owned.get("ios.teamId")); + + // Declared in the main file itself, which is the same rule one step in. + String declaresItHere = "package com.example;\n" + + "import com.codename1.annotations.buildhints.*;\n" + + "@interface Ios { String teamId(); }\n" + + "@Ios(teamId = \"X\")\n" + + "class MyApp {}\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(declaresItHere, owned, false); + assertNull(owned.get("ios.teamId")); + + // With no such type the wildcard import is ours, as before. + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, false); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // A NAMED import is the more specific statement and still wins. + String named = "package com.example;\n" + + "import com.codename1.annotations.buildhints.Ios;\n" + + "@Ios(teamId = \"X\")\n" + + "public class MyApp {}\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(named, owned, false, + java.util.Collections.singletonList(ownAnnotation)); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // Kotlin declares an annotation with `annotation class`. + String kotlinOwn = "package com.example\n" + + "annotation class Ios(val teamId: String)\n"; + String kotlinMain = "package com.example\n" + + "import com.codename1.annotations.buildhints.*\n" + + "@Ios(teamId = \"X\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(kotlinMain, owned, true, + java.util.Collections.singletonList(kotlinOwn)); + assertNull(owned.get("ios.teamId")); + } } From 59b78ee1f9ea2661f1f5ae2cfc1a931baa514c44 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 05:09:22 +0300 Subject: [PATCH 091/115] Read the Java peers too, not only the Kotlin ones The same-package rule that beats an on-demand import is a Java rule as much as a Kotlin one -- a `p/Ios.java` beside the main class is what `@Ios` means there, whatever the wildcard import says -- but the sweep that supplies the peers was passed only for a Kotlin main class, because it had been written for typealiases. So the shadow check saw the main file alone in every Java project, which is most of them. The sweep now reads `.java` as well as `.kt` and is passed for both, with the same bounds on files read and directories walked. A package declaration reads the same in either language, so a mixed project's peers are classified correctly whichever the main class is written in. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 39 +++++++++++-------- .../settings/BuildHintCatalogTest.java | 19 +++++++++ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 6995597ed98..b712ff61cf6 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2320,8 +2320,7 @@ private java.util.Map annotationOwnedHintsFromSource() { continue; } collectAnnotationOwnedHints(text, out, ext.equals(".kt"), - ext.equals(".kt") - ? otherKotlinSources(binding.projectDir(), path) : null); + otherProjectSources(binding.projectDir(), path)); return out; } } @@ -2334,9 +2333,7 @@ private java.util.Map annotationOwnedHintsFromSource() { String found = findMainClassSource(binding.projectDir(), main, pkg); if (found != null) { collectAnnotationOwnedHints(found, out, lastSourceWasKotlin, - lastSourceWasKotlin - ? otherKotlinSources(binding.projectDir(), lastSourcePath) - : null); + otherProjectSources(binding.projectDir(), lastSourcePath)); return out; } // Genuinely no source. Distinct from "found and declares nothing", and @@ -2352,14 +2349,20 @@ private java.util.Map annotationOwnedHintsFromSource() { /// declarations made elsewhere. private String lastSourcePath; - /// The text of every OTHER Kotlin source in the project, bounded. + /// The text of every OTHER source in the project, bounded. /// - /// Only for declarations that are not file-scoped -- a `typealias` naming one - /// of our annotations. Bounded in files read and in directories walked, - /// because this runs when the tool opens a project and a source tree is not - /// a search index; a project past the bound simply keeps the previous - /// behaviour for an alias declared in a file nobody reached. - private java.util.List otherKotlinSources(String projectDir, String exclude) { + /// For the declarations that are not file-scoped and so can decide what a + /// name in the main source means: a `typealias` naming one of our + /// annotations, and a type whose name shadows an on-demand import of one. + /// Java as well as Kotlin, because the second of those is a Java rule too -- + /// a `p/Ios.java` beside the main class is what `@Ios` means there, + /// whatever the wildcard import says. + /// + /// Bounded in files read and in directories walked, because this runs when + /// the tool opens a project and a source tree is not a search index; a + /// project past the bound simply keeps the earlier behaviour for whatever + /// was declared in a file nobody reached. + private java.util.List otherProjectSources(String projectDir, String exclude) { java.util.List out = new java.util.ArrayList<>(); if (projectDir == null) { return out; @@ -2390,7 +2393,10 @@ private java.util.List otherKotlinSources(String projectDir, String excl } continue; } - if (!name.endsWith(".kt") || path.equals(exclude) || out.size() >= 200) { + if (!name.endsWith(".kt") && !name.endsWith(".java")) { + continue; + } + if (path.equals(exclude) || out.size() >= 200) { continue; } String text = readIfPresent(path); @@ -3290,10 +3296,9 @@ static void collectAnnotationOwnedHints(String source, java.util.Map(); // The sources whose top-level types could shadow an on-demand import: - // this file, and the rest of its package. Limited to what has been read - // -- the sweep exists for typealiases and so is Kotlin-only -- which - // costs a false "owned" for a same-named type declared in a file nobody - // opened, exactly as before this check existed. + // this file, and the rest of its package. A package declaration reads + // the same in both languages, so a mixed project's Java peers are + // classified correctly even when the main class is Kotlin. java.util.List samePackage = new java.util.ArrayList<>(); samePackage.add(source); if (otherSources != null) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 96e9ddbb608..f22f8703cbd 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1443,6 +1443,25 @@ public void aSamePackageTypeBeatsAWildcardImport() { java.util.Collections.singletonList(ownAnnotation)); assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + // A Kotlin main class with a Java peer, which a mixed project has: the + // package declaration reads the same in both languages. + String javaPeer = "package com.example;\n@interface Ios { String teamId(); }\n"; + String kotlinMainWithJavaPeer = "package com.example\n" + + "import com.codename1.annotations.buildhints.*\n" + + "@Ios(teamId = \"X\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(kotlinMainWithJavaPeer, owned, true, + java.util.Collections.singletonList(javaPeer)); + assertNull(owned.get("ios.teamId")); + + // A peer in ANOTHER package shadows nothing. + String elsewherePeer = "package com.other;\n@interface Ios { String teamId(); }\n"; + owned.clear(); + CodenameOneSettings.collectAnnotationOwnedHints(main, owned, false, + java.util.Collections.singletonList(elsewherePeer)); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + // Kotlin declares an annotation with `annotation class`. String kotlinOwn = "package com.example\n" + "annotation class Ios(val teamId: String)\n"; From 61a944b2ce7de22f005d119dde39abba2211e398 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 05:20:57 +0300 Subject: [PATCH 092/115] Give each peer its own language, and leave the test tree out Two consequences of reading the project's other sources, both mine from the commit before. A peer is parsed with the language it is written in now, not the main file's. The two genuinely disagree -- a block comment nests in Kotlin and does not in Java, raw strings close differently, and Java translates unicode escapes before it tokenizes -- so a Java peer read by Kotlin's rules could land in the wrong package and shadow nothing. The language travels with the text rather than being inferred at the far end, and a Java peer is decoded as it is read. And a test tree takes no part in compiling the main class, so a same-package type there shadows nothing. Counting it made a production `@Ios` look like somebody else's, so the hint read as unowned and Add wrote the duplicate declaration the next build refuses. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 102 ++++++++++++++---- .../settings/BuildHintCatalogTest.java | 24 ++++- 2 files changed, 98 insertions(+), 28 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index b712ff61cf6..c1868fc713d 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2319,7 +2319,7 @@ private java.util.Map annotationOwnedHintsFromSource() { if (text == null || !declaresClass(text, main, pkg, ext.equals(".kt"))) { continue; } - collectAnnotationOwnedHints(text, out, ext.equals(".kt"), + collectOwnedHints(text, out, ext.equals(".kt"), otherProjectSources(binding.projectDir(), path)); return out; } @@ -2332,7 +2332,7 @@ private java.util.Map annotationOwnedHintsFromSource() { // properly before giving up. String found = findMainClassSource(binding.projectDir(), main, pkg); if (found != null) { - collectAnnotationOwnedHints(found, out, lastSourceWasKotlin, + collectOwnedHints(found, out, lastSourceWasKotlin, otherProjectSources(binding.projectDir(), lastSourcePath)); return out; } @@ -2362,8 +2362,8 @@ private java.util.Map annotationOwnedHintsFromSource() { /// the tool opens a project and a source tree is not a search index; a /// project past the bound simply keeps the earlier behaviour for whatever /// was declared in a file nobody reached. - private java.util.List otherProjectSources(String projectDir, String exclude) { - java.util.List out = new java.util.ArrayList<>(); + private java.util.List otherProjectSources(String projectDir, String exclude) { + java.util.List out = new java.util.ArrayList<>(); if (projectDir == null) { return out; } @@ -2388,7 +2388,13 @@ private java.util.List otherProjectSources(String projectDir, String exc // holds copies, unless `build` is a package name under src/. boolean output = ("target".equals(name) || "build".equals(name)) && !insideSourceTree(dir); - if (!output && !name.startsWith(".")) { + // A test tree does not take part in compiling the main + // class, so a same-package type there shadows nothing -- + // counting it made a production `@Ios` look like somebody + // else's, so the hint read as unowned and Add wrote the + // duplicate the next build refuses. + boolean tests = "test".equals(name) && dir.endsWith("/src"); + if (!output && !tests && !name.startsWith(".")) { queue.add(path); } continue; @@ -2401,7 +2407,13 @@ private java.util.List otherProjectSources(String projectDir, String exc } String text = readIfPresent(path); if (text != null) { - out.add(text); + boolean kotlinPeer = name.endsWith(".kt"); + // Java's escapes are translated before anything is + // tokenized, so a peer declaring an escaped package name is + // in the package it decodes to -- and reading it literally + // put it in another one, where it shadows nothing. + out.add(new PeerSource( + kotlinPeer ? text : decodeUnicodeEscapes(text), kotlinPeer)); } } } @@ -3019,6 +3031,37 @@ private static boolean declaredPrivate(String source, int at) { return false; } + /// Another source file, with the language it is written in. + /// + /// The language travels with the text because it changes what the text + /// MEANS: raw strings close differently, block comments nest in one and not + /// the other, and Java translates unicode escapes before it tokenizes -- so + /// a Java peer declaring `package \u0070;` is in package p, which a + /// Kotlin-mode read cannot see. + static final class PeerSource { + final String text; + final boolean kotlin; + + PeerSource(String text, boolean kotlin) { + this.text = text; + this.kotlin = kotlin; + } + } + + /// Peers written in one language, which is what a caller that has plain + /// texts means by them. + static java.util.List peers(java.util.List texts, boolean kotlin) { + java.util.List out = new java.util.ArrayList<>(); + if (texts != null) { + for (String text : texts) { + if (text != null) { + out.add(new PeerSource(text, kotlin)); + } + } + } + return out; + } + /// Every `typealias` `mainSource` can see, with the name it sees it under. /// /// Visibility is per SYMBOL, not per package: `import com.other.Unrelated` @@ -3030,26 +3073,33 @@ private static boolean declaredPrivate(String source, int at) { /// annotation went unrecognised and Add wrote the duplicate. static java.util.List visibleTypeAliases(String mainSource, java.util.List others) { + return visibleTypeAliases(mainSource, peers(others, true), true); + } + + static java.util.List visibleTypeAliases(String mainSource, + java.util.List others, + boolean kotlin) { java.util.List out = new java.util.ArrayList<>(); if (mainSource == null) { return out; } - String mainPkg = declaredPackageIn(mainSource, true); - for (String[] declared : typeAliasDeclarations(mainSource, true)) { + String mainPkg = declaredPackageIn(mainSource, kotlin); + for (String[] declared : typeAliasDeclarations(mainSource, kotlin)) { out.add(new AliasDeclaration(declared[0], declared[0], declared[1], mainPkg, mainSource)); } if (others == null) { return out; } - java.util.List imports = importsIn(mainSource, true); - for (String other : others) { + java.util.List imports = importsIn(mainSource, kotlin); + for (PeerSource peer : others) { + String other = peer == null ? null : peer.text; if (other == null) { continue; } - String pkg = declaredPackageIn(other, true); + String pkg = declaredPackageIn(other, peer.kotlin); boolean samePackage = pkg.equals(mainPkg); - for (String[] declared : typeAliasDeclarations(other, true)) { + for (String[] declared : typeAliasDeclarations(other, peer.kotlin)) { if ("private".equals(declared[2])) { // On a top-level Kotlin declaration `private` means this FILE // only, not this package -- so another file's is not a name @@ -3290,22 +3340,28 @@ static void collectAnnotationOwnedHints(String source, java.util.Map out, boolean kotlin, java.util.List otherSources) { + collectOwnedHints(source, out, kotlin, peers(otherSources, kotlin)); + } + + static void collectOwnedHints(String source, java.util.Map out, + boolean kotlin, java.util.List otherSources) { // Once, not once per hint: which aliases exist and what the main file // calls them does not depend on which hint is being asked about. java.util.List declaredAliases = - kotlin ? visibleTypeAliases(source, otherSources) + kotlin ? visibleTypeAliases(source, otherSources, kotlin) : new java.util.ArrayList(); // The sources whose top-level types could shadow an on-demand import: - // this file, and the rest of its package. A package declaration reads - // the same in both languages, so a mixed project's Java peers are - // classified correctly even when the main class is Kotlin. - java.util.List samePackage = new java.util.ArrayList<>(); - samePackage.add(source); + // this file, and the rest of its package. Each is read in the language + // it is written in, since that decides what its text means -- a Java + // peer's unicode escapes among other things. + java.util.List samePackage = new java.util.ArrayList<>(); + samePackage.add(new PeerSource(source, kotlin)); if (otherSources != null) { String mainPkg = declaredPackageIn(source, kotlin); - for (String other : otherSources) { - if (other != null && declaredPackageIn(other, kotlin).equals(mainPkg)) { - samePackage.add(other); + for (PeerSource peer : otherSources) { + if (peer != null && peer.text != null + && declaredPackageIn(peer.text, peer.kotlin).equals(mainPkg)) { + samePackage.add(peer); } } } @@ -3334,8 +3390,8 @@ static void collectAnnotationOwnedHints(String source, java.util.Map Date: Tue, 25 Aug 2026 05:31:20 +0300 Subject: [PATCH 093/115] Walk generated sources, and make the walk's rules testable `target/generated-sources` is a compile root that Maven plugins add, so a declaration there is one the compiler sees -- and the blanket exclusion of `target` as an output directory skipped it. A same-package alias or type generated into it went unread, so the hint looked unowned and Add wrote the duplicate the next build refuses. Recognised by name rather than by reading the effective model, which is a large amount of machinery for one conventional directory. The three rules the walk applies -- output directories hold copies, unless `build` is a package name under a source tree, and a `src/test` tree takes no part in compiling the main class -- moved out of the loop into predicates that take strings. Each was a bug first and none of them was covered, because the walk needs a project directory and the Codename One FileSystemStorage; now only the listing loop itself is untested. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 56 +++++++++++++++---- .../settings/BuildHintCatalogTest.java | 37 ++++++++++++ 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index c1868fc713d..ab63d345182 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2349,6 +2349,44 @@ private java.util.Map annotationOwnedHintsFromSource() { /// declarations made elsewhere. private String lastSourcePath; + /// Whether the walk should descend into `dir`/`name` looking for sources + /// that take part in compiling the main class. + /// + /// Three rules, and each of them was a bug first: + /// + /// - An output directory holds COPIES of the sources read elsewhere, and + /// walking one found a generated stub and called it the main class. + /// - Unless `build` is a package NAME under a source tree, which this + /// repository has -- refusing to descend there meant a main class living + /// in it could not be read at all. + /// - A `src/test` tree takes no part in compiling the main class, so a + /// same-package type there shadows nothing; counting it made a production + /// annotation look like somebody else's. + static boolean peerDirectoryHoldsSources(String dir, String name, boolean insideSourceTree) { + if (name.startsWith(".")) { + return false; + } + if (("target".equals(name) || "build".equals(name)) && !insideSourceTree) { + return false; + } + return !("test".equals(name) && dir.endsWith("/src")); + } + + /// The generated source root under the output directory `dir`/`name`, or + /// null when that is not what this directory is. + /// + /// Everything else under an output directory is a copy, but + /// `generated-sources` is not: Maven plugins add it as a compile root, so a + /// declaration there is one the compiler sees. Named rather than read out of + /// the effective model, which is a large amount of machinery for one + /// conventional directory. + static String generatedSourceRootUnder(String dir, String name) { + if (!"target".equals(name) && !"build".equals(name)) { + return null; + } + return dir + "/" + name + "/generated-sources"; + } + /// The text of every OTHER source in the project, bounded. /// /// For the declarations that are not file-scoped and so can decide what a @@ -2384,18 +2422,14 @@ private java.util.List otherProjectSources(String projectDir, String String name = child.endsWith("/") ? child.substring(0, child.length() - 1) : child; String path = dir + "/" + name; if (FileSystemStorage.getInstance().isDirectory(ProjectIO.fsUrl(path))) { - // Same rule as the main-class search: an output directory - // holds copies, unless `build` is a package name under src/. - boolean output = ("target".equals(name) || "build".equals(name)) - && !insideSourceTree(dir); - // A test tree does not take part in compiling the main - // class, so a same-package type there shadows nothing -- - // counting it made a production `@Ios` look like somebody - // else's, so the hint read as unowned and Add wrote the - // duplicate the next build refuses. - boolean tests = "test".equals(name) && dir.endsWith("/src"); - if (!output && !tests && !name.startsWith(".")) { + if (peerDirectoryHoldsSources(dir, name, insideSourceTree(dir))) { queue.add(path); + } else { + String generated = generatedSourceRootUnder(dir, name); + if (generated != null && FileSystemStorage.getInstance() + .isDirectory(ProjectIO.fsUrl(generated))) { + queue.add(generated); + } } continue; } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index de3c4939696..b47e04a1c36 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1488,4 +1488,41 @@ public void aSamePackageTypeBeatsAWildcardImport() { java.util.Collections.singletonList(kotlinOwn)); assertNull(owned.get("ios.teamId")); } + + /// Which directories the peer sweep walks. Each of these rules was a bug + /// first, and until now they lived inside the walk itself -- which needs a + /// project directory and the Codename One FileSystemStorage, so none of them + /// were covered. + @Test + public void thePeerSweepWalksTheSourceTreesOnly() { + // Ordinary source directories. + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common", "src", false)); + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "main", false)); + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src/main", "kotlin", + false)); + + // Output directories hold copies of those same sources. + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common", "target", false)); + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common", "build", false)); + + // ...unless `build` is a package name, which this repository has. + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( + "/p/common/src/main/java/com/codename1", "build", true)); + + // A test tree takes no part in compiling the main class. + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "test", false)); + // ...but a directory called test deeper in a package is a package. + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( + "/p/common/src/main/java/com", "test", false)); + + // Hidden directories are not source trees. + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p", ".git", false)); + + // Generated sources under an output directory ARE a compile root. + assertEquals("/p/common/target/generated-sources", + CodenameOneSettings.generatedSourceRootUnder("/p/common", "target")); + assertEquals("/p/common/build/generated-sources", + CodenameOneSettings.generatedSourceRootUnder("/p/common", "build")); + assertNull(CodenameOneSettings.generatedSourceRootUnder("/p/common/src", "test")); + } } From 9eb6090ecf88d5c89e095cc2295d2b93e40917bf Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 05:44:58 +0300 Subject: [PATCH 094/115] Read a source in its own encoding, and let a private peer type be private The compiler's source encoding is a project setting the orphan filter cannot see, and it decoded everything as UTF-8 -- so an ISO-8859-1 source declaring a non-ASCII package came back as replacement characters, never matched, and a live annotated class was dropped with its placement error lost. The file is now read as UTF-8 when it decodes as UTF-8 and as ISO-8859-1 when it does not, which reads a single-byte source correctly instead of mangling it. What neither reading can settle -- a non-ASCII name in some third encoding -- is inconclusive rather than an orphan, because everywhere else in this walk an unanswerable question keeps the class. And in Settings, a peer's `private annotation class Ios` is visible in that file only, so it shadows nothing; counting it made a real `@Ios` read as somebody else's, so the hint looked unowned and Add wrote the duplicate the next build refuses. Same rule the type aliases already had. The modifier walk learned the class modifiers while doing it -- it stopped at `annotation` in `private annotation class` and reported the declaration public. Co-Authored-By: Claude Opus 5 (1M context) --- .../BuildHintAnnotationProcessor.java | 71 +++++++++++++++++-- .../BuildHintAnnotationProcessorTest.java | 41 +++++++++++ .../settings/CodenameOneSettings.java | 47 ++++++++++-- .../settings/BuildHintCatalogTest.java | 33 +++++++++ 4 files changed, 181 insertions(+), 11 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java index c3e94bcb3f2..22f15e95ee4 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/processors/BuildHintAnnotationProcessor.java @@ -371,8 +371,14 @@ private static boolean matches(File f, String pkg, String simple, String[] neste if (!kotlin) { text = decodeUnicodeEscapes(text); } - if (!pkg.equals(declaredPackageIn(text, kotlin))) { - return false; + String declaredPkg = declaredPackageIn(text, kotlin); + if (!pkg.equals(declaredPkg)) { + // A name outside ASCII cannot be judged without the compiler's + // source encoding, which this scan cannot see -- so a mismatch + // involving one is "cannot tell", not "somewhere else". Concluding + // otherwise dropped a live annotated class and lost its placement + // error, which is the failure this whole guard exists to prevent. + return !isAscii(pkg) || !isAscii(declaredPkg); } // The name as spelled, before it is read as a nesting path: `$` is legal // in a Java type name, so a top-level `class Wrong$Type` really is @@ -381,7 +387,8 @@ private static boolean matches(File f, String pkg, String simple, String[] neste return true; } if (!declaresType(text, simple, kotlin)) { - return false; + // Same reasoning as the package: an unreadable name is unjudgeable. + return !isAscii(simple) || !isAscii(text); } // The whole nesting PATH has to be there, in order. Checking only the // innermost name let an unrelated Main.B.Wrong vouch for a deleted @@ -391,6 +398,17 @@ private static boolean matches(File f, String pkg, String simple, String[] neste return nested == null || declaresNestedPath(text, nested, kotlin); } + /// Whether every character of `s` is ASCII, which is where this scan can + /// read a source without knowing its encoding. + private static boolean isAscii(String s) { + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) > 0x7F) { + return false; + } + } + return true; + } + /// Whether `text` declares the chain `path` -- {"Main", "A", "Wrong"} -- each /// inside the body of the one before it. /// @@ -1218,13 +1236,56 @@ private static int endOfKotlinRawString(char[] c, int i) { /// a budget: 4MB is far past any hand-written Java or Kotlin file, and /// exceeding it returns null, which the caller reads as "cannot tell" and so /// keeps the class. - private static String readHead(File f) { + /// The charset to read `f` as: UTF-8 when it decodes as UTF-8, ISO-8859-1 + /// otherwise. + /// + /// The compiler's source encoding is a project setting this scan cannot see. + /// Guessing it from the bytes is not exact, but decoding everything as UTF-8 + /// produced replacement characters for a source that was never UTF-8, and a + /// name that cannot be read never matches. + private static java.nio.charset.Charset decoderFor(File f) { + try { + byte[] bytes = new byte[(int) Math.min(f.length(), 64L * 1024)]; + InputStream in = new FileInputStream(f); + try { + int read = 0; + while (read < bytes.length) { + int n = in.read(bytes, read, bytes.length - read); + if (n < 0) { + break; + } + read += n; + } + java.nio.charset.CharsetDecoder decoder = + java.nio.charset.Charset.forName("UTF-8").newDecoder(); + decoder.onMalformedInput(java.nio.charset.CodingErrorAction.REPORT); + decoder.onUnmappableCharacter(java.nio.charset.CodingErrorAction.REPORT); + decoder.decode(java.nio.ByteBuffer.wrap(bytes, 0, read)); + return java.nio.charset.Charset.forName("UTF-8"); + } finally { + in.close(); + } + } catch (java.nio.charset.CharacterCodingException notUtf8) { + return java.nio.charset.Charset.forName("ISO-8859-1"); + } catch (IOException ex) { + return java.nio.charset.Charset.forName("UTF-8"); + } + } + + static String readHead(File f) { if (f.length() > 4L * 1024 * 1024) { return null; } BufferedReader r = null; try { - r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); + // UTF-8 where the file is UTF-8, which is the overwhelming case and + // what a name from the class file can be compared against directly. + // Where it is not, ISO-8859-1: it never fails to decode, so a source + // in a single-byte encoding is read correctly rather than turned + // into replacement characters. What neither reading can settle -- a + // non-ASCII name in some third encoding -- is left inconclusive by + // the caller rather than called an orphan. + r = new BufferedReader(new InputStreamReader(new FileInputStream(f), decoderFor(f))); StringBuilder sb = new StringBuilder(); String line; while ((line = r.readLine()) != null) { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java index 025b7116322..bf7e6b854f1 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/processors/BuildHintAnnotationProcessorTest.java @@ -745,6 +745,47 @@ public void aStringInsideAKotlinTemplateIsStillAString() { assertFalse(BuildHintAnnotationProcessor.declaresType(raw, "Fake", true)); } + /// The compiler's source encoding is a project setting this scan cannot + /// see, and decoding an ISO-8859-1 source as UTF-8 produced replacement + /// characters -- so a name with a non-ASCII character never matched, the + /// class read as an orphan, and its misplaced annotation went unreported. + @Test + public void aSourceEncodingThatIsNotUtf8StillMatches() throws Exception { + String pkg = "com.caf\u00e9"; + for (String charset : new String[] {"UTF-8", "ISO-8859-1"}) { + File src = tmp.newFolder(); + File dir = new File(src, "com/cafe"); + dir.mkdirs(); + java.io.OutputStream os = + new java.io.FileOutputStream(new File(dir, "Accented.java")); + try { + os.write(("package " + pkg + ";\npublic class Accented {\n}\n") + .getBytes(charset)); + } finally { + os.close(); + } + + File classes = tmp.newFolder(); + JavaSourceCompiler.compile( + JavaSourceCompiler.singleSource(pkg + ".Accented", + "package " + pkg + ";\npublic class Accented {\n}\n"), + classes, Arrays.asList(testClassesDir(), coreJar())); + AnnotatedClass cls = ClassScanner.scan(classes) + .get(pkg.replace('.', '/') + "/Accented"); + assertTrue("the class under test must have been compiled", cls != null); + + assertTrue("not matched when the source is " + charset, + BuildHintAnnotationProcessor.hasBackingSource(cls, + java.util.Collections.singletonList(src.getAbsolutePath()))); + + // Read correctly, not merely judged unreadable: the name comes back + // as it was written, whichever of the two encodings the file is in. + assertTrue("misread when the source is " + charset, + BuildHintAnnotationProcessor.readHead(new File(dir, "Accented.java")) + .contains(pkg)); + } + } + /// Running out of search budget is "cannot tell", not "no such source". /// Answering no dropped a live annotated class silently, with its placement /// error lost, for the sake of a bound -- which is the wrong way round: diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index ab63d345182..3a31dee101f 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2892,6 +2892,21 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin, /// with `annotation class` in Kotlin and `@interface` in Java, and any of /// those shadows an on-demand import of the same name. static boolean declaresTypeNamed(String text, String simple, boolean kotlin) { + return declaresTypeNamed(text, simple, kotlin, true); + } + + /// As above; `includePrivate` is false for a peer, where a file-private + /// declaration is not a name the main source can see. + /// + /// On a top-level Kotlin declaration `private` means this FILE only, so + /// another file's `private annotation class Ios` shadows nothing -- counting + /// it made a real `@Ios` read as somebody else's, so the hint looked unowned + /// and Add wrote the duplicate the next build refuses. In the main file + /// itself a private type does shadow, because that is the file it belongs + /// to. + static boolean declaresTypeNamed(String text, String simple, boolean kotlin, + boolean includePrivate) { + String modifiers = !includePrivate && kotlin ? blanked(text, kotlin) : null; int depth = 0; int i = 0; while (i < text.length()) { @@ -2928,7 +2943,8 @@ static boolean declaresTypeNamed(String text, String simple, boolean kotlin) { int n = nextLiveChar(text, wordEnd, kotlin); if (n >= 0) { int end = componentEnd(text, n, kotlin); - if (end > n && componentText(text, n, end, kotlin).equals(simple)) { + if (end > n && componentText(text, n, end, kotlin).equals(simple) + && (modifiers == null || !declaredPrivate(modifiers, i))) { return true; } } @@ -3000,6 +3016,22 @@ static final class AliasDeclaration { } } + /// Whether `word` is a modifier that may sit between `private` and the + /// keyword it belongs to. + /// + /// A class carries more of them than a typealias does -- `private + /// annotation class Ios` is the shape that matters here -- and stopping at + /// the first one not on this list is what keeps a `private` belonging to an + /// earlier declaration from being read as this one's. + private static boolean isDeclarationModifier(String word) { + return "public".equals(word) || "internal".equals(word) || "protected".equals(word) + || "actual".equals(word) || "expect".equals(word) + || "annotation".equals(word) || "data".equals(word) || "enum".equals(word) + || "sealed".equals(word) || "open".equals(word) || "abstract".equals(word) + || "final".equals(word) || "inner".equals(word) || "value".equals(word) + || "inline".equals(word) || "external".equals(word); + } + /// `source` with its comments and literals replaced by spaces, offsets and /// line breaks preserved. /// @@ -3038,7 +3070,7 @@ private static String blanked(String source, boolean kotlin) { /// before this declaration is not read as this one's. private static boolean declaredPrivate(String source, int at) { int i = at; - for (int word = 0; word < 4; word++) { + for (int word = 0; word < 8; word++) { int end = i; while (end > 0 && (source.charAt(end - 1) == ' ' || source.charAt(end - 1) == '\t' || source.charAt(end - 1) == '\n' || source.charAt(end - 1) == '\r')) { @@ -3055,9 +3087,7 @@ private static boolean declaredPrivate(String source, int at) { if ("private".equals(modifier)) { return true; } - if (!"public".equals(modifier) && !"internal".equals(modifier) - && !"protected".equals(modifier) && !"actual".equals(modifier) - && !"expect".equals(modifier)) { + if (!isDeclarationModifier(modifier)) { return false; } i = start; @@ -3424,8 +3454,13 @@ && declaredPackageIn(peer.text, peer.kotlin).equals(mainPkg)) { // processor never emits, which is indistinguishable from the tool // being broken. boolean shadowed = false; + boolean first = true; for (PeerSource peer : samePackage) { - if (declaresTypeNamed(peer.text, simple, peer.kotlin)) { + // The first entry is the main source itself, where a private + // type is in the file it belongs to and does shadow. + boolean own = first; + first = false; + if (declaresTypeNamed(peer.text, simple, peer.kotlin, own)) { shadowed = true; break; } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index b47e04a1c36..3e92a14f69b 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1469,6 +1469,39 @@ public void aSamePackageTypeBeatsAWildcardImport() { new CodenameOneSettings.PeerSource(javaPeer, true))); assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + // A file-private Kotlin type in a PEER shadows nothing either: on a + // top-level declaration `private` means that file only. + String privatePeer = "package com.example\n" + + "private annotation class Ios(val teamId: String)\n"; + String ktMain = "package com.example\n" + + "import com.codename1.annotations.buildhints.*\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectOwnedHints(ktMain, owned, true, + java.util.Collections.singletonList( + new CodenameOneSettings.PeerSource(privatePeer, true))); + assertEquals("@Ios(teamId)", owned.get("ios.teamId")); + + // Without the modifier the same peer shadows. + String sharedPeer = "package com.example\n" + + "annotation class Ios(val teamId: String)\n"; + owned.clear(); + CodenameOneSettings.collectOwnedHints(ktMain, owned, true, + java.util.Collections.singletonList( + new CodenameOneSettings.PeerSource(sharedPeer, true))); + assertNull(owned.get("ios.teamId")); + + // A private type in the MAIN file is in the file it belongs to. + String privateHere = "package com.example\n" + + "import com.codename1.annotations.buildhints.*\n" + + "private annotation class Ios(val teamId: String)\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + owned.clear(); + CodenameOneSettings.collectOwnedHints(privateHere, owned, true, null); + assertNull(owned.get("ios.teamId")); + // A peer in ANOTHER package shadows nothing. String elsewherePeer = "package com.other;\n@interface Ios { String teamId(); }\n"; owned.clear(); From b4749f38ab5d7faa7f87161e74918762802d6806 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 05:52:22 +0300 Subject: [PATCH 095/115] Read the main source in its own encoding, and look where it may be Two Settings-side twins of fixes made elsewhere in this branch. The main-source walk still skipped `target` and `build` wholesale, so a main class generated into a compile root under one of them could not be found -- its ownership then read as unknown, and Settings offered an annotation-owned hint as editable. It shares the peer sweep's rules now, which is where those rules were made testable. And every source was decoded as UTF-8. The compiler's source encoding is a project setting this tool does not have, so a single-byte source came back as replacement characters: a non-ASCII package or class name never matched codename1.packageName, the real main source was rejected, and a hint an annotation owns read as editable. UTF-8 where the file decodes as UTF-8, ISO-8859-1 where it does not, which never fails. The UTF-8 check is hand-rolled for the same reason the name predicate and the hex reader are: CharsetDecoder is outside the API subset this class compiles against. It rejects a truncated sequence, an overlong one and the surrogate range, all of which decode to a replacement character rather than failing if left to String. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 84 ++++++++++++++++--- .../settings/BuildHintCatalogTest.java | 36 ++++++++ 2 files changed, 107 insertions(+), 13 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 3a31dee101f..56d1b956c47 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2489,19 +2489,19 @@ private String findMainClassSource(String projectDir, String main, String pkg) { String name = child.endsWith("/") ? child.substring(0, child.length() - 1) : child; String path = dir + "/" + name; if (FileSystemStorage.getInstance().isDirectory(ProjectIO.fsUrl(path))) { - // target/ and build/ hold compiled copies of these same - // sources, and walking one would find a generated stub and - // call it the main class. - // - // Unless we are already inside a source tree, where `build` is - // an ordinary package name -- this repository has - // com.codename1.build.shared -- and refusing to descend meant - // a main class living there could not be read at all. An - // output directory is never nested under src/. - boolean output = ("target".equals(name) || "build".equals(name)) - && !insideSourceTree(dir); - if (!output && !name.startsWith(".")) { + // The same rules as the peer sweep, and for the same reason: + // a main class generated into target/generated-sources is a + // class the compiler sees, so skipping the whole of target + // left its ownership unknown -- and Settings then offered an + // annotation-owned hint as editable. + if (peerDirectoryHoldsSources(dir, name, insideSourceTree(dir))) { queue.add(path); + } else { + String generated = generatedSourceRootUnder(dir, name); + if (generated != null && FileSystemStorage.getInstance() + .isDirectory(ProjectIO.fsUrl(generated))) { + queue.add(generated); + } } continue; } @@ -2678,7 +2678,15 @@ private String readIfPresent(String path) { return null; } in = fs.openInputStream(url); - return Util.readToString(in, "UTF-8"); + byte[] bytes = Util.readInputStream(in); + // UTF-8 where the file is UTF-8, which is the overwhelming case; + // ISO-8859-1 where it is not, since that never fails to decode. The + // compiler's source encoding is a project setting this tool does not + // have, and decoding a single-byte source as UTF-8 produced + // replacement characters -- so a non-ASCII package or class name + // never matched codename1.packageName, the real main source was + // rejected, and a hint an annotation owns read as editable. + return new String(bytes, isValidUtf8(bytes) ? "UTF-8" : "ISO-8859-1"); } catch (Exception ex) { Log.e(ex); return null; @@ -3032,6 +3040,56 @@ private static boolean isDeclarationModifier(String word) { || "inline".equals(word) || "external".equals(word); } + /// Whether `bytes` decode as UTF-8. + /// + /// Hand-rolled because CharsetDecoder is outside the Codename One API + /// subset this class compiles against, the same reason the name predicate + /// and the hex reader are. + static boolean isValidUtf8(byte[] bytes) { + int i = 0; + while (i < bytes.length) { + int b = bytes[i] & 0xFF; + int following; + int lowest; + int payload; + if (b < 0x80) { + i++; + continue; + } else if (b >= 0xC2 && b <= 0xDF) { + following = 1; + lowest = 0x80; + payload = 0x1F; + } else if (b >= 0xE0 && b <= 0xEF) { + following = 2; + lowest = 0x800; + payload = 0x0F; + } else if (b >= 0xF0 && b <= 0xF4) { + following = 3; + lowest = 0x10000; + payload = 0x07; + } else { + return false; + } + if (i + following >= bytes.length) { + return false; + } + int value = b & payload; + for (int n = 1; n <= following; n++) { + int next = bytes[i + n] & 0xFF; + if (next < 0x80 || next > 0xBF) { + return false; + } + value = (value << 6) | (next & 0x3F); + } + // Overlong, and the surrogate range, which UTF-8 does not encode. + if (value < lowest || value > 0x10FFFF || (value >= 0xD800 && value <= 0xDFFF)) { + return false; + } + i += following + 1; + } + return true; + } + /// `source` with its comments and literals replaced by spaces, offsets and /// line breaks preserved. /// diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 3e92a14f69b..d8bf9293114 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1558,4 +1558,40 @@ public void thePeerSweepWalksTheSourceTreesOnly() { CodenameOneSettings.generatedSourceRootUnder("/p/common", "build")); assertNull(CodenameOneSettings.generatedSourceRootUnder("/p/common/src", "test")); } + + /// The compiler's source encoding is a project setting this tool does not + /// have, and decoding a single-byte source as UTF-8 produced replacement + /// characters -- so a non-ASCII package or class name never matched + /// `codename1.packageName` and the real main source was rejected. + @Test + public void aSourceIsReadInTheEncodingItIsWrittenIn() throws Exception { + String text = "package com.caf\u00e9;\npublic class MyApp {}\n"; + + // A UTF-8 source decodes as UTF-8... + byte[] utf8 = text.getBytes("UTF-8"); + assertTrue(CodenameOneSettings.isValidUtf8(utf8)); + assertTrue(CodenameOneSettings.declaresClass( + new String(utf8, "UTF-8"), "MyApp", "com.caf\u00e9")); + + // ...and a single-byte one does not, so it is read as ISO-8859-1, which + // is what it is. + byte[] latin1 = text.getBytes("ISO-8859-1"); + assertFalse(CodenameOneSettings.isValidUtf8(latin1)); + assertTrue(CodenameOneSettings.declaresClass( + new String(latin1, "ISO-8859-1"), "MyApp", "com.caf\u00e9")); + // Read as UTF-8 instead it is mojibake, which is the bug. + assertFalse(CodenameOneSettings.declaresClass( + new String(latin1, "UTF-8"), "MyApp", "com.caf\u00e9")); + + // Plain ASCII decodes either way, so nothing about the common case moves. + assertTrue(CodenameOneSettings.isValidUtf8("package com.example;\n".getBytes("UTF-8"))); + + // The shapes the validity check exists to reject. + assertFalse(CodenameOneSettings.isValidUtf8(new byte[] {(byte) 0xC3})); + assertFalse(CodenameOneSettings.isValidUtf8(new byte[] {(byte) 0xC0, (byte) 0xAF})); + assertFalse(CodenameOneSettings.isValidUtf8(new byte[] {(byte) 0xED, (byte) 0xA0, + (byte) 0x80})); + assertTrue(CodenameOneSettings.isValidUtf8(new byte[] {(byte) 0xF0, (byte) 0x9F, + (byte) 0x98, (byte) 0x80})); + } } From 83681dc842c6b441acccc8aa73bd3feacd394d2e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 06:01:29 +0300 Subject: [PATCH 096/115] A comment is a natural line, so it does not continue Continuation does not apply to a comment: `# note \` ends at the newline and the declaration below it is an ordinary property, which is what Properties.load reads. Joining the two made the pair read as a comment, so the migrated declaration was retained while the annotation was added beside it, and the verification build failed on the duplicate the goal had just created. Both comment markers, and a real continuation on a real declaration still continues. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 20 ++++++++- .../MigrateBuildHintsPropertyParsingTest.java | 42 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index f81d189caec..76c64748d2a 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -1396,7 +1396,14 @@ static void removeMigratedLines(File settingsFile, List keys) throws IOE // declaration and have to go with it. int last = i; StringBuilder logical = new StringBuilder(withoutTerminator(lines.get(i))); - while (continues(withoutTerminator(lines.get(last))) && last + 1 < lines.size()) { + // A COMMENT is a natural line: continuation does not apply to it, so + // `# note \` ends at the newline and the declaration below it is an + // ordinary property. Joining the two made the pair read as a + // comment, so the migrated declaration was retained and the + // verification build failed on the duplicate. + boolean comment = isCommentLine(logical.toString()); + while (!comment && continues(withoutTerminator(lines.get(last))) + && last + 1 < lines.size()) { // The continuation backslash is a MARKER, not part of the value: // Properties.load drops it, so leaving it in made // `codename1.arg.ios.teamId\` + ` =ABCDE` read as an escaped `=` @@ -1412,7 +1419,7 @@ static void removeMigratedLines(File settingsFile, List keys) throws IOE // Properties.load, while leaving it in produced a key ending in `\` // that matched nothing -- so the declaration stayed and the // verification build failed on the duplicate. - if (continues(logical.toString())) { + if (!comment && continues(logical.toString())) { logical.setLength(logical.length() - 1); } String key = propertyKeyOf(logical.toString()); @@ -1484,6 +1491,15 @@ private static String withoutTerminator(String line) { return line.substring(0, end); } + /** Whether the line is a comment, which continuation does not apply to. */ + private static boolean isCommentLine(String line) { + int i = 0; + while (i < line.length() && isPropertySpace(line.charAt(i))) { + i++; + } + return i < line.length() && (line.charAt(i) == '#' || line.charAt(i) == '!'); + } + /** Whether a physical line ends in an odd number of backslashes. */ private static boolean continues(String line) { int backslashes = 0; diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index abf1c8609ef..cadc1b916a3 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -696,4 +696,46 @@ public void aContinuationMarkerAtTheEndOfTheFileIsStillAMarker() throws Exceptio java.util.Arrays.asList("codename1.arg.ios.teamId")); assertEquals("codename1.displayName=Demo\n", read(f)); } + + /// A comment is a natural line: continuation does not apply to it, so + /// `# note \` ends at the newline and the declaration below it is an + /// ordinary property. Joining the two made the pair read as a comment, so + /// the migrated declaration was retained and the verification build failed + /// on the duplicate. + @Test + public void aCommentEndingInABackslashDoesNotSwallowTheNextLine() throws Exception { + File f = File.createTempFile("cn1-settings", ".properties"); + f.deleteOnExit(); + write(f, "# note \\\n" + + "codename1.arg.ios.pods=Alamofire\n" + + "codename1.displayName=Demo\n"); + + java.util.Properties loaded = new java.util.Properties(); + java.io.InputStream in = new java.io.FileInputStream(f); + try { + loaded.load(in); + } finally { + in.close(); + } + assertEquals("Alamofire", loaded.getProperty("codename1.arg.ios.pods")); + + MigrateBuildHintsMojo.removeMigratedLines(f, + java.util.Arrays.asList("codename1.arg.ios.pods")); + assertEquals("# note \\\ncodename1.displayName=Demo\n", read(f)); + } + + /// A `!` comment is a comment too, and a real continuation still continues. + @Test + public void onlyCommentsAreExemptFromContinuation() throws Exception { + File f = File.createTempFile("cn1-settings", ".properties"); + f.deleteOnExit(); + write(f, "! note \\\n" + + "codename1.arg.ios.pods=Alamofire,\\\n" + + " SwiftyJSON\n" + + "codename1.displayName=Demo\n"); + + MigrateBuildHintsMojo.removeMigratedLines(f, + java.util.Arrays.asList("codename1.arg.ios.pods")); + assertEquals("! note \\\ncodename1.displayName=Demo\n", read(f)); + } } From 80b7e127993bac8b41707b9ca8ee729b54fd3174 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 06:16:07 +0300 Subject: [PATCH 097/115] Write imports that name our annotations, not a wildcard The migration imported the package on demand, which loses to an explicit `import com.example.Build;` already in the file and to a type in the file's own package -- so the generated `@Build` referred to theirs, the verification build failed, and an otherwise valid migration rolled back. Named imports are written instead, one per annotation used, which beats a same-package type outright. A simple name the file has already given to something else -- an explicit import of another package's type, or a type this very file declares, which cannot be imported at all -- gets the fully qualified name in the annotation and no import. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 107 +++++++++++++++++- .../MigrateBuildHintsPropertyParsingTest.java | 52 +++++++++ 2 files changed, 153 insertions(+), 6 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 76c64748d2a..af2f2b9ae73 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -871,13 +871,10 @@ static String asWrittenInSource(String name) { * any formatting, and rewriting it through a parser would reformat code the * developer did not ask to have touched.

*/ - private void insertAnnotations(File source, String annotations, String simpleName) + void insertAnnotations(File source, String annotations, String simpleName) throws IOException { String text = read(source); boolean kotlin = source.getName().endsWith(".kt"); - String importLine = kotlin - ? "import com.codename1.annotations.buildhints.*" - : "import com.codename1.annotations.buildhints.*;"; String blanked = com.codename1.maven.processors.BuildHintAnnotationProcessor .blankNonCode(text, kotlin); // A live import, not the words anywhere in the file. A comment or a @@ -890,6 +887,37 @@ private void insertAnnotations(File source, String annotations, String simpleNam + "overwritten."); } + // Named imports, one per annotation written, rather than the package on + // demand -- and the fully qualified name instead for any whose simple + // name the file has already given to something else. A wildcard import + // loses to an explicit `import com.example.Build;` and to a type in the + // file's own package, so the generated `@Build` referred to theirs, the + // verification build failed, and an otherwise valid migration rolled + // back. A named import beats a same-package type; only a type declared + // in this very file cannot be imported at all, so that one is qualified. + StringBuilder importLines = new StringBuilder(); + StringBuilder written = new StringBuilder(); + for (String line : annotations.split("\n", -1)) { + String name = annotationNameOf(line); + if (name == null) { + written.append(line).append('\n'); + continue; + } + if (simpleNameIsTaken(text, blanked, name, kotlin)) { + written.append("@").append(ANNOTATION_PACKAGE).append('.') + .append(line.substring(1)).append('\n'); + continue; + } + if (importLines.indexOf(importOf(name, kotlin)) < 0) { + importLines.append(importOf(name, kotlin)).append('\n'); + } + written.append(line).append('\n'); + } + // split(-1) keeps the trailing empty piece, which put a blank line back. + annotations = written.substring(0, Math.max(0, written.length() - 1)); + String importLine = importLines.length() == 0 ? null + : importLines.substring(0, importLines.length() - 1); + int declaration = classDeclarationIndex(text, kotlin, simpleName); if (declaration < 0) { throw new IOException("Could not find the class declaration in " + source.getName()); @@ -906,8 +934,10 @@ private void insertAnnotations(File source, String annotations, String simpleNam // there spliced the new import into the middle of the old one and the // verification build rolled a correct migration back. int at = endOfImportDeclaration(blankedHead, lastImport); - head = head.substring(0, at) + importLine + "\n" + head.substring(at); - } else { + if (importLine != null) { + head = head.substring(0, at) + importLine + "\n" + head.substring(at); + } + } else if (importLine != null) { // No existing import. Anchor after the package declaration, and when // the class is in the default package anchor above any annotation it // already carries: indexOf returns -1 there, and the old arithmetic @@ -928,6 +958,71 @@ private void insertAnnotations(File source, String annotations, String simpleNam write(source, head + annotations + tail); } + /** The package the generated annotations live in. */ + private static final String ANNOTATION_PACKAGE = "com.codename1.annotations.buildhints"; + + /** The import statement for one of them, in the right language. */ + private static String importOf(String simple, boolean kotlin) { + return "import " + ANNOTATION_PACKAGE + "." + simple + (kotlin ? "" : ";"); + } + + /// The annotation a generated line writes, or null when the line is not one. + /// + /// The text being read here is the text this goal just rendered, one + /// annotation to a line, so this recognises that shape rather than parsing + /// the language. + private static String annotationNameOf(String line) { + if (!line.startsWith("@")) { + return null; + } + int end = 1; + while (end < line.length() && Character.isJavaIdentifierPart(line.charAt(end))) { + end++; + } + return end > 1 ? line.substring(1, end) : null; + } + + /// Whether `simple` already names something else in this file: a type it + /// declares, or a type it imports from elsewhere. + /// + /// A same-package type is NOT taken, because the named import this goal + /// writes beats it. A type declared in this very file is, because importing + /// a name the compilation unit declares is an error rather than a shadowing. + private static boolean simpleNameIsTaken(String text, String blanked, String simple, + boolean kotlin) { + if (com.codename1.maven.processors.BuildHintAnnotationProcessor + .declaresType(text, simple, kotlin)) { + return true; + } + for (int at = importKeywordAt(blanked, 0); at >= 0; + at = importKeywordAt(blanked, at + "import".length())) { + int end = endOfImportDeclaration(blanked, at); + String statement = blanked.substring(at, Math.min(end, blanked.length())); + String name = com.codename1.maven.processors.BuildHintAnnotationProcessor + .qualifiedNameAt(blanked, at + "import".length()); + int dot = name.lastIndexOf('.'); + String last = dot < 0 ? name : name.substring(dot + 1); + if ("*".equals(last)) { + // On demand, which the named import this goal writes beats. + continue; + } + int as = statement.indexOf(" as "); + if (kotlin && as >= 0) { + String alias = statement.substring(as + 4).trim(); + int stop = 0; + while (stop < alias.length() + && Character.isJavaIdentifierPart(alias.charAt(stop))) { + stop++; + } + last = alias.substring(0, stop); + } + if (simple.equals(last)) { + return true; + } + } + return false; + } + /** * Index of the start of the line declaring the top-level type. * diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index cadc1b916a3..143c5f8f850 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -738,4 +738,56 @@ public void onlyCommentsAreExemptFromContinuation() throws Exception { java.util.Arrays.asList("codename1.arg.ios.pods")); assertEquals("! note \\\ncodename1.displayName=Demo\n", read(f)); } + + /// A wildcard import loses to an explicit `import com.example.Build;` and to + /// a type in the file's own package, so the generated `@Build` referred to + /// theirs and the verification build failed. Named imports are written + /// instead, and the fully qualified name for any simple name the file has + /// already given away. + @Test + public void theGeneratedAnnotationsNameOurAnnotations() throws Exception { + String migrated = migrate("package com.example;\n" + + "import com.example.other.Build;\n" + + "public class MyApp {\n}\n", + "@Build(nativeTheme = NativeThemeMode.MODERN)\n@Ios(teamId = \"X\")\n"); + + // The taken name is qualified and not imported... + assertTrue(migrated, + migrated.contains("@com.codename1.annotations.buildhints.Build(nativeTheme")); + assertFalse(migrated, + migrated.contains("import com.codename1.annotations.buildhints.Build;")); + // ...the free one is imported and written plainly... + assertTrue(migrated, migrated.contains("import com.codename1.annotations.buildhints.Ios;")); + assertTrue(migrated, migrated.contains("@Ios(teamId")); + // ...and the package is never imported on demand. + assertFalse(migrated, migrated.contains("buildhints.*")); + } + + /// A type in the file's own package is beaten by the named import, so it is + /// not a collision; a type in THIS file cannot be imported at all. + @Test + public void onlyThisFilesOwnDeclarationForcesTheQualifiedName() throws Exception { + String plain = migrate("package com.example;\npublic class MyApp {\n}\n", + "@Ios(teamId = \"X\")\n"); + assertTrue(plain, plain.contains("import com.codename1.annotations.buildhints.Ios;")); + assertTrue(plain, plain.contains("@Ios(teamId")); + + String declaresIt = migrate("package com.example;\n" + + "@interface Ios { String teamId(); }\n" + + "public class MyApp {\n}\n", + "@Ios(teamId = \"X\")\n"); + assertTrue(declaresIt, + declaresIt.contains("@com.codename1.annotations.buildhints.Ios(teamId")); + assertFalse(declaresIt, + declaresIt.contains("import com.codename1.annotations.buildhints.Ios;")); + } + + /// Runs the real insertion over a temporary file and hands back the result. + private String migrate(String source, String annotations) throws Exception { + File f = File.createTempFile("MyApp", ".java"); + f.deleteOnExit(); + write(f, source); + new MigrateBuildHintsMojo().insertAnnotations(f, annotations, "MyApp"); + return read(f); + } } From 37092171c3b120f09850e0749648506cd8bf1747 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 06:24:48 +0300 Subject: [PATCH 098/115] `test` is not the only name a test source set goes by Excluding the literal `src/test` left `src/testFixtures`, `src/integrationTest` and `src/androidTest` in the sweep, and they are source sets by the same convention -- none of them takes part in compiling the main class, so a same-package type in one shadows nothing. Counting it made a production annotation look like somebody else's, so the hint read as unowned and Add wrote the duplicate the next build refuses. Any name under `src` that says test, and only under `src`: deeper down the same word is an ordinary package name. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/settings/CodenameOneSettings.java | 10 ++++++++-- .../codename1/settings/BuildHintCatalogTest.java | 13 +++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 56d1b956c47..45a36afb050 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2359,9 +2359,15 @@ private java.util.Map annotationOwnedHintsFromSource() { /// - Unless `build` is a package NAME under a source tree, which this /// repository has -- refusing to descend there meant a main class living /// in it could not be read at all. - /// - A `src/test` tree takes no part in compiling the main class, so a + /// - A test source set takes no part in compiling the main class, so a /// same-package type there shadows nothing; counting it made a production /// annotation look like somebody else's. + /// + /// That last one is a directory under `src` whose name says test: + /// `src/test`, and equally `src/testFixtures`, `src/integrationTest` and + /// `src/androidTest`, which are source sets by the same convention. Only + /// under `src`, because deeper down the same word is an ordinary package + /// name. static boolean peerDirectoryHoldsSources(String dir, String name, boolean insideSourceTree) { if (name.startsWith(".")) { return false; @@ -2369,7 +2375,7 @@ static boolean peerDirectoryHoldsSources(String dir, String name, boolean inside if (("target".equals(name) || "build".equals(name)) && !insideSourceTree) { return false; } - return !("test".equals(name) && dir.endsWith("/src")); + return !(dir.endsWith("/src") && name.toLowerCase().indexOf("test") >= 0); } /// The generated source root under the output directory `dir`/`name`, or diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index d8bf9293114..61570a3178a 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1542,9 +1542,18 @@ public void thePeerSweepWalksTheSourceTreesOnly() { assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( "/p/common/src/main/java/com/codename1", "build", true)); - // A test tree takes no part in compiling the main class. + // A test source set takes no part in compiling the main class, and + // `test` is not the only name one goes by. assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "test", false)); - // ...but a directory called test deeper in a package is a package. + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "testFixtures", + false)); + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", + "integrationTest", false)); + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "androidTest", + false)); + // The main source set is not one of them. + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "main", false)); + // ...and deeper down the same word is an ordinary package name. assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( "/p/common/src/main/java/com", "test", false)); From 8963452631068abf97ac5572f973a44f582839d6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 06:30:44 +0300 Subject: [PATCH 099/115] Match the shape of a test source set, not the letters `src/latest` and `src/contest` are production roots, and a substring match pruned them -- so a main class living in one could not be found at all, its annotations went unread, and Settings offered an annotation-owned hint as editable. The convention is a shape: `test` itself, `test` followed by a capital as in `testFixtures`, or a name ending in `Test`/`Tests` as in `integrationTest` and `androidTest`. `testing` is none of those and stays in the sweep. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 21 ++++++++++++++++++- .../settings/BuildHintCatalogTest.java | 10 ++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 45a36afb050..33de13fab0b 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2375,7 +2375,26 @@ static boolean peerDirectoryHoldsSources(String dir, String name, boolean inside if (("target".equals(name) || "build".equals(name)) && !insideSourceTree) { return false; } - return !(dir.endsWith("/src") && name.toLowerCase().indexOf("test") >= 0); + return !(dir.endsWith("/src") && isTestSourceSet(name)); + } + + /// Whether `name` is a test source set by the usual convention. + /// + /// The convention is a shape, not a word anywhere in the name: `test` + /// itself, `test` followed by a capital as in `testFixtures`, or a name + /// ending in `Test`/`Tests` as in `integrationTest` and `androidTest`. + /// Matching the substring pruned `src/latest` and `src/contest`, which are + /// production roots, and a main class living in one could not be found at + /// all. + private static boolean isTestSourceSet(String name) { + if ("test".equals(name)) { + return true; + } + if (name.length() > 4 && name.startsWith("test") + && Character.isUpperCase(name.charAt(4))) { + return true; + } + return name.endsWith("Test") || name.endsWith("Tests"); } /// The generated source root under the output directory `dir`/`name`, or diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 61570a3178a..749317ff491 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1551,8 +1551,16 @@ public void thePeerSweepWalksTheSourceTreesOnly() { "integrationTest", false)); assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "androidTest", false)); - // The main source set is not one of them. + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "jvmTests", + false)); + // The main source set is not one of them, and neither is a production + // root that merely contains the letters. assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "main", false)); + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "latest", false)); + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "contest", + false)); + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "testing", + false)); // ...and deeper down the same word is an ordinary package name. assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( "/p/common/src/main/java/com", "test", false)); From ad4c991fd9d56f3fb32656e8ca9f1116bd8b9291 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 06:41:26 +0300 Subject: [PATCH 100/115] Look in the Kotlin roots, and read a static import as one The orphan filter is handed the roots Maven is compiling, and my comment claimed the Kotlin plugin adds its own to that list. It does not: it compiles its `` without adding them back, so in a module that configures them a Kotlin class could have a perfectly good source and still look deleted -- dropped silently, producing neither its hint nor the placement error. The plugin's configured directories are included now, and the conventional src/main/kotlin when it exists, because this list is used to decide that a source is ABSENT and a merely incomplete one must not be read as that. And Settings read `static` as the imported name, so `import static com.example.Types.Ios;` never registered as giving `Ios` away: a wildcard import of ours was trusted instead and the editor was hidden for a hint the processor never emits. The same modifier the migration goal already skips, on the reader that had not learned it. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/ProcessAnnotationsMojo.java | 90 ++++++++++++++++++- .../settings/CodenameOneSettings.java | 12 +++ .../settings/BuildHintCatalogTest.java | 35 ++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java index 88d6af4b840..f2642554406 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java @@ -109,9 +109,8 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException index, getLog(), getCN1ProjectDir(), rawProjectSettings(), mainClassBinaryName(), // The roots Maven is actually compiling, so a processor asking // whether a class still has a source is not guessing at the - // layout. Kotlin and generated-source roots are in here too, - // because build-helper and the Kotlin plugin add them. - project == null ? null : project.getCompileSourceRoots()); + // layout. + compileSourceRoots()); // start() for (Iterator it = processors.iterator(); it.hasNext(); ) { @@ -244,6 +243,91 @@ index, getLog(), getCN1ProjectDir(), rawProjectSettings(), mainClassBinaryName() } } + /** + * Every directory a source could be compiled from, not only the ones + * {@code getCompileSourceRoots} lists. + * + *

build-helper and the generated-source plugins do add their roots there, + * but the Kotlin plugin compiles its own {@code } without adding + * them back -- so in a module that configures them, a Kotlin class could + * have a perfectly good source and still look deleted. The orphan filter + * would then drop it silently and its misplaced annotation would produce + * neither its hint nor the placement error.

+ * + *

The conventional {@code src/main/kotlin} is included when it exists for + * the same reason: this list is used to decide that a source is ABSENT, and + * a list that is merely incomplete must not be read as that.

+ */ + private List compileSourceRoots() { + if (project == null) { + return null; + } + List roots = new ArrayList(); + List configured = project.getCompileSourceRoots(); + if (configured != null) { + roots.addAll(configured); + } + File basedir = project.getBasedir(); + if (basedir != null) { + File kotlin = new File(basedir, "src" + File.separator + "main" + + File.separator + "kotlin"); + if (kotlin.isDirectory() && !roots.contains(kotlin.getAbsolutePath())) { + roots.add(kotlin.getAbsolutePath()); + } + } + addKotlinSourceDirs(roots); + return roots; + } + + /** The Kotlin plugin's {@code }, wherever they are configured. */ + private void addKotlinSourceDirs(List roots) { + List plugins; + try { + plugins = project.getBuildPlugins(); + } catch (RuntimeException ex) { + return; + } + if (plugins == null) { + return; + } + for (org.apache.maven.model.Plugin plugin : plugins) { + if (!"kotlin-maven-plugin".equals(plugin.getArtifactId())) { + continue; + } + addSourceDirsFrom(plugin.getConfiguration(), roots); + if (plugin.getExecutions() == null) { + continue; + } + for (org.apache.maven.model.PluginExecution execution : plugin.getExecutions()) { + addSourceDirsFrom(execution.getConfiguration(), roots); + } + } + } + + private void addSourceDirsFrom(Object configuration, List roots) { + if (!(configuration instanceof org.codehaus.plexus.util.xml.Xpp3Dom)) { + return; + } + org.codehaus.plexus.util.xml.Xpp3Dom dirs = + ((org.codehaus.plexus.util.xml.Xpp3Dom) configuration).getChild("sourceDirs"); + if (dirs == null) { + return; + } + for (org.codehaus.plexus.util.xml.Xpp3Dom dir : dirs.getChildren()) { + String value = dir.getValue(); + if (value == null || value.trim().isEmpty()) { + continue; + } + File f = new File(value.trim()); + if (!f.isAbsolute() && project.getBasedir() != null) { + f = new File(project.getBasedir(), value.trim()); + } + if (!roots.contains(f.getAbsolutePath())) { + roots.add(f.getAbsolutePath()); + } + } + } + private static boolean intersects(Set a, Set b) { if (a == null || b == null || a.isEmpty() || b.isEmpty()) return false; if (a.size() > b.size()) { diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 33de13fab0b..e7d6057995f 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2824,6 +2824,18 @@ static java.util.List importsIn(String source, boolean kotlin) { if (i < 0) { return out; } + // Java's optional `static`, which is a modifier and not the imported + // name. Reading it as the name recorded an import called `static`, + // so `import static com.example.Types.Ios;` never registered as + // giving `Ios` away -- a wildcard import of ours was trusted instead + // and the editor was hidden for a hint the processor never emits. + if (!kotlin && source.startsWith("static", i) + && i + 6 < source.length() && !continuesAName(source.charAt(i + 6))) { + int afterStatic = nextLiveChar(source, i + 6, kotlin); + if (afterStatic >= 0) { + i = afterStatic; + } + } // Component by component, stepping over whitespace and comments // around each dot. `import com.codename1.annotations. /* x */ // buildhints.Ios;` is legal, and reading the name as one contiguous diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 749317ff491..c975ef6d388 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1611,4 +1611,39 @@ public void aSourceIsReadInTheEncodingItIsWrittenIn() throws Exception { assertTrue(CodenameOneSettings.isValidUtf8(new byte[] {(byte) 0xF0, (byte) 0x9F, (byte) 0x98, (byte) 0x80})); } + + /// `static` is a modifier, not the imported name. Reading it as the name + /// recorded an import called `static`, so + /// `import static com.example.Types.Ios;` never registered as giving `Ios` + /// away -- a wildcard import of ours was trusted instead and the editor was + /// hidden for a hint the processor never emits. + @Test + public void aSingleStaticImportTakesTheNameToo() { + String src = "package com.example;\n" + + "import static com.example.Types.Ios;\n" + + "import com.codename1.annotations.buildhints.*;\n" + + "@Ios(teamId = \"X\")\n" + + "public class MyApp {}\n"; + assertFalse(CodenameOneSettings.importsAnnotation(src, "Ios", false)); + + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(src, owned, false); + assertNull(owned.get("ios.teamId")); + + // A static import of something else leaves our wildcard alone. + String other = "package com.example;\n" + + "import static com.example.Types.OTHER;\n" + + "import com.codename1.annotations.buildhints.*;\n" + + "@Ios(teamId = \"X\")\n" + + "public class MyApp {}\n"; + assertTrue(CodenameOneSettings.importsAnnotation(other, "Ios", false)); + + // A name that merely starts with `static` is a name. + String staticky = "package com.example;\n" + + "import staticky.Ios;\n" + + "@Ios(teamId = \"X\")\n" + + "public class MyApp {}\n"; + assertFalse(CodenameOneSettings.importsAnnotation(staticky, "Ios", false)); + assertEquals("staticky.Ios", CodenameOneSettings.importsIn(staticky, false).get(0).name); + } } From 377bdf57b987727b9e401c9d43ac1cf550dfa304 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 06:51:37 +0300 Subject: [PATCH 101/115] Read `as` as a token, and leave the resources root alone `import com.example.Other as\nIos` is legal, and the collision check I added two commits ago searched for the literal `" as "` -- so it missed the alias, decided `Ios` was free, and wrote its own import beside one already giving that local name, which does not compile. The whole import is read as tokens now: the optional Java `static`, the qualified name, the `as` with any whitespace around it, and an escaped alias. And a source set's resources are not compiled, so a `.java` or `.kt` template kept in one is not a package peer -- treating it as a same-package type made a real annotation read as somebody else's. Only where the layout says resources root, `src/main/resources` and the flat `src/resources`, not a package that happens to be called that. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 79 ++++++++++++++----- .../MigrateBuildHintsPropertyParsingTest.java | 31 ++++++++ .../settings/CodenameOneSettings.java | 17 +++- .../settings/BuildHintCatalogTest.java | 10 +++ 4 files changed, 115 insertions(+), 22 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index af2f2b9ae73..4d2869e3b42 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -996,33 +996,70 @@ private static boolean simpleNameIsTaken(String text, String blanked, String sim } for (int at = importKeywordAt(blanked, 0); at >= 0; at = importKeywordAt(blanked, at + "import".length())) { - int end = endOfImportDeclaration(blanked, at); - String statement = blanked.substring(at, Math.min(end, blanked.length())); - String name = com.codename1.maven.processors.BuildHintAnnotationProcessor - .qualifiedNameAt(blanked, at + "import".length()); - int dot = name.lastIndexOf('.'); - String last = dot < 0 ? name : name.substring(dot + 1); - if ("*".equals(last)) { - // On demand, which the named import this goal writes beats. - continue; - } - int as = statement.indexOf(" as "); - if (kotlin && as >= 0) { - String alias = statement.substring(as + 4).trim(); - int stop = 0; - while (stop < alias.length() - && Character.isJavaIdentifierPart(alias.charAt(stop))) { - stop++; - } - last = alias.substring(0, stop); - } - if (simple.equals(last)) { + if (simple.equals(importedSimpleName(blanked, at, kotlin))) { return true; } } return false; } + /// The name the import at `at` makes available, or null when it makes none + /// this goal has to work around. + /// + /// The `as` alias is read as a TOKEN. Searching for the literal `" as "` + /// missed `import com.example.Other as\nIos`, which is legal, so the goal + /// wrote its own `import ...buildhints.Ios` beside it -- two imports giving + /// the same local name, which does not compile, so verification rolled back + /// an otherwise valid migration. An on-demand import is not a name at all: + /// the named import this goal writes beats it. + private static String importedSimpleName(String blanked, int at, boolean kotlin) { + int nameAt = at + "import".length(); + int probeStatic = nameAt; + while (probeStatic < blanked.length() + && Character.isWhitespace(blanked.charAt(probeStatic))) { + probeStatic++; + } + if (!kotlin && blanked.startsWith("static", probeStatic) + && probeStatic + 6 < blanked.length() + && !Character.isJavaIdentifierPart(blanked.charAt(probeStatic + 6))) { + nameAt = probeStatic + 6; + } + String name = com.codename1.maven.processors.BuildHintAnnotationProcessor + .qualifiedNameAt(blanked, nameAt); + int end = com.codename1.maven.processors.BuildHintAnnotationProcessor + .qualifiedNameEnd(blanked, nameAt); + int dot = name.lastIndexOf('.'); + String last = dot < 0 ? name : name.substring(dot + 1); + if ("*".equals(last) || last.length() == 0) { + return null; + } + if (!kotlin) { + return last; + } + int probe = end; + while (probe < blanked.length() && Character.isWhitespace(blanked.charAt(probe))) { + probe++; + } + if (!blanked.startsWith("as", probe) || probe + 2 >= blanked.length() + || Character.isJavaIdentifierPart(blanked.charAt(probe + 2))) { + return last; + } + int alias = probe + 2; + while (alias < blanked.length() && Character.isWhitespace(blanked.charAt(alias))) { + alias++; + } + int escaped = com.codename1.maven.processors.BuildHintAnnotationProcessor + .escapedIdentifierEnd(blanked, alias); + if (escaped > alias) { + return blanked.substring(alias + 1, escaped - 1); + } + int stop = alias; + while (stop < blanked.length() && Character.isJavaIdentifierPart(blanked.charAt(stop))) { + stop++; + } + return stop > alias ? blanked.substring(alias, stop) : last; + } + /** * Index of the start of the line declaring the top-level type. * diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 143c5f8f850..3fa3120b8f3 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -782,6 +782,37 @@ public void onlyThisFilesOwnDeclarationForcesTheQualifiedName() throws Exception declaresIt.contains("import com.codename1.annotations.buildhints.Ios;")); } + /// A Kotlin alias is a TOKEN, and `import com.example.Other as\nIos` is + /// legal. Searching for the literal `" as "` missed it, so the goal wrote + /// its own `import ...buildhints.Ios` beside it -- two imports giving the + /// same local name, which does not compile. + @Test + public void anAliasSpanningLinesStillTakesTheName() throws Exception { + String migrated = migrateKotlin("package com.example\n" + + "import com.example.other.Other as\n Ios\n" + + "class MyApp\n", + "@Ios(teamId = \"X\")\n"); + assertTrue(migrated, + migrated.contains("@com.codename1.annotations.buildhints.Ios(teamId")); + assertFalse(migrated, migrated.contains("import com.codename1.annotations.buildhints.Ios")); + + // An alias that takes some OTHER name leaves ours alone. + String free = migrateKotlin("package com.example\n" + + "import com.example.other.Other as\n Something\n" + + "class MyApp\n", + "@Ios(teamId = \"X\")\n"); + assertTrue(free, free.contains("import com.codename1.annotations.buildhints.Ios")); + assertTrue(free, free.contains("@Ios(teamId")); + } + + private String migrateKotlin(String source, String annotations) throws Exception { + File f = File.createTempFile("MyApp", ".kt"); + f.deleteOnExit(); + write(f, source); + new MigrateBuildHintsMojo().insertAnnotations(f, annotations, "MyApp"); + return read(f); + } + /// Runs the real insertion over a temporary file and hands back the result. private String migrate(String source, String annotations) throws Exception { File f = File.createTempFile("MyApp", ".java"); diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index e7d6057995f..7f40232e53c 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2375,7 +2375,22 @@ static boolean peerDirectoryHoldsSources(String dir, String name, boolean inside if (("target".equals(name) || "build".equals(name)) && !insideSourceTree) { return false; } - return !(dir.endsWith("/src") && isTestSourceSet(name)); + if (dir.endsWith("/src") && isTestSourceSet(name)) { + return false; + } + // A source set's resources are not compiled, so a `.java` or `.kt` + // TEMPLATE kept in one is not a peer -- treating it as a same-package + // type made a real annotation read as somebody else's. Only where the + // layout says resources root: `src/main/resources` and the flat + // `src/resources`, not a package that happens to be called that. + return !("resources".equals(name) + && (dir.endsWith("/src") || parentOf(dir).endsWith("/src"))); + } + + /// `dir` without its last segment. + private static String parentOf(String dir) { + int slash = dir.lastIndexOf('/'); + return slash < 0 ? "" : dir.substring(0, slash); } /// Whether `name` is a test source set by the usual convention. diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index c975ef6d388..79373a0280c 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1565,6 +1565,16 @@ public void thePeerSweepWalksTheSourceTreesOnly() { assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( "/p/common/src/main/java/com", "test", false)); + // A source set's resources are not compiled, so a source template kept + // in one is not a peer. + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src/main", + "resources", false)); + assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "resources", + false)); + // ...but a package that happens to be called that is a package. + assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( + "/p/common/src/main/java/com/example", "resources", false)); + // Hidden directories are not source trees. assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p", ".git", false)); From 069cfd5e606df73e2cd0f44803a6f52f7808e62c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:02:13 +0300 Subject: [PATCH 102/115] One answer to "where could this source be", for all three askers The Kotlin-root fix went into the processor mojo and left the other two readers on `getCompileSourceRoots()` alone -- the build-time fallback that reports a misplaced annotation when processing did not run, and the migration goal's search for the main class. Both decide that a source is ABSENT from that list, which is exactly what an incomplete list must not be used for: the first silently omitted the hints instead of reporting the placement, and the second refused a migration saying it could not find a main class Maven compiles perfectly well. The helper moved to AbstractCN1Mojo, which all three extend, and takes the project so the migration goal can ask about the module that owns the directory rather than the one being built. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/AbstractCN1Mojo.java | 87 +++++++++++++++++++ .../com/codename1/maven/CN1BuildMojo.java | 5 +- .../maven/MigrateBuildHintsMojo.java | 8 +- .../maven/ProcessAnnotationsMojo.java | 87 +------------------ 4 files changed, 98 insertions(+), 89 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java index b044da429cc..aacd3ff9647 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java @@ -1115,5 +1115,92 @@ protected boolean isCN1ProjectDir() { return true; } + + /** + * Every directory a source could be compiled from, not only the ones + * {@code getCompileSourceRoots} lists. + * + *

build-helper and the generated-source plugins do add their roots there, + * but the Kotlin plugin compiles its own {@code } without adding + * them back -- so in a module that configures them, a Kotlin class could + * have a perfectly good source and still look deleted. The orphan filter + * would then drop it silently and its misplaced annotation would produce + * neither its hint nor the placement error.

+ * + *

The conventional {@code src/main/kotlin} is included when it exists for + * the same reason: this list is used to decide that a source is ABSENT, and + * a list that is merely incomplete must not be read as that.

+ */ + protected static List compileSourceRoots(MavenProject project) { + if (project == null) { + return null; + } + List roots = new ArrayList(); + List configured = project.getCompileSourceRoots(); + if (configured != null) { + roots.addAll(configured); + } + File basedir = project.getBasedir(); + if (basedir != null) { + File kotlin = new File(basedir, "src" + File.separator + "main" + + File.separator + "kotlin"); + if (kotlin.isDirectory() && !roots.contains(kotlin.getAbsolutePath())) { + roots.add(kotlin.getAbsolutePath()); + } + } + addKotlinSourceDirs(project, roots); + return roots; + } + + /** The Kotlin plugin's {@code }, wherever they are configured. */ + private static void addKotlinSourceDirs(MavenProject project, List roots) { + List plugins; + try { + plugins = project.getBuildPlugins(); + } catch (RuntimeException ex) { + return; + } + if (plugins == null) { + return; + } + for (org.apache.maven.model.Plugin plugin : plugins) { + if (!"kotlin-maven-plugin".equals(plugin.getArtifactId())) { + continue; + } + addSourceDirsFrom(project, plugin.getConfiguration(), roots); + if (plugin.getExecutions() == null) { + continue; + } + for (org.apache.maven.model.PluginExecution execution : plugin.getExecutions()) { + addSourceDirsFrom(project, execution.getConfiguration(), roots); + } + } + } + + private static void addSourceDirsFrom(MavenProject project, Object configuration, + List roots) { + if (!(configuration instanceof org.codehaus.plexus.util.xml.Xpp3Dom)) { + return; + } + org.codehaus.plexus.util.xml.Xpp3Dom dirs = + ((org.codehaus.plexus.util.xml.Xpp3Dom) configuration).getChild("sourceDirs"); + if (dirs == null) { + return; + } + for (org.codehaus.plexus.util.xml.Xpp3Dom dir : dirs.getChildren()) { + String value = dir.getValue(); + if (value == null || value.trim().isEmpty()) { + continue; + } + File f = new File(value.trim()); + if (!f.isAbsolute() && project.getBasedir() != null) { + f = new File(project.getBasedir(), value.trim()); + } + if (!roots.contains(f.getAbsolutePath())) { + roots.add(f.getAbsolutePath()); + } + } + } + } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java index 8ee9912b43f..6a1b7380587 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java @@ -2978,7 +2978,10 @@ private String liveAnnotatedClass(File element, java.util.Collection des String exclude) { List roots; try { - roots = project == null ? null : project.getCompileSourceRoots(); + // The complete set, not only what getCompileSourceRoots lists: the + // Kotlin plugin compiles its own sourceDirs without adding them + // back, and this list is used to decide that a source is ABSENT. + roots = compileSourceRoots(project); } catch (RuntimeException ex) { return null; } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 4d2869e3b42..41edcae406b 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -773,10 +773,14 @@ private String findMainClassSource(File projectDir, Properties settings) { // aborted with "Could not find the source" on a project Maven compiles // perfectly well. org.apache.maven.project.MavenProject owner = moduleAt(projectDir); - if (owner == null || owner.getCompileSourceRoots() == null) { + // The complete set, since the Kotlin plugin compiles its own sourceDirs + // without adding them back -- a main class living only in one of those + // is compiled by Maven and was reported here as missing. + java.util.List moduleRoots = compileSourceRoots(owner); + if (moduleRoots == null) { return null; } - for (String root : owner.getCompileSourceRoots()) { + for (String root : moduleRoots) { File dir = new File(root); if (!dir.isDirectory()) { continue; diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java index f2642554406..b06d16d00db 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/ProcessAnnotationsMojo.java @@ -110,7 +110,7 @@ index, getLog(), getCN1ProjectDir(), rawProjectSettings(), mainClassBinaryName() // The roots Maven is actually compiling, so a processor asking // whether a class still has a source is not guessing at the // layout. - compileSourceRoots()); + compileSourceRoots(project)); // start() for (Iterator it = processors.iterator(); it.hasNext(); ) { @@ -243,91 +243,6 @@ index, getLog(), getCN1ProjectDir(), rawProjectSettings(), mainClassBinaryName() } } - /** - * Every directory a source could be compiled from, not only the ones - * {@code getCompileSourceRoots} lists. - * - *

build-helper and the generated-source plugins do add their roots there, - * but the Kotlin plugin compiles its own {@code } without adding - * them back -- so in a module that configures them, a Kotlin class could - * have a perfectly good source and still look deleted. The orphan filter - * would then drop it silently and its misplaced annotation would produce - * neither its hint nor the placement error.

- * - *

The conventional {@code src/main/kotlin} is included when it exists for - * the same reason: this list is used to decide that a source is ABSENT, and - * a list that is merely incomplete must not be read as that.

- */ - private List compileSourceRoots() { - if (project == null) { - return null; - } - List roots = new ArrayList(); - List configured = project.getCompileSourceRoots(); - if (configured != null) { - roots.addAll(configured); - } - File basedir = project.getBasedir(); - if (basedir != null) { - File kotlin = new File(basedir, "src" + File.separator + "main" - + File.separator + "kotlin"); - if (kotlin.isDirectory() && !roots.contains(kotlin.getAbsolutePath())) { - roots.add(kotlin.getAbsolutePath()); - } - } - addKotlinSourceDirs(roots); - return roots; - } - - /** The Kotlin plugin's {@code }, wherever they are configured. */ - private void addKotlinSourceDirs(List roots) { - List plugins; - try { - plugins = project.getBuildPlugins(); - } catch (RuntimeException ex) { - return; - } - if (plugins == null) { - return; - } - for (org.apache.maven.model.Plugin plugin : plugins) { - if (!"kotlin-maven-plugin".equals(plugin.getArtifactId())) { - continue; - } - addSourceDirsFrom(plugin.getConfiguration(), roots); - if (plugin.getExecutions() == null) { - continue; - } - for (org.apache.maven.model.PluginExecution execution : plugin.getExecutions()) { - addSourceDirsFrom(execution.getConfiguration(), roots); - } - } - } - - private void addSourceDirsFrom(Object configuration, List roots) { - if (!(configuration instanceof org.codehaus.plexus.util.xml.Xpp3Dom)) { - return; - } - org.codehaus.plexus.util.xml.Xpp3Dom dirs = - ((org.codehaus.plexus.util.xml.Xpp3Dom) configuration).getChild("sourceDirs"); - if (dirs == null) { - return; - } - for (org.codehaus.plexus.util.xml.Xpp3Dom dir : dirs.getChildren()) { - String value = dir.getValue(); - if (value == null || value.trim().isEmpty()) { - continue; - } - File f = new File(value.trim()); - if (!f.isAbsolute() && project.getBasedir() != null) { - f = new File(project.getBasedir(), value.trim()); - } - if (!roots.contains(f.getAbsolutePath())) { - roots.add(f.getAbsolutePath()); - } - } - } - private static boolean intersects(Set a, Set b) { if (a == null || b == null || a.isEmpty() || b.isEmpty()) return false; if (a.size() > b.size()) { From 8b6537708983668b8a657b9f975e022545bbdbe3 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:11:35 +0300 Subject: [PATCH 103/115] Only the compile goal's source dirs, and a test for the collection A `test-compile` execution's sourceDirs are src/test/kotlin and friends, and reading every execution's added them to the main roots. A deleted production class then looked like it still had a source, because a same-named test fixture does, so a stale class under target/classes kept failing the build on a misplaced annotation no production source declares. Executions are taken only when their goals include `compile`; plugin-level configuration still counts, since that applies to the main compilation too. This is the third finding against a helper I had been calling untestable because it reads MavenProject. It is not: Plugin, PluginExecution and Xpp3Dom are plain model objects, and a MavenProject with a file has a basedir. CompileSourceRootsTest builds one and covers what Maven listed, the conventional Kotlin root, plugin-level and per-execution sourceDirs, the test execution that must NOT count, a conventional root that does not exist, and no project at all. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/AbstractCN1Mojo.java | 10 ++ .../maven/CompileSourceRootsTest.java | 125 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/CompileSourceRootsTest.java diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java index aacd3ff9647..2965b2ecc51 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/AbstractCN1Mojo.java @@ -1172,6 +1172,16 @@ private static void addKotlinSourceDirs(MavenProject project, List roots continue; } for (org.apache.maven.model.PluginExecution execution : plugin.getExecutions()) { + // The `compile` goal only. A `test-compile` execution's + // sourceDirs are src/test/kotlin and friends, and adding them + // here made a deleted production class look like it still had a + // source -- because a same-named test fixture does -- so a stale + // class under target/classes kept failing the build on a + // misplaced annotation no production source declares. + List goals = execution.getGoals(); + if (goals == null || !goals.contains("compile")) { + continue; + } addSourceDirsFrom(project, execution.getConfiguration(), roots); } } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/CompileSourceRootsTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/CompileSourceRootsTest.java new file mode 100644 index 00000000000..751b6ae6306 --- /dev/null +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/CompileSourceRootsTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.maven; + +import org.apache.maven.model.Build; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.Xpp3DomBuilder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.StringReader; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Where a source could be compiled from. + * + *

This list is used to decide that a source is ABSENT -- an annotated class + * with no source behind it is dropped as an orphan, taking its placement error + * with it -- so a list that is merely incomplete must not be read as that, and + * one that is too wide is just as wrong in the other direction.

+ */ +public class CompileSourceRootsTest { + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + /** The Kotlin plugin compiles its own sourceDirs without adding them back. */ + @Test + public void theKotlinPluginsConfiguredDirectoriesCount() throws Exception { + File basedir = tmp.newFolder(); + new File(basedir, "src/main/kotlin").mkdirs(); + MavenProject project = projectAt(basedir); + project.addCompileSourceRoot(new File(basedir, "src/main/java").getAbsolutePath()); + + Plugin kotlin = new Plugin(); + kotlin.setArtifactId("kotlin-maven-plugin"); + kotlin.setConfiguration(config("src/shared/kotlin")); + kotlin.addExecution(execution("compile", "src/extra/kotlin")); + kotlin.addExecution( + execution("test-compile", "src/test/kotlin")); + project.getBuild().addPlugin(kotlin); + + List roots = AbstractCN1Mojo.compileSourceRoots(project); + + // What Maven listed, the conventional Kotlin root, and the dirs the + // plugin compiles. + assertTrue(roots.toString(), contains(roots, basedir, "src/main/java")); + assertTrue(roots.toString(), contains(roots, basedir, "src/main/kotlin")); + assertTrue(roots.toString(), contains(roots, basedir, "src/shared/kotlin")); + assertTrue(roots.toString(), contains(roots, basedir, "src/extra/kotlin")); + + // NOT the test execution's: a same-named test fixture would then make a + // deleted production class look like it still has a source. + assertFalse(roots.toString(), contains(roots, basedir, "src/test/kotlin")); + } + + /** A conventional root that does not exist is not invented. */ + @Test + public void anAbsentKotlinRootIsNotAdded() throws Exception { + File basedir = tmp.newFolder(); + MavenProject project = projectAt(basedir); + assertFalse(contains(AbstractCN1Mojo.compileSourceRoots(project), basedir, + "src/main/kotlin")); + } + + /** No project is "not told", which the callers read as inconclusive. */ + @Test + public void noProjectIsNoAnswer() { + org.junit.Assert.assertNull(AbstractCN1Mojo.compileSourceRoots(null)); + } + + private MavenProject projectAt(File basedir) { + MavenProject project = new MavenProject(); + project.setBuild(new Build()); + project.setFile(new File(basedir, "pom.xml")); + return project; + } + + private static PluginExecution execution(String goal, String configuration) throws Exception { + PluginExecution execution = new PluginExecution(); + execution.setGoals(Arrays.asList(goal)); + execution.setConfiguration(config(configuration)); + return execution; + } + + private static Xpp3Dom config(String inner) throws Exception { + return Xpp3DomBuilder.build(new StringReader("" + inner + + "")); + } + + private static boolean contains(List roots, File basedir, String relative) { + return roots != null + && roots.contains(new File(basedir, relative).getAbsolutePath()); + } +} From 3bac60a156cd218774d8dc55f7922845f4f8b171 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:21:57 +0300 Subject: [PATCH 104/115] An enum value is a second type, and an alias can take a name The migration renders an enum-valued hint as `IosThemeMode.MODERN` and imported only the annotation, so the generated code did not compile and every enum-valued migration was rolled back by its own verification build. The enum types the catalog can render are now accounted for by the same rule as the annotation names: imported where the name is free, and written out in full where the file has given it away. Two more names that can be taken and were not being noticed. A Kotlin `typealias Ios = ...` is a declaration this file makes, so a named import beside it gives the same local name twice; the type lookup only knew about class, object, interface, enum and record. And in Settings, `import com.example.Other as Ios` makes `@Ios` mean Other -- every aliased import was skipped as irrelevant, so a wildcard import of ours was trusted and the editor was hidden for a hint the processor never emits. Our own annotation aliased to its own name is still ours. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/MigrateBuildHintsMojo.java | 111 +++++++++++++++++- .../MigrateBuildHintsPropertyParsingTest.java | 38 ++++++ .../settings/CodenameOneSettings.java | 11 +- .../settings/BuildHintCatalogTest.java | 31 +++++ 4 files changed, 183 insertions(+), 8 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java index 41edcae406b..a817eca6f62 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/MigrateBuildHintsMojo.java @@ -907,15 +907,18 @@ void insertAnnotations(File source, String annotations, String simpleName) written.append(line).append('\n'); continue; } + String body = line; if (simpleNameIsTaken(text, blanked, name, kotlin)) { - written.append("@").append(ANNOTATION_PACKAGE).append('.') - .append(line.substring(1)).append('\n'); - continue; - } - if (importLines.indexOf(importOf(name, kotlin)) < 0) { + body = "@" + ANNOTATION_PACKAGE + "." + line.substring(1); + } else if (importLines.indexOf(importOf(name, kotlin)) < 0) { importLines.append(importOf(name, kotlin)).append('\n'); } - written.append(line).append('\n'); + // An enum-valued hint renders as `IosThemeMode.MODERN`, which is a + // second type to account for -- without its own import the generated + // annotation does not compile, so every enum-valued migration was + // rolled back by its own verification build. + written.append(withEnumsResolved(body, text, blanked, kotlin, importLines)) + .append('\n'); } // split(-1) keeps the trailing empty piece, which put a blank line back. annotations = written.substring(0, Math.max(0, written.length() - 1)); @@ -962,6 +965,56 @@ void insertAnnotations(File source, String annotations, String simpleName) write(source, head + annotations + tail); } + /// `line` with every enum type it names either imported or written out in + /// full, by the same rule the annotation names follow. + private static String withEnumsResolved(String line, String text, String blanked, + boolean kotlin, StringBuilder importLines) { + java.util.Set enums = enumTypeNames(); + StringBuilder out = new StringBuilder(); + int i = 0; + while (i < line.length()) { + char c = line.charAt(i); + if (!Character.isJavaIdentifierStart(c) + || (i > 0 && Character.isJavaIdentifierPart(line.charAt(i - 1)))) { + out.append(c); + i++; + continue; + } + int end = i; + while (end < line.length() && Character.isJavaIdentifierPart(line.charAt(end))) { + end++; + } + String word = line.substring(i, end); + if (!enums.contains(word) || end >= line.length() || line.charAt(end) != '.') { + out.append(word); + i = end; + continue; + } + if (simpleNameIsTaken(text, blanked, word, kotlin)) { + out.append(ANNOTATION_PACKAGE).append('.').append(word); + } else { + if (importLines.indexOf(importOf(word, kotlin)) < 0) { + importLines.append(importOf(word, kotlin)).append('\n'); + } + out.append(word); + } + i = end; + } + return out.toString(); + } + + /// Every enum type the catalog can render a value as. + private static java.util.Set enumTypeNames() { + java.util.Set out = new java.util.LinkedHashSet(); + for (com.codename1.build.shared.BuildHints.Hint h + : com.codename1.build.shared.BuildHints.entries()) { + if (h.enumName() != null && h.enumName().length() > 0) { + out.add(h.enumName()); + } + } + return out; + } + /** The package the generated annotations live in. */ private static final String ANNOTATION_PACKAGE = "com.codename1.annotations.buildhints"; @@ -998,6 +1051,9 @@ private static boolean simpleNameIsTaken(String text, String blanked, String sim .declaresType(text, simple, kotlin)) { return true; } + if (kotlin && declaresTypeAlias(blanked, simple)) { + return true; + } for (int at = importKeywordAt(blanked, 0); at >= 0; at = importKeywordAt(blanked, at + "import".length())) { if (simple.equals(importedSimpleName(blanked, at, kotlin))) { @@ -1007,6 +1063,49 @@ private static boolean simpleNameIsTaken(String text, String blanked, String sim return false; } + /// Whether blanked `code` declares `typealias simple = ...`. + /// + /// A typealias is a declaration this file makes, so it takes the name as + /// surely as a class does -- and the type lookup only knows about class, + /// object, interface, enum and record. Writing a named import beside one + /// gives the same local name twice, which does not compile. + private static boolean declaresTypeAlias(String code, String simple) { + int at = 0; + while (true) { + at = code.indexOf("typealias", at); + if (at < 0) { + return false; + } + int after = at + "typealias".length(); + boolean whole = (at == 0 || !Character.isJavaIdentifierPart(code.charAt(at - 1))) + && after < code.length() + && !Character.isJavaIdentifierPart(code.charAt(after)); + if (whole) { + int n = after; + while (n < code.length() && Character.isWhitespace(code.charAt(n))) { + n++; + } + int escaped = com.codename1.maven.processors.BuildHintAnnotationProcessor + .escapedIdentifierEnd(code, n); + String name; + if (escaped > n) { + name = code.substring(n + 1, escaped - 1); + } else { + int stop = n; + while (stop < code.length() + && Character.isJavaIdentifierPart(code.charAt(stop))) { + stop++; + } + name = code.substring(n, stop); + } + if (simple.equals(name)) { + return true; + } + } + at = after; + } + } + /// The name the import at `at` makes available, or null when it makes none /// this goal has to work around. /// diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java index 3fa3120b8f3..29c9eba1150 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/MigrateBuildHintsPropertyParsingTest.java @@ -805,6 +805,44 @@ public void anAliasSpanningLinesStillTakesTheName() throws Exception { assertTrue(free, free.contains("@Ios(teamId")); } + /// An enum-valued hint renders as `IosThemeMode.MODERN`, which is a second + /// type to account for: without its own import the generated annotation does + /// not compile, so every enum-valued migration was rolled back by its own + /// verification build. + @Test + public void anEnumValueBringsItsOwnType() throws Exception { + String migrated = migrate("package com.example;\npublic class MyApp {\n}\n", + "@Ios(themeMode = IosThemeMode.MODERN)\n"); + assertTrue(migrated, + migrated.contains("import com.codename1.annotations.buildhints.IosThemeMode;")); + assertTrue(migrated, migrated.contains("@Ios(themeMode = IosThemeMode.MODERN)")); + + // A file that has given that name away gets the qualified form instead. + String taken = migrate("package com.example;\n" + + "import com.example.other.IosThemeMode;\n" + + "public class MyApp {\n}\n", + "@Ios(themeMode = IosThemeMode.MODERN)\n"); + assertTrue(taken, taken.contains( + "themeMode = com.codename1.annotations.buildhints.IosThemeMode.MODERN")); + assertFalse(taken, + taken.contains("import com.codename1.annotations.buildhints.IosThemeMode;")); + } + + /// A `typealias` is a declaration this file makes, so it takes the name as + /// surely as a class does. Writing a named import beside one gives the same + /// local name twice, which does not compile. + @Test + public void aTypeAliasTakesTheNameTheImportWouldWant() throws Exception { + String migrated = migrateKotlin("package com.example\n" + + "typealias Ios = com.example.other.Ios\n" + + "class MyApp\n", + "@Ios(teamId = \"X\")\n"); + assertTrue(migrated, + migrated.contains("@com.codename1.annotations.buildhints.Ios(teamId")); + assertFalse(migrated, + migrated.contains("import com.codename1.annotations.buildhints.Ios")); + } + private String migrateKotlin(String source, String annotations) throws Exception { File f = File.createTempFile("MyApp", ".kt"); f.deleteOnExit(); diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 7f40232e53c..bb39a66ab47 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2930,8 +2930,15 @@ static boolean importsAnnotation(String source, String simple, boolean kotlin, boolean ours = false; for (Imported imported : importsIn(source, kotlin)) { if (imported.alias != null) { - // Introduces its alias, not this name, so it neither grants the - // simple spelling nor shadows it. + // Introduces its ALIAS rather than its own name -- so it neither + // grants nor shadows the simple spelling, unless the alias IS + // that spelling. `import com.example.Other as Ios` makes `@Ios` + // mean Other, and ignoring it let a wildcard import of ours be + // trusted instead, hiding the editor for a hint the processor + // never emits. + if (imported.alias.equals(simple)) { + return imported.name.startsWith(pkg); + } continue; } if (imported.name.equals(pkg + simple)) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 79373a0280c..af658311d69 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1656,4 +1656,35 @@ public void aSingleStaticImportTakesTheNameToo() { assertFalse(CodenameOneSettings.importsAnnotation(staticky, "Ios", false)); assertEquals("staticky.Ios", CodenameOneSettings.importsIn(staticky, false).get(0).name); } + + /// `import com.example.Other as Ios` makes `@Ios` mean Other, so it shadows + /// a wildcard import of ours. Ignoring every aliased import let the wildcard + /// be trusted, hiding the editor for a hint the processor never emits. + @Test + public void anImportAliasedToOurNameShadowsTheWildcard() { + String shadowed = "package com.example\n" + + "import com.example.other.Other as Ios\n" + + "import com.codename1.annotations.buildhints.*\n" + + "@Ios(teamId = \"X\")\n" + + "class MyApp\n"; + assertFalse(CodenameOneSettings.importsAnnotation(shadowed, "Ios", true)); + java.util.Map owned = new java.util.HashMap<>(); + CodenameOneSettings.collectAnnotationOwnedHints(shadowed, owned, true); + assertNull(owned.get("ios.teamId")); + + // OUR annotation aliased to its own name is still ours. + String ours = "package com.example\n" + + "import com.codename1.annotations.buildhints.Ios as Ios\n" + + "@Ios(teamId = \"ABCDE12345\")\n" + + "class MyApp\n"; + assertTrue(CodenameOneSettings.importsAnnotation(ours, "Ios", true)); + + // An alias to some OTHER name shadows nothing. + String elsewhere = "package com.example\n" + + "import com.example.other.Other as Something\n" + + "import com.codename1.annotations.buildhints.*\n" + + "@Ios(teamId = \"X\")\n" + + "class MyApp\n"; + assertTrue(CodenameOneSettings.importsAnnotation(elsewhere, "Ios", true)); + } } From 8fd723f18d62717074eb6a25aa132f6c9b4f7ea1 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:26:19 +0300 Subject: [PATCH 105/115] Let the retry wait grow, so it outlasts the outage `build suite classes (x64)` spent all three attempts inside sixty seconds and took a 403 from Maven Central on every one, so the branch failed for a reason that had nothing to do with it. The wrapper worked -- it matched the shape and retried -- but a flat 30s wait lands squarely inside the window Central is still refusing in. Quadrupling from the first delay gives 30s, 2m, 5m, which is the shape the Windows cross-compile workflow already settled on for this exact reason and wrote down; the shared helper had not learned it. Capped, and the cap is overridable. A failure that does not match RETRY_ONLY_MATCHING is still terminal on its first attempt. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/ci/retry.sh | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/ci/retry.sh b/scripts/ci/retry.sh index d5e43beeced..3c7ff5f00bd 100755 --- a/scripts/ci/retry.sh +++ b/scripts/ci/retry.sh @@ -22,6 +22,12 @@ set -uo pipefail # gets a second chance. Leave it unset for steps that merely download and build. attempts="${RETRY_ATTEMPTS:-3}" delay="${RETRY_DELAY_SECONDS:-30}" +# The wait GROWS. A flat retry spends all its attempts inside the same window +# Central is still refusing in -- observed as three 403s in sixty seconds, which +# failed the branch for a reason that had nothing to do with it. Quadrupling +# from the first delay gives 30s, 2m, 5m, which is the same shape the Windows +# cross-compile workflow settled on for the same reason. +max_delay="${RETRY_MAX_DELAY_SECONDS:-300}" only_matching="${RETRY_ONLY_MATCHING:-}" if [ "$#" -eq 0 ]; then @@ -38,6 +44,13 @@ esac case "$delay" in ''|*[!0-9]*) echo "retry.sh: RETRY_DELAY_SECONDS must be a non-negative integer, got '${delay}'" >&2; exit 2 ;; esac +case "$max_delay" in + ''|*[!0-9]*) echo "retry.sh: RETRY_MAX_DELAY_SECONDS must be a non-negative integer, got '${max_delay}'" >&2; exit 2 ;; +esac +if [ "$max_delay" -lt "$delay" ]; then + max_delay="$delay" +fi +wait_seconds="$delay" # Counted with arithmetic rather than `seq`: BSD and GNU seq disagree on # degenerate ranges, and this loop body must run exactly `attempts` times. @@ -69,8 +82,12 @@ while [ "$attempt" -le "$attempts" ]; do fi if [ "$attempt" -lt "$attempts" ]; then echo "retry.sh: attempt ${attempt}/${attempts} failed with status ${status};" \ - "retrying in ${delay}s (possible transient Maven Central 403/429/5xx)" >&2 - sleep "$delay" + "retrying in ${wait_seconds}s (possible transient Maven Central 403/429/5xx)" >&2 + sleep "$wait_seconds" + wait_seconds=$((wait_seconds * 4)) + if [ "$wait_seconds" -gt "$max_delay" ]; then + wait_seconds="$max_delay" + fi fi attempt=$((attempt + 1)) done From cc67d3db70cee285e0a30024dfc4506df24af898 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:33:38 +0300 Subject: [PATCH 106/115] Name the source roots instead of excluding directories The sweep walked the whole project and excluded by name, and that list was never going to be complete: `src/test`, then `src/testFixtures`, then `src/main/resources`, then `src/main/templates` and `src/main/proto` -- because "not a source root" is not a property of a directory's name. Each one let a source template shadow a real annotation, so the hint read as unowned and Add wrote the duplicate the next build refuses. The roots are named now, so every one of those is wrong by construction, and the main-class search uses the same list so the two cannot disagree about where a module's sources are. The `build`-as-a-package rule goes with it: starting inside a source root walks com.codename1.build.shared as the package it is, with nothing needed to tell it from an output directory. `insideSourceTree` had no callers left and is deleted rather than left to rot. KNOWN LIMIT, written where the roots are built: a module that configures a custom root in its POM is not covered, because this tool has no resolved project to ask and its POM handling is string surgery rather than an XML model. Missing a peer leaves a hint reading as annotation-owned and its editor hidden, which is annoying but cannot write the duplicate declaration that including a non-source directory could. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 148 ++++++------------ .../settings/BuildHintCatalogTest.java | 104 ++++-------- 2 files changed, 82 insertions(+), 170 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index bb39a66ab47..244e147fa03 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2349,82 +2349,61 @@ private java.util.Map annotationOwnedHintsFromSource() { /// declarations made elsewhere. private String lastSourcePath; - /// Whether the walk should descend into `dir`/`name` looking for sources - /// that take part in compiling the main class. + /// The directories a module's MAIN sources are compiled from, by + /// convention, as candidates to be filtered by what exists. /// - /// Three rules, and each of them was a bug first: + /// An allow-list rather than a walk of the whole project with exclusions. + /// The exclusions were never going to be complete -- `src/test` was + /// followed by `src/testFixtures`, then `src/main/resources`, then + /// `src/main/templates` and `src/main/proto` -- because "not a source root" + /// is not a property of a directory's name. Naming the roots instead makes + /// every one of those wrong by construction. /// - /// - An output directory holds COPIES of the sources read elsewhere, and - /// walking one found a generated stub and called it the main class. - /// - Unless `build` is a package NAME under a source tree, which this - /// repository has -- refusing to descend there meant a main class living - /// in it could not be read at all. - /// - A test source set takes no part in compiling the main class, so a - /// same-package type there shadows nothing; counting it made a production - /// annotation look like somebody else's. + /// `target/generated-sources` is a root: Maven plugins add it, so a + /// declaration there is one the compiler sees. Nothing else under an output + /// directory is reachable, which also retires the `build` special case -- + /// starting inside a source root means com.codename1.build.shared is walked + /// as the package it is, with no rule needed to tell it from an output + /// directory. /// - /// That last one is a directory under `src` whose name says test: - /// `src/test`, and equally `src/testFixtures`, `src/integrationTest` and - /// `src/androidTest`, which are source sets by the same convention. Only - /// under `src`, because deeper down the same word is an ordinary package - /// name. - static boolean peerDirectoryHoldsSources(String dir, String name, boolean insideSourceTree) { - if (name.startsWith(".")) { - return false; - } - if (("target".equals(name) || "build".equals(name)) && !insideSourceTree) { - return false; - } - if (dir.endsWith("/src") && isTestSourceSet(name)) { - return false; - } - // A source set's resources are not compiled, so a `.java` or `.kt` - // TEMPLATE kept in one is not a peer -- treating it as a same-package - // type made a real annotation read as somebody else's. Only where the - // layout says resources root: `src/main/resources` and the flat - // `src/resources`, not a package that happens to be called that. - return !("resources".equals(name) - && (dir.endsWith("/src") || parentOf(dir).endsWith("/src"))); - } - - /// `dir` without its last segment. - private static String parentOf(String dir) { - int slash = dir.lastIndexOf('/'); - return slash < 0 ? "" : dir.substring(0, slash); - } - - /// Whether `name` is a test source set by the usual convention. + /// The flat `src` is a candidate only where there is no `src/main`, since + /// that is the layout it belongs to; in a Maven layout it would drag the + /// test sets back in. /// - /// The convention is a shape, not a word anywhere in the name: `test` - /// itself, `test` followed by a capital as in `testFixtures`, or a name - /// ending in `Test`/`Tests` as in `integrationTest` and `androidTest`. - /// Matching the substring pruned `src/latest` and `src/contest`, which are - /// production roots, and a main class living in one could not be found at - /// all. - private static boolean isTestSourceSet(String name) { - if ("test".equals(name)) { - return true; + /// KNOWN LIMIT, and the reason it is acceptable: a module that configures a + /// custom root in its POM is not covered, because this tool has no resolved + /// project to ask and its POM handling is deliberately string surgery rather + /// than an XML model. Missing a peer means a shadowing type goes unseen, so + /// a hint reads as annotation-owned and its editor stays hidden -- annoying, + /// but it cannot write the duplicate declaration that fails the next build, + /// which is what including a non-source directory could. + static java.util.List candidateSourceRoots(String projectDir, boolean hasSrcMain) { + java.util.List out = new java.util.ArrayList<>(); + if (projectDir == null) { + return out; } - if (name.length() > 4 && name.startsWith("test") - && Character.isUpperCase(name.charAt(4))) { - return true; + out.add(projectDir + "/src/main/java"); + out.add(projectDir + "/src/main/kotlin"); + if (!hasSrcMain) { + out.add(projectDir + "/src"); } - return name.endsWith("Test") || name.endsWith("Tests"); + out.add(projectDir + "/target/generated-sources"); + out.add(projectDir + "/build/generated-sources"); + return out; } - /// The generated source root under the output directory `dir`/`name`, or - /// null when that is not what this directory is. - /// - /// Everything else under an output directory is a copy, but - /// `generated-sources` is not: Maven plugins add it as a compile root, so a - /// declaration there is one the compiler sees. Named rather than read out of - /// the effective model, which is a large amount of machinery for one - /// conventional directory. - static String generatedSourceRootUnder(String dir, String name) { - if (!"target".equals(name) && !"build".equals(name)) { - return null; + /// Those of them that are there. + private java.util.List mainSourceRoots(String projectDir) { + FileSystemStorage fs = FileSystemStorage.getInstance(); + boolean hasSrcMain = projectDir != null + && fs.isDirectory(ProjectIO.fsUrl(projectDir + "/src/main")); + java.util.List out = new java.util.ArrayList<>(); + for (String candidate : candidateSourceRoots(projectDir, hasSrcMain)) { + if (fs.isDirectory(ProjectIO.fsUrl(candidate))) { + out.add(candidate); + } } - return dir + "/" + name + "/generated-sources"; + return out; } /// The text of every OTHER source in the project, bounded. @@ -2445,8 +2424,7 @@ private java.util.List otherProjectSources(String projectDir, String if (projectDir == null) { return out; } - java.util.List queue = new java.util.ArrayList<>(); - queue.add(projectDir); + java.util.List queue = new java.util.ArrayList<>(mainSourceRoots(projectDir)); for (int i = 0; i < queue.size() && i < 4000 && out.size() < 200; i++) { String dir = queue.get(i); String[] children; @@ -2462,14 +2440,8 @@ private java.util.List otherProjectSources(String projectDir, String String name = child.endsWith("/") ? child.substring(0, child.length() - 1) : child; String path = dir + "/" + name; if (FileSystemStorage.getInstance().isDirectory(ProjectIO.fsUrl(path))) { - if (peerDirectoryHoldsSources(dir, name, insideSourceTree(dir))) { + if (!name.startsWith(".")) { queue.add(path); - } else { - String generated = generatedSourceRootUnder(dir, name); - if (generated != null && FileSystemStorage.getInstance() - .isDirectory(ProjectIO.fsUrl(generated))) { - queue.add(generated); - } } continue; } @@ -2512,8 +2484,7 @@ private String findMainClassSource(String projectDir, String main, String pkg) { // for, so exactly the case it would drop. java.util.List named = new java.util.ArrayList<>(); java.util.List others = new java.util.ArrayList<>(); - java.util.List queue = new java.util.ArrayList<>(); - queue.add(projectDir); + java.util.List queue = new java.util.ArrayList<>(mainSourceRoots(projectDir)); for (int i = 0; i < queue.size() && i < 4000; i++) { String dir = queue.get(i); String[] children; @@ -2529,19 +2500,10 @@ private String findMainClassSource(String projectDir, String main, String pkg) { String name = child.endsWith("/") ? child.substring(0, child.length() - 1) : child; String path = dir + "/" + name; if (FileSystemStorage.getInstance().isDirectory(ProjectIO.fsUrl(path))) { - // The same rules as the peer sweep, and for the same reason: - // a main class generated into target/generated-sources is a - // class the compiler sees, so skipping the whole of target - // left its ownership unknown -- and Settings then offered an - // annotation-owned hint as editable. - if (peerDirectoryHoldsSources(dir, name, insideSourceTree(dir))) { + // The same roots as the peer sweep, so the two cannot + // disagree about where this module's sources are. + if (!name.startsWith(".")) { queue.add(path); - } else { - String generated = generatedSourceRootUnder(dir, name); - if (generated != null && FileSystemStorage.getInstance() - .isDirectory(ProjectIO.fsUrl(generated))) { - queue.add(generated); - } } continue; } @@ -2559,12 +2521,6 @@ private String findMainClassSource(String projectDir, String main, String pkg) { return hit != null ? hit : firstDeclaring(others, main, pkg, 400); } - /// Whether `dir` is under a source root, where `build` is a package name - /// rather than an output directory. - static boolean insideSourceTree(String dir) { - String normalised = dir.replace('\\', '/'); - return normalised.contains("/src/") || normalised.endsWith("/src"); - } /// The text of the first of `paths` that declares `main` in `pkg`, opening at /// most `budget` of them. diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index af658311d69..8cbf87829ba 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -738,18 +738,6 @@ public void theCatalogCarriesTheBuildersOwnDefault() { "a hint with no builder default must not invent one"); } - /// `build` is an ordinary package name -- this repository has - /// com.codename1.build.shared -- so refusing to descend into it meant a main - /// class living there could not be read. An output directory is never nested - /// under src/. - @Test - public void buildIsAPackageNameInsideASourceTree() { - assertTrue(CodenameOneSettings.insideSourceTree("/p/common/src/main/kotlin/com/example")); - assertTrue(CodenameOneSettings.insideSourceTree("/p/common/src")); - assertFalse(CodenameOneSettings.insideSourceTree("/p/common")); - assertFalse(CodenameOneSettings.insideSourceTree("/p/common/target/classes")); - } - /// facebook.appId has no default: both builders decide whether Facebook /// support is in the app by asking whether the hint is null, so seeding the /// literal from the call site enabled the integration against an unrelated @@ -1522,68 +1510,36 @@ public void aSamePackageTypeBeatsAWildcardImport() { assertNull(owned.get("ios.teamId")); } - /// Which directories the peer sweep walks. Each of these rules was a bug - /// first, and until now they lived inside the walk itself -- which needs a - /// project directory and the Codename One FileSystemStorage, so none of them - /// were covered. - @Test - public void thePeerSweepWalksTheSourceTreesOnly() { - // Ordinary source directories. - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common", "src", false)); - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "main", false)); - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src/main", "kotlin", - false)); - - // Output directories hold copies of those same sources. - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common", "target", false)); - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common", "build", false)); - - // ...unless `build` is a package name, which this repository has. - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( - "/p/common/src/main/java/com/codename1", "build", true)); - - // A test source set takes no part in compiling the main class, and - // `test` is not the only name one goes by. - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "test", false)); - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "testFixtures", - false)); - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", - "integrationTest", false)); - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "androidTest", - false)); - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "jvmTests", - false)); - // The main source set is not one of them, and neither is a production - // root that merely contains the letters. - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "main", false)); - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "latest", false)); - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "contest", - false)); - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "testing", - false)); - // ...and deeper down the same word is an ordinary package name. - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( - "/p/common/src/main/java/com", "test", false)); - - // A source set's resources are not compiled, so a source template kept - // in one is not a peer. - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src/main", - "resources", false)); - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p/common/src", "resources", - false)); - // ...but a package that happens to be called that is a package. - assertTrue(CodenameOneSettings.peerDirectoryHoldsSources( - "/p/common/src/main/java/com/example", "resources", false)); - - // Hidden directories are not source trees. - assertFalse(CodenameOneSettings.peerDirectoryHoldsSources("/p", ".git", false)); - - // Generated sources under an output directory ARE a compile root. - assertEquals("/p/common/target/generated-sources", - CodenameOneSettings.generatedSourceRootUnder("/p/common", "target")); - assertEquals("/p/common/build/generated-sources", - CodenameOneSettings.generatedSourceRootUnder("/p/common", "build")); - assertNull(CodenameOneSettings.generatedSourceRootUnder("/p/common/src", "test")); + /// Where the peer sweep looks. An allow-list of roots, not a walk of the + /// whole project with exclusions -- those were never going to be complete: + /// `src/test` was followed by `src/testFixtures`, then `src/main/resources`, + /// then `src/main/templates` and `src/main/proto`, because "not a source + /// root" is not a property of a directory's name. + @Test + public void thePeerSweepLooksInTheSourceRootsOnly() { + java.util.List maven = + CodenameOneSettings.candidateSourceRoots("/p/common", true); + assertTrue(maven.contains("/p/common/src/main/java")); + assertTrue(maven.contains("/p/common/src/main/kotlin")); + // Generated sources are a compile root that plugins add. + assertTrue(maven.contains("/p/common/target/generated-sources")); + + // Everything the exclusion list used to chase is simply not a root. + for (String notARoot : new String[] {"/p/common/src/test", "/p/common/src/testFixtures", + "/p/common/src/integrationTest", "/p/common/src/main/resources", + "/p/common/src/main/templates", "/p/common/src/main/proto", + "/p/common/target", "/p/common/build"}) { + assertFalse(maven.contains(notARoot), maven.toString()); + } + + // The flat layout keeps its own root, and only there: under a Maven + // layout `src` would drag the test sets back in. + assertFalse(maven.contains("/p/common/src")); + java.util.List flat = + CodenameOneSettings.candidateSourceRoots("/p/common", false); + assertTrue(flat.contains("/p/common/src")); + + assertTrue(CodenameOneSettings.candidateSourceRoots(null, false).isEmpty()); } /// The compiler's source encoding is a project setting this tool does not From c7d42ae5cca7707991e57c390ff6d84085e1b1d9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:35:48 +0300 Subject: [PATCH 107/115] Read the encoding the project declares, rather than guessing twice The guess can only tell UTF-8 from a single-byte encoding, so a multibyte one such as Shift_JIS came back as mojibake and a non-ASCII package or main-class name never matched -- the real main source was rejected, and with no manifest yet the hints it owns read as editable. What the project SAYS it is written in settles it: the conventional property, then the compiler plugin's own setting, read as a string the way this tool handles POMs everywhere else. The guess stays for a project that declares nothing, and an unresolved ${property} is not an encoding, so it is ignored rather than passed on to throw on every file. The processor side needs no equivalent: a non-ASCII name it cannot read is already inconclusive there rather than an orphan, so a Shift_JIS source keeps its class and its placement error. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 84 +++++++++++++++++++ .../settings/BuildHintCatalogTest.java | 35 ++++++++ 2 files changed, 119 insertions(+) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 244e147fa03..1bebf83dd38 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2665,6 +2665,25 @@ static boolean declaresClass(String text, String main, String pkg, boolean kotli return false; } + /// The POM as bytes-to-ISO-8859-1, used only to find the encoding + /// declaration -- which is ASCII wherever it appears. + private String readIfPresentRaw(String path) { + InputStream in = null; + try { + String url = ProjectIO.fsUrl(path); + FileSystemStorage fs = FileSystemStorage.getInstance(); + if (!fs.exists(url)) { + return null; + } + in = fs.openInputStream(url); + return new String(Util.readInputStream(in), "ISO-8859-1"); + } catch (Exception ex) { + return null; + } finally { + Util.cleanup(in); + } + } + private String readIfPresent(String path) { InputStream in = null; try { @@ -2675,6 +2694,19 @@ private String readIfPresent(String path) { } in = fs.openInputStream(url); byte[] bytes = Util.readInputStream(in); + // What the project SAYS it is written in, when it says. The guess + // below can only tell UTF-8 from a single-byte encoding, so a + // multibyte one such as Shift_JIS came back as mojibake and its + // non-ASCII names never matched. + String declared = declaredSourceEncoding(); + if (declared != null) { + try { + return new String(bytes, declared); + } catch (Exception unsupported) { + // Named an encoding this runtime does not have. Guessing is + // better than failing to read the file at all. + } + } // UTF-8 where the file is UTF-8, which is the overwhelming case; // ISO-8859-1 where it is not, since that never fails to decode. The // compiler's source encoding is a project setting this tool does not @@ -3055,6 +3087,58 @@ private static boolean isDeclarationModifier(String word) { || "inline".equals(word) || "external".equals(word); } + /// The source encoding the POM declares, or null when it declares none. + /// + /// Read once per session: this is asked for every source file the sweeps + /// open, and the answer cannot change while the project is bound. + private String declaredSourceEncoding() { + if (!sourceEncodingRead) { + sourceEncodingRead = true; + if (binding != null && binding.pom() != null && !binding.pom().isEmpty()) { + sourceEncoding = declaredSourceEncoding(readIfPresentRaw(binding.pom())); + } + } + return sourceEncoding; + } + + private boolean sourceEncodingRead; + private String sourceEncoding; + + /// The encoding `pomText` declares: the conventional property first, then + /// the compiler plugin's own setting. + /// + /// A string read rather than an XML model, which is how this tool handles + /// POMs everywhere else. It is looking for one value that is written as a + /// plain element in both places. + static String declaredSourceEncoding(String pomText) { + if (pomText == null) { + return null; + } + String value = elementValue(pomText, "project.build.sourceEncoding"); + if (value == null) { + value = elementValue(pomText, "maven.compiler.encoding"); + } + if (value == null) { + value = elementValue(pomText, "encoding"); + } + if (value == null || value.trim().isEmpty() || value.indexOf('$') >= 0) { + // An unresolved ${property} is not an encoding, and this reader has + // no model to resolve it against. + return null; + } + return value.trim(); + } + + private static String elementValue(String xml, String name) { + String open = "<" + name + ">"; + int at = xml.indexOf(open); + if (at < 0) { + return null; + } + int close = xml.indexOf("", at + open.length()); + return close < 0 ? null : xml.substring(at + open.length(), close); + } + /// Whether `bytes` decode as UTF-8. /// /// Hand-rolled because CharsetDecoder is outside the Codename One API diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 8cbf87829ba..033c17fa0b3 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1643,4 +1643,39 @@ public void anImportAliasedToOurNameShadowsTheWildcard() { + "class MyApp\n"; assertTrue(CodenameOneSettings.importsAnnotation(elsewhere, "Ios", true)); } + + /// The guess can only tell UTF-8 from a single-byte encoding, so a + /// multibyte one such as Shift_JIS came back as mojibake and its non-ASCII + /// names never matched. What the project SAYS it is written in settles it, + /// when it says. + @Test + public void thePomsDeclaredSourceEncodingIsUsed() throws Exception { + assertEquals("Shift_JIS", CodenameOneSettings.declaredSourceEncoding( + "" + + "Shift_JIS" + + "")); + // The compiler plugin's own setting counts too. + assertEquals("Shift_JIS", CodenameOneSettings.declaredSourceEncoding( + "" + + "Shift_JIS" + + "")); + // Nothing declared is nothing to use, and the guess stays. + assertNull(CodenameOneSettings.declaredSourceEncoding("")); + assertNull(CodenameOneSettings.declaredSourceEncoding(null)); + // An unresolved property is not an encoding: this reader has no model to + // resolve it against, and passing it on would throw on every file. + assertNull(CodenameOneSettings.declaredSourceEncoding( + "${enc}" + + "")); + + // And it decodes what the guess could not: a multibyte source whose + // package name is only readable in its own encoding. + String text = "package com.\u30a2\u30d7\u30ea;\npublic class MyApp {}\n"; + byte[] sjis = text.getBytes("Shift_JIS"); + assertFalse(CodenameOneSettings.isValidUtf8(sjis)); + assertTrue(CodenameOneSettings.declaresClass( + new String(sjis, "Shift_JIS"), "MyApp", "com.\u30a2\u30d7\u30ea")); + assertFalse(CodenameOneSettings.declaresClass( + new String(sjis, "ISO-8859-1"), "MyApp", "com.\u30a2\u30d7\u30ea")); + } } From ee57dced7610d2ac36ff76c9296c80a32ff7d9f2 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:42:28 +0300 Subject: [PATCH 108/115] Search the roots the POM declares, and take the compiler's encoding The safety argument I wrote for the allow-list covered missing PEERS, where the error is a hidden editor. It does not cover the main class: a module with `appsrc` or a build-helper root had its main source missed entirely, so nothing knew which hints an annotation owns and Add wrote the duplicate the next build refuses. The declared roots are read now -- sourceDirectory, the Kotlin plugin's sourceDirs and build-helper's sources -- as a string, the way this tool handles POMs everywhere else, and a declared TEST root is dropped since those are configured through the same elements. And the encoding lookup took the first `` in the file, which is maven-resources-plugin's in a project that sets one. Scoped to the compiler plugin's own block; the conventional property still wins. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 121 +++++++++++++++++- .../settings/BuildHintCatalogTest.java | 76 ++++++++++- 2 files changed, 190 insertions(+), 7 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 1bebf83dd38..92789ea5010 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2392,13 +2392,86 @@ static java.util.List candidateSourceRoots(String projectDir, boolean ha return out; } - /// Those of them that are there. + /// The roots a POM declares: ``, and the source lists the + /// Kotlin and build-helper plugins take. + /// + /// A string read, like the rest of this tool's POM handling. It is looking + /// for plain elements, and what it cannot resolve -- a `${property}` path -- + /// it leaves alone rather than guessing. + /// + /// A declared root under a test tree is dropped: those are configured + /// through the same elements, and one of them shadowing a production type is + /// the failure this list exists to avoid. + static java.util.List declaredSourceRoots(String pomText) { + java.util.List out = new java.util.ArrayList<>(); + if (pomText == null) { + return out; + } + for (String element : new String[] {"sourceDirectory", "sourceDir", "source"}) { + for (String value : elementValues(pomText, element)) { + String path = value.trim().replace('\\', '/'); + if (path.isEmpty() || path.indexOf('$') >= 0 || looksLikeATestRoot(path)) { + continue; + } + if (!out.contains(path)) { + out.add(path); + } + } + } + return out; + } + + /// Whether a declared path is a test tree, by the same convention the source + /// sets follow: a `test` segment, or one that says test the way + /// `testFixtures` and `integrationTest` do. + private static boolean looksLikeATestRoot(String path) { + for (String segment : com.codename1.util.StringUtil.tokenize(path, "/")) { + if ("test".equals(segment) + || (segment.length() > 4 && segment.startsWith("test") + && Character.isUpperCase(segment.charAt(4))) + || segment.endsWith("Test") || segment.endsWith("Tests")) { + return true; + } + } + return false; + } + + private static java.util.List elementValues(String xml, String name) { + java.util.List out = new java.util.ArrayList<>(); + String open = "<" + name + ">"; + String shut = ""; + int at = xml.indexOf(open); + while (at >= 0) { + int close = xml.indexOf(shut, at + open.length()); + if (close < 0) { + break; + } + out.add(xml.substring(at + open.length(), close)); + at = xml.indexOf(open, close + shut.length()); + } + return out; + } + + /// Those of them that are there, plus whatever the POM declares. private java.util.List mainSourceRoots(String projectDir) { FileSystemStorage fs = FileSystemStorage.getInstance(); boolean hasSrcMain = projectDir != null && fs.isDirectory(ProjectIO.fsUrl(projectDir + "/src/main")); + java.util.List candidates = + new java.util.ArrayList<>(candidateSourceRoots(projectDir, hasSrcMain)); + // A module may put its sources somewhere else entirely, and the main + // class is the one file this list cannot afford to miss: without it + // nothing knows which hints an annotation owns, and Add writes the + // duplicate the next build refuses. + for (String declared : declaredSourceRoots(pomText())) { + String path = declared.startsWith("/") || declared.indexOf(':') == 1 + ? declared : projectDir + "/" + declared; + if (!candidates.contains(path)) { + candidates.add(path); + } + } java.util.List out = new java.util.ArrayList<>(); - for (String candidate : candidateSourceRoots(projectDir, hasSrcMain)) { + for (String candidate : candidates) { if (fs.isDirectory(ProjectIO.fsUrl(candidate))) { out.add(candidate); } @@ -2406,6 +2479,20 @@ private java.util.List mainSourceRoots(String projectDir) { return out; } + /// The bound POM's text, read once per session. + private String pomText() { + if (!pomTextRead) { + pomTextRead = true; + if (binding != null && binding.pom() != null && !binding.pom().isEmpty()) { + pomText = readIfPresentRaw(binding.pom()); + } + } + return pomText; + } + + private boolean pomTextRead; + private String pomText; + /// The text of every OTHER source in the project, bounded. /// /// For the declarations that are not file-scoped and so can decide what a @@ -3094,9 +3181,7 @@ private static boolean isDeclarationModifier(String word) { private String declaredSourceEncoding() { if (!sourceEncodingRead) { sourceEncodingRead = true; - if (binding != null && binding.pom() != null && !binding.pom().isEmpty()) { - sourceEncoding = declaredSourceEncoding(readIfPresentRaw(binding.pom())); - } + sourceEncoding = declaredSourceEncoding(pomText()); } return sourceEncoding; } @@ -3119,7 +3204,11 @@ static String declaredSourceEncoding(String pomText) { value = elementValue(pomText, "maven.compiler.encoding"); } if (value == null) { - value = elementValue(pomText, "encoding"); + // Inside the COMPILER plugin. maven-resources-plugin declares an + // of its own, and taking the first one in the file + // adopted the resource charset for every source -- so a UTF-8 source + // with a differently encoded resources block was read as neither. + value = elementValue(pluginBlock(pomText, "maven-compiler-plugin"), "encoding"); } if (value == null || value.trim().isEmpty() || value.indexOf('$') >= 0) { // An unresolved ${property} is not an encoding, and this reader has @@ -3129,7 +3218,27 @@ static String declaredSourceEncoding(String pomText) { return value.trim(); } + /// The `` element declaring `artifactId`, or null. + static String pluginBlock(String pomText, String artifactId) { + if (pomText == null) { + return null; + } + int at = pomText.indexOf("" + artifactId + ""); + if (at < 0) { + return null; + } + int open = pomText.lastIndexOf("", at); + int close = pomText.indexOf("", at); + if (open < 0 || close < 0) { + return null; + } + return pomText.substring(open, close); + } + private static String elementValue(String xml, String name) { + if (xml == null) { + return null; + } String open = "<" + name + ">"; int at = xml.indexOf(open); if (at < 0) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 033c17fa0b3..07d637a8687 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1654,9 +1654,11 @@ public void thePomsDeclaredSourceEncodingIsUsed() throws Exception { "" + "Shift_JIS" + "")); - // The compiler plugin's own setting counts too. + // The compiler plugin's own setting counts too -- and only that + // plugin's; see theEncodingIsTheCompilerPluginsOwn. assertEquals("Shift_JIS", CodenameOneSettings.declaredSourceEncoding( "" + + "maven-compiler-plugin" + "Shift_JIS" + "")); // Nothing declared is nothing to use, and the guess stays. @@ -1678,4 +1680,76 @@ public void thePomsDeclaredSourceEncodingIsUsed() throws Exception { assertFalse(CodenameOneSettings.declaresClass( new String(sjis, "ISO-8859-1"), "MyApp", "com.\u30a2\u30d7\u30ea")); } + + /// A module may put its sources somewhere else entirely, and the main class + /// is the one file the root list cannot afford to miss: without it nothing + /// knows which hints an annotation owns, and Add writes the duplicate the + /// next build refuses. + @Test + public void theRootsThePomDeclaresAreSearchedToo() { + java.util.List roots = CodenameOneSettings.declaredSourceRoots( + "" + + "appsrc" + + "" + + "build-helper-maven-plugin" + + "" + + "src/generated/java" + + "" + + "kotlin-maven-plugin" + + "" + + "src/main/kt" + + "" + + ""); + assertTrue(roots.contains("appsrc"), roots.toString()); + assertTrue(roots.contains("src/generated/java"), roots.toString()); + assertTrue(roots.contains("src/main/kt"), roots.toString()); + + // A declared TEST root is dropped: those are configured through the same + // elements, and one shadowing a production type is the failure the root + // list exists to avoid. + java.util.List withTests = CodenameOneSettings.declaredSourceRoots( + "" + + "appsrc" + + "src/test/java" + + "build-helper-maven-plugin" + + "" + + "src/integrationTest/java" + + "" + + ""); + assertTrue(withTests.contains("appsrc"), withTests.toString()); + assertFalse(withTests.contains("src/test/java"), withTests.toString()); + assertFalse(withTests.contains("src/integrationTest/java"), withTests.toString()); + + // What it cannot resolve it leaves alone rather than guessing. + assertTrue(CodenameOneSettings.declaredSourceRoots( + "${basedir}/x") + .isEmpty()); + assertTrue(CodenameOneSettings.declaredSourceRoots(null).isEmpty()); + } + + /// maven-resources-plugin declares an `` of its own, and taking + /// the first one in the file adopted the resource charset for every source. + @Test + public void theEncodingIsTheCompilerPluginsOwn() { + String pom = "" + + "maven-resources-plugin" + + "ISO-8859-1" + + "maven-compiler-plugin" + + "UTF-8" + + ""; + assertEquals("UTF-8", CodenameOneSettings.declaredSourceEncoding(pom)); + + // With no compiler encoding at all, another plugin's is not adopted. + String resourcesOnly = "" + + "maven-resources-plugin" + + "ISO-8859-1" + + ""; + assertNull(CodenameOneSettings.declaredSourceEncoding(resourcesOnly)); + + // The property still wins, since that is what the convention is. + String property = "" + + "Shift_JIS" + + "" + resourcesOnly.substring("".length()); + assertEquals("Shift_JIS", CodenameOneSettings.declaredSourceEncoding(property)); + } } From 17cc2566c04b09afbf44253eaeb66abd5a3d2922 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:53:17 +0300 Subject: [PATCH 109/115] Follow the POM up to where the encoding is actually declared `project.build.sourceEncoding` is normally declared once in the parent, which is where a multi-module Codename One project puts it -- so reading only the bound module POM found nothing in the standard layout, and the fix I made for it two commits ago did not fire on the projects it was written for. The chain is walked instead, nearest first: `` when the POM says, `../pom.xml` when it does not, which is Maven's own default. An empty `` means resolve from the repository, which this reader cannot do, so the walk stops there rather than guessing. Bounded, since a parent chain is short and this touches the filesystem. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 86 ++++++++++++++++++- .../settings/BuildHintCatalogTest.java | 33 +++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 92789ea5010..0d19fe1a150 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2479,6 +2479,81 @@ private java.util.List mainSourceRoots(String projectDir) { return out; } + /// The bound POM and its ancestors, nearest first. + /// + /// Bounded: a parent chain is short, and this walks the filesystem. + private java.util.List pomChain() { + java.util.List out = new java.util.ArrayList<>(); + String path = binding == null ? null : binding.pom(); + String text = pomText(); + for (int depth = 0; depth < 8 && path != null && text != null; depth++) { + out.add(text); + String parent = parentPomPath(path, text); + if (parent == null || parent.equals(path)) { + break; + } + path = parent; + text = readIfPresentRaw(path); + } + return out; + } + + /// Where `pomText`'s parent POM is, given that it lives at `pomPath`. + /// + /// `` when it says, `../pom.xml` when it does not, which is + /// Maven's own default. Null when the POM declares no parent at all. + static String parentPomPath(String pomPath, String pomText) { + if (pomPath == null || pomText == null || pomText.indexOf("") < 0) { + return null; + } + String relative = elementValue( + pomText.substring(pomText.indexOf("")), "relativePath"); + if (relative == null) { + relative = "../pom.xml"; + } + relative = relative.trim().replace('\\', '/'); + if (relative.isEmpty()) { + // An empty relativePath means "resolve from the repository", which + // this reader cannot do. + return null; + } + if (!relative.endsWith(".xml")) { + relative = relative + "/pom.xml"; + } + int slash = pomPath.replace('\\', '/').lastIndexOf('/'); + String dir = slash < 0 ? "" : pomPath.replace('\\', '/').substring(0, slash); + return normalizePath(dir + "/" + relative); + } + + /// A path with its `.` and `..` segments applied. + static String normalizePath(String path) { + java.util.List parts = new java.util.ArrayList<>(); + boolean absolute = path.startsWith("/"); + for (String segment : com.codename1.util.StringUtil.tokenize(path, "/")) { + if (segment.isEmpty() || ".".equals(segment)) { + continue; + } + if ("..".equals(segment)) { + if (!parts.isEmpty() && !"..".equals(parts.get(parts.size() - 1))) { + parts.remove(parts.size() - 1); + continue; + } + if (absolute) { + continue; + } + } + parts.add(segment); + } + StringBuilder out = new StringBuilder(absolute ? "/" : ""); + for (int i = 0; i < parts.size(); i++) { + if (i > 0) { + out.append('/'); + } + out.append(parts.get(i)); + } + return out.toString(); + } + /// The bound POM's text, read once per session. private String pomText() { if (!pomTextRead) { @@ -3181,7 +3256,16 @@ private static boolean isDeclarationModifier(String word) { private String declaredSourceEncoding() { if (!sourceEncodingRead) { sourceEncodingRead = true; - sourceEncoding = declaredSourceEncoding(pomText()); + // The chain, not the module alone: `project.build.sourceEncoding` + // is normally declared once in the parent, which is where a + // multi-module Codename One project puts it -- so looking only at + // the bound POM found nothing in the standard layout. + for (String pom : pomChain()) { + sourceEncoding = declaredSourceEncoding(pom); + if (sourceEncoding != null) { + break; + } + } } return sourceEncoding; } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 07d637a8687..4ba7857efe8 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1752,4 +1752,37 @@ public void theEncodingIsTheCompilerPluginsOwn() { + "" + resourcesOnly.substring("".length()); assertEquals("Shift_JIS", CodenameOneSettings.declaredSourceEncoding(property)); } + + /// `project.build.sourceEncoding` is normally declared once in the parent, + /// which is where a multi-module Codename One project puts it -- so looking + /// only at the bound module POM found nothing in the standard layout. + @Test + public void theParentPomIsPartOfTheChain() { + // Maven's own default when a parent is declared without a relativePath. + assertEquals("/p/pom.xml", + CodenameOneSettings.parentPomPath("/p/common/pom.xml", + "root")); + + // An explicit relativePath, to a file or to a directory. + assertEquals("/p/build/pom.xml", + CodenameOneSettings.parentPomPath("/p/common/pom.xml", + "../build/pom.xml" + + "")); + assertEquals("/p/build/pom.xml", + CodenameOneSettings.parentPomPath("/p/common/pom.xml", + "../build" + + "")); + + // No parent is the end of the chain, and an empty relativePath means + // "from the repository", which this reader cannot follow. + assertNull(CodenameOneSettings.parentPomPath("/p/common/pom.xml", "")); + assertNull(CodenameOneSettings.parentPomPath("/p/common/pom.xml", + "")); + + // The path arithmetic the walk depends on. + assertEquals("/p/pom.xml", CodenameOneSettings.normalizePath("/p/common/../pom.xml")); + assertEquals("/p/a/pom.xml", + CodenameOneSettings.normalizePath("/p/common/./../a/pom.xml")); + assertEquals("a/pom.xml", CodenameOneSettings.normalizePath("b/../a/pom.xml")); + } } From ea8fae261cf46fc08aa10a72447bf29eae454640 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 08:02:49 +0300 Subject: [PATCH 110/115] Take the root elements only where they mean a root, and up the chain Reading `` and `` anywhere in the POM undid the fix they were added to: another plugin naming src/main/templates in a `` element is not saying it is compiled, so the templates went straight back into the sweep the root list had just taken them out of. They are read from their own plugins now -- build-helper and the Kotlin plugin -- and an `add-test-source` execution is passed over, which is the same distinction the Kotlin plugin's compile and test-compile executions needed. And Maven inherits `` and plugin configuration, so the roots are collected from the whole POM chain rather than the bound module alone; a relative one resolves against THIS module, as Maven does it. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 72 +++++++++++++++---- .../settings/BuildHintCatalogTest.java | 24 +++++++ 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 0d19fe1a150..654587d0569 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2407,20 +2407,58 @@ static java.util.List declaredSourceRoots(String pomText) { if (pomText == null) { return out; } - for (String element : new String[] {"sourceDirectory", "sourceDir", "source"}) { - for (String value : elementValues(pomText, element)) { - String path = value.trim().replace('\\', '/'); - if (path.isEmpty() || path.indexOf('$') >= 0 || looksLikeATestRoot(path)) { - continue; - } - if (!out.contains(path)) { - out.add(path); + // Scoped to the elements that actually declare a compile root. + // `` and `` are ordinary words: another plugin + // naming src/main/templates in one of them is not saying it is + // compiled, and treating it as a root put the templates back in the + // sweep that the root list had just taken them out of. + collectRoots(elementValues(pomText, "sourceDirectory"), out); + collectRoots(elementValues(pluginBlock(pomText, "kotlin-maven-plugin"), "sourceDir"), out); + String helper = pluginBlock(pomText, "build-helper-maven-plugin"); + if (helper != null) { + // add-test-source uses the same element, so an execution that adds + // TEST sources is passed over -- the same distinction the Kotlin + // plugin's compile and test-compile executions need. + for (String execution : executionsOf(helper)) { + if (execution.indexOf("add-test-source") < 0) { + collectRoots(elementValues(execution, "source"), out); } } } return out; } + private static void collectRoots(java.util.List values, java.util.List out) { + for (String value : values) { + String path = value.trim().replace('\\', '/'); + if (path.isEmpty() || path.indexOf('$') >= 0 || looksLikeATestRoot(path)) { + continue; + } + if (!out.contains(path)) { + out.add(path); + } + } + } + + /// The `` blocks in a plugin element, or the whole element when + /// it has none -- configuration at plugin level applies to every goal. + private static java.util.List executionsOf(String pluginBlock) { + java.util.List out = new java.util.ArrayList<>(); + int at = pluginBlock.indexOf(""); + while (at >= 0) { + int close = pluginBlock.indexOf("", at); + if (close < 0) { + break; + } + out.add(pluginBlock.substring(at, close)); + at = pluginBlock.indexOf("", close); + } + if (out.isEmpty()) { + out.add(pluginBlock); + } + return out; + } + /// Whether a declared path is a test tree, by the same convention the source /// sets follow: a `test` segment, or one that says test the way /// `testFixtures` and `integrationTest` do. @@ -2438,6 +2476,9 @@ private static boolean looksLikeATestRoot(String path) { private static java.util.List elementValues(String xml, String name) { java.util.List out = new java.util.ArrayList<>(); + if (xml == null) { + return out; + } String open = "<" + name + ">"; String shut = ""; int at = xml.indexOf(open); @@ -2463,11 +2504,16 @@ private java.util.List mainSourceRoots(String projectDir) { // class is the one file this list cannot afford to miss: without it // nothing knows which hints an annotation owns, and Add writes the // duplicate the next build refuses. - for (String declared : declaredSourceRoots(pomText())) { - String path = declared.startsWith("/") || declared.indexOf(':') == 1 - ? declared : projectDir + "/" + declared; - if (!candidates.contains(path)) { - candidates.add(path); + // The whole chain: Maven inherits and plugin + // configuration from the parent, and a relative one resolves against + // THIS module rather than the module that declared it. + for (String pom : pomChain()) { + for (String declared : declaredSourceRoots(pom)) { + String path = declared.startsWith("/") || declared.indexOf(':') == 1 + ? declared : normalizePath(projectDir + "/" + declared); + if (!candidates.contains(path)) { + candidates.add(path); + } } } java.util.List out = new java.util.ArrayList<>(); diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 4ba7857efe8..1fcd1ebad99 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1720,6 +1720,30 @@ public void theRootsThePomDeclaresAreSearchedToo() { assertFalse(withTests.contains("src/test/java"), withTests.toString()); assertFalse(withTests.contains("src/integrationTest/java"), withTests.toString()); + // `` and `` are ordinary words: another plugin naming + // a directory in one is not saying it is compiled. + java.util.List unrelated = CodenameOneSettings.declaredSourceRoots( + "" + + "some-other-plugin" + + "src/main/templates" + + "" + + ""); + assertTrue(unrelated.isEmpty(), unrelated.toString()); + + // build-helper's add-test-source uses the same element as add-source. + java.util.List helper = CodenameOneSettings.declaredSourceRoots( + "" + + "build-helper-maven-plugin" + + "add-source" + + "gen/main" + + "add-test-source" + + "gen/fixtures" + + "" + + "" + + ""); + assertTrue(helper.contains("gen/main"), helper.toString()); + assertFalse(helper.contains("gen/fixtures"), helper.toString()); + // What it cannot resolve it leaves alone rather than guessing. assertTrue(CodenameOneSettings.declaredSourceRoots( "${basedir}/x") From 871dce608ae3dd4ee904d79116ddea655f4a1059 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 08:09:02 +0300 Subject: [PATCH 111/115] Take the compile goal's dirs, and resolve the paths Maven always resolves The build-helper filter went in without its Kotlin twin, so every `` counted -- including a `test-compile` execution's. A test directory whose NAME does not look like one, `fixtures` say, was then read as production code and shadowed a real annotation. Both plugins go through one step now, which takes the executions bound to the main goal, or the whole element when there are none, since plugin-level configuration applies to every goal. The goal is matched as an ELEMENT: `test-compile` contains `compile`, so a substring test takes exactly the executions it must not. And `${project.basedir}/appsrc` is a deterministic path, not an unresolvable one. The project-directory expressions Maven resolves the same way every time are applied, and only what is left over makes a root unusable. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 103 ++++++++++++++---- .../settings/BuildHintCatalogTest.java | 41 ++++++- 2 files changed, 116 insertions(+), 28 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 654587d0569..ed1d5c33c6c 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2413,25 +2413,69 @@ static java.util.List declaredSourceRoots(String pomText) { // compiled, and treating it as a root put the templates back in the // sweep that the root list had just taken them out of. collectRoots(elementValues(pomText, "sourceDirectory"), out); - collectRoots(elementValues(pluginBlock(pomText, "kotlin-maven-plugin"), "sourceDir"), out); + collectRoots(compileGoalConfiguration(pluginBlock(pomText, "kotlin-maven-plugin"), + "compile"), "sourceDir", out); String helper = pluginBlock(pomText, "build-helper-maven-plugin"); if (helper != null) { // add-test-source uses the same element, so an execution that adds // TEST sources is passed over -- the same distinction the Kotlin // plugin's compile and test-compile executions need. - for (String execution : executionsOf(helper)) { - if (execution.indexOf("add-test-source") < 0) { - collectRoots(elementValues(execution, "source"), out); - } + collectRoots(compileGoalConfiguration(helper, "add-source"), "source", out); + } + return out; + } + + /// The parts of a plugin element whose configuration applies to the MAIN + /// compilation: its executions bound to `goal`, or the whole element when it + /// declares no executions, since plugin-level configuration applies to every + /// goal including that one. + /// + /// The test goals use the same elements -- `test-compile` for Kotlin, + /// `add-test-source` for build-helper -- and a test directory whose name + /// does not look like one, `fixtures` say, is otherwise read as production + /// code and shadows a real annotation. + private static java.util.List compileGoalConfiguration(String pluginBlock, + String goal) { + java.util.List out = new java.util.ArrayList<>(); + if (pluginBlock == null) { + return out; + } + boolean any = false; + int at = pluginBlock.indexOf(""); + while (at >= 0) { + int close = pluginBlock.indexOf("", at); + if (close < 0) { + break; } + any = true; + String execution = pluginBlock.substring(at, close); + // The goal as an ELEMENT: `test-compile` contains `compile`, so a + // substring test takes exactly the executions it must not. + if (execution.indexOf("" + goal + "") >= 0) { + out.add(execution); + } + at = pluginBlock.indexOf("", close); + } + if (!any) { + out.add(pluginBlock); } return out; } + private static void collectRoots(java.util.List blocks, String element, + java.util.List out) { + for (String block : blocks) { + collectRoots(elementValues(block, element), out); + } + } + private static void collectRoots(java.util.List values, java.util.List out) { for (String value : values) { String path = value.trim().replace('\\', '/'); - if (path.isEmpty() || path.indexOf('$') >= 0 || looksLikeATestRoot(path)) { + // A `${project.basedir}` prefix is deterministic and common; only + // an expression this reader cannot resolve makes the path unusable. + if (path.isEmpty() || expandProjectPaths(path, "/probe") == null + || looksLikeATestRoot(path)) { continue; } if (!out.contains(path)) { @@ -2440,23 +2484,32 @@ private static void collectRoots(java.util.List values, java.util.List` blocks in a plugin element, or the whole element when - /// it has none -- configuration at plugin level applies to every goal. - private static java.util.List executionsOf(String pluginBlock) { - java.util.List out = new java.util.ArrayList<>(); - int at = pluginBlock.indexOf(""); - while (at >= 0) { - int close = pluginBlock.indexOf("", at); - if (close < 0) { - break; + + /// `path` with the project-directory expressions Maven resolves the same way + /// every time applied, or null when an expression is left that this reader + /// cannot resolve. + static String expandProjectPaths(String path, String projectDir) { + String out = path; + out = replaceLiteral(out, "${project.basedir}", projectDir); + out = replaceLiteral(out, "${project.baseDir}", projectDir); + out = replaceLiteral(out, "${basedir}", projectDir); + out = replaceLiteral(out, "${pom.basedir}", projectDir); + out = replaceLiteral(out, "${project.build.directory}", projectDir + "/target"); + return out.indexOf('$') >= 0 ? null : out; + } + + private static String replaceLiteral(String text, String find, String with) { + StringBuilder out = new StringBuilder(); + int at = 0; + while (true) { + int hit = text.indexOf(find, at); + if (hit < 0) { + out.append(text.substring(at)); + return out.toString(); } - out.add(pluginBlock.substring(at, close)); - at = pluginBlock.indexOf("", close); + out.append(text, at, hit).append(with); + at = hit + find.length(); } - if (out.isEmpty()) { - out.add(pluginBlock); - } - return out; } /// Whether a declared path is a test tree, by the same convention the source @@ -2509,8 +2562,12 @@ private java.util.List mainSourceRoots(String projectDir) { // THIS module rather than the module that declared it. for (String pom : pomChain()) { for (String declared : declaredSourceRoots(pom)) { - String path = declared.startsWith("/") || declared.indexOf(':') == 1 - ? declared : normalizePath(projectDir + "/" + declared); + String expanded = expandProjectPaths(declared, projectDir); + if (expanded == null) { + continue; + } + String path = expanded.startsWith("/") || expanded.indexOf(':') == 1 + ? normalizePath(expanded) : normalizePath(projectDir + "/" + expanded); if (!candidates.contains(path)) { candidates.add(path); } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 1fcd1ebad99..393b1034e43 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1692,7 +1692,8 @@ public void theRootsThePomDeclaresAreSearchedToo() { + "appsrc" + "" + "build-helper-maven-plugin" - + "" + + "add-source" + + "" + "src/generated/java" + "" + "kotlin-maven-plugin" @@ -1712,7 +1713,8 @@ public void theRootsThePomDeclaresAreSearchedToo() { + "appsrc" + "src/test/java" + "build-helper-maven-plugin" - + "" + + "add-source" + + "" + "src/integrationTest/java" + "" + ""); @@ -1744,10 +1746,39 @@ public void theRootsThePomDeclaresAreSearchedToo() { assertTrue(helper.contains("gen/main"), helper.toString()); assertFalse(helper.contains("gen/fixtures"), helper.toString()); - // What it cannot resolve it leaves alone rather than guessing. + // The Kotlin plugin's test-compile execution uses the same element, and + // a test directory whose NAME does not look like one is otherwise read + // as production code. + java.util.List kotlin = CodenameOneSettings.declaredSourceRoots( + "" + + "kotlin-maven-plugin" + + "compile" + + "src/main/kt" + + "" + + "test-compile" + + "fixtures" + + "" + + ""); + assertTrue(kotlin.contains("src/main/kt"), kotlin.toString()); + assertFalse(kotlin.contains("fixtures"), kotlin.toString()); + + // A project-directory expression is deterministic, so it is resolved + // rather than discarded. + java.util.List expression = CodenameOneSettings.declaredSourceRoots( + "${project.basedir}/appsrc" + + ""); + assertTrue(expression.contains("${project.basedir}/appsrc"), expression.toString()); + assertEquals("/p/common/appsrc", + CodenameOneSettings.expandProjectPaths("${project.basedir}/appsrc", "/p/common")); + assertEquals("/p/common/target/generated", + CodenameOneSettings.expandProjectPaths("${project.build.directory}/generated", + "/p/common")); + + // What it still cannot resolve it leaves alone rather than guessing. assertTrue(CodenameOneSettings.declaredSourceRoots( - "${basedir}/x") - .isEmpty()); + "${custom.dir}/x" + + "").isEmpty()); + assertNull(CodenameOneSettings.expandProjectPaths("${custom.dir}/x", "/p/common")); assertTrue(CodenameOneSettings.declaredSourceRoots(null).isEmpty()); } From 9680eb81255e57aa2f16cd42c39154ee0aeca378 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 08:18:03 +0300 Subject: [PATCH 112/115] Plugin-level configuration counts, and `target` is not always target Filtering to the matching executions threw away the plugin-level configuration around them, and Maven applies that to every execution -- so a `` written once outside the executions was dropped and a main class compiled from there became invisible. Both are collected now: the element with its `` span removed, plus the executions bound to the main goal. And `${project.build.directory}` is `target` by default and whatever `` says otherwise. Hard-coding it sent the search to a directory a project that overrides it does not compile from. Read as a DIRECT child of ``, since `` and the plugin sections carry `` elements of their own and the first one in the element is usually a resource directory. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 89 +++++++++++++++++-- .../settings/BuildHintCatalogTest.java | 53 +++++++++++ 2 files changed, 135 insertions(+), 7 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index ed1d5c33c6c..82c281f7d52 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2440,14 +2440,22 @@ private static java.util.List compileGoalConfiguration(String pluginBloc if (pluginBlock == null) { return out; } - boolean any = false; + // Plugin-level configuration applies to every execution, so it counts + // whether or not there are any -- returning only the matching executions + // dropped a `` written once outside them, and a main class + // compiled from there became invisible. + int executions = pluginBlock.indexOf(""); + int endOfExecutions = pluginBlock.indexOf(""); + out.add(executions >= 0 && endOfExecutions > executions + ? pluginBlock.substring(0, executions) + + pluginBlock.substring(endOfExecutions) + : pluginBlock); int at = pluginBlock.indexOf(""); while (at >= 0) { int close = pluginBlock.indexOf("", at); if (close < 0) { break; } - any = true; String execution = pluginBlock.substring(at, close); // The goal as an ELEMENT: `test-compile` contains `compile`, so a // substring test takes exactly the executions it must not. @@ -2456,9 +2464,6 @@ private static java.util.List compileGoalConfiguration(String pluginBloc } at = pluginBlock.indexOf("", close); } - if (!any) { - out.add(pluginBlock); - } return out; } @@ -2489,15 +2494,72 @@ private static void collectRoots(java.util.List values, java.util.List` says otherwise -- hard-coding `target` sent the + /// search to a directory a project that overrides it does not compile from. + static String expandProjectPaths(String path, String projectDir, String buildDirectory) { + String build = buildDirectory == null || buildDirectory.trim().isEmpty() + ? projectDir + "/target" : buildDirectory.trim().replace('\\', '/'); + if (!build.startsWith("/") && build.indexOf(':') != 1) { + build = projectDir + "/" + build; + } String out = path; out = replaceLiteral(out, "${project.basedir}", projectDir); out = replaceLiteral(out, "${project.baseDir}", projectDir); out = replaceLiteral(out, "${basedir}", projectDir); out = replaceLiteral(out, "${pom.basedir}", projectDir); - out = replaceLiteral(out, "${project.build.directory}", projectDir + "/target"); + out = replaceLiteral(out, "${project.build.directory}", build); return out.indexOf('$') >= 0 ? null : out; } + /// The `` the POM's `` element configures, or null. + /// + /// A DIRECT child: `` and the plugin sections carry + /// `` elements of their own, and taking the first one in the + /// build element would read a resource directory as the output directory. + static String configuredBuildDirectory(String pomText) { + if (pomText == null) { + return null; + } + int at = pomText.indexOf(""); + if (at < 0) { + return null; + } + int close = pomText.indexOf("", at); + String build = close < 0 ? pomText.substring(at) : pomText.substring(at, close); + for (String nested : new String[] {"resources", "testResources", "plugins", + "pluginManagement", "filters", "extensions"}) { + build = withoutElement(build, nested); + } + return elementValue(build, "directory"); + } + + private static String withoutElement(String xml, String name) { + String open = "<" + name + ">"; + String shut = ""; + StringBuilder out = new StringBuilder(); + int at = 0; + while (true) { + int hit = xml.indexOf(open, at); + if (hit < 0) { + out.append(xml.substring(at)); + return out.toString(); + } + out.append(xml, at, hit); + int close = xml.indexOf(shut, hit); + if (close < 0) { + return out.toString(); + } + at = close + shut.length(); + } + } + private static String replaceLiteral(String text, String find, String with) { StringBuilder out = new StringBuilder(); int at = 0; @@ -2562,7 +2624,7 @@ private java.util.List mainSourceRoots(String projectDir) { // THIS module rather than the module that declared it. for (String pom : pomChain()) { for (String declared : declaredSourceRoots(pom)) { - String expanded = expandProjectPaths(declared, projectDir); + String expanded = expandProjectPaths(declared, projectDir, buildDirectory()); if (expanded == null) { continue; } @@ -2657,6 +2719,19 @@ static String normalizePath(String path) { return out.toString(); } + /// The build directory the POM chain configures, nearest first, or null for + /// Maven's own default. + private String buildDirectory() { + for (String pom : pomChain()) { + String configured = configuredBuildDirectory(pom); + if (configured != null && !configured.trim().isEmpty() + && configured.indexOf('$') < 0) { + return configured; + } + } + return null; + } + /// The bound POM's text, read once per session. private String pomText() { if (!pomTextRead) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 393b1034e43..0f7ffad5c15 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1762,6 +1762,25 @@ public void theRootsThePomDeclaresAreSearchedToo() { assertTrue(kotlin.contains("src/main/kt"), kotlin.toString()); assertFalse(kotlin.contains("fixtures"), kotlin.toString()); + // Plugin-level configuration applies to every execution, so it counts + // alongside them rather than being dropped when executions exist. + java.util.List both = CodenameOneSettings.declaredSourceRoots( + "" + + "kotlin-maven-plugin" + + "src/shared/kt" + + "" + + "" + + "compile" + + "src/main/kt" + + "" + + "test-compile" + + "fixtures" + + "" + + ""); + assertTrue(both.contains("src/shared/kt"), both.toString()); + assertTrue(both.contains("src/main/kt"), both.toString()); + assertFalse(both.contains("fixtures"), both.toString()); + // A project-directory expression is deterministic, so it is resolved // rather than discarded. java.util.List expression = CodenameOneSettings.declaredSourceRoots( @@ -1840,4 +1859,38 @@ public void theParentPomIsPartOfTheChain() { CodenameOneSettings.normalizePath("/p/common/./../a/pom.xml")); assertEquals("a/pom.xml", CodenameOneSettings.normalizePath("b/../a/pom.xml")); } + + /// `${project.build.directory}` is `target` by default and whatever + /// `` says otherwise -- hard-coding `target` sent the + /// search to a directory a project that overrides it does not compile from. + @Test + public void theConfiguredBuildDirectoryIsUsed() { + assertEquals("out", CodenameOneSettings.configuredBuildDirectory( + "out")); + assertEquals("/p/common/out/generated-sources", + CodenameOneSettings.expandProjectPaths( + "${project.build.directory}/generated-sources", "/p/common", "out")); + + // The default when nothing configures one. + assertNull(CodenameOneSettings.configuredBuildDirectory("")); + assertEquals("/p/common/target/generated-sources", + CodenameOneSettings.expandProjectPaths( + "${project.build.directory}/generated-sources", "/p/common", null)); + + // A DIRECT child: resources and the plugin sections carry `` + // elements of their own, and taking the first would read a resource + // directory as the output directory. + assertNull(CodenameOneSettings.configuredBuildDirectory( + "" + + "src/main/resources" + + "")); + assertEquals("out", CodenameOneSettings.configuredBuildDirectory( + "" + + "src/main/resources" + + "out")); + + // An absolute one is taken as it stands. + assertEquals("/elsewhere/gen", CodenameOneSettings.expandProjectPaths( + "${project.build.directory}/gen", "/p/common", "/elsewhere")); + } } From d37020dbd2022fa03bb968f1d234a0bab9960c01 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 08:27:46 +0300 Subject: [PATCH 113/115] An inactive profile is not the build, and a build directory may say ${} Every `` in the file counted, profiles included, so an inactive `src/preview` was read as a production root and a type kept there shadowed the real annotation. Only the configuration in effect without being asked for is read now: the document with its `` removed, plus any profile that says it is active by default. A profile this reader cannot evaluate is left out rather than merged in -- activation turns on properties, files, the JDK and the OS, and this tool has a model for none of them. Applied to the encoding lookup as well, which had the same hole. And `${project.basedir}/out` is legal and resolvable, while the guard I wrote discarded anything with a `$` in it. The basedir family is applied to it -- and deliberately not `${project.build.directory}`, since the value being read IS that directory and the general expander resolves it to `target`, which would make a self-reference quietly mean the default. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/CodenameOneSettings.java | 79 +++++++++++++++++-- .../settings/BuildHintCatalogTest.java | 47 +++++++++++ 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 82c281f7d52..06256ff4ec1 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2403,10 +2403,44 @@ static java.util.List candidateSourceRoots(String projectDir, boolean ha /// through the same elements, and one of them shadowing a production type is /// the failure this list exists to avoid. static java.util.List declaredSourceRoots(String pomText) { + java.util.List out = new java.util.ArrayList<>(); + for (String active : activeConfiguration(pomText)) { + collectDeclaredRoots(active, out); + } + return out; + } + + /// The parts of a POM whose configuration is in effect without being asked + /// for: the document with its `` removed, and any profile that + /// says it is active by default. + /// + /// A profile this reader cannot evaluate is left out rather than merged in. + /// An inactive `src/preview` was being + /// read as a production root, so a type kept there shadowed the real + /// annotation -- and activation depends on properties, files, the JDK and + /// the OS, none of which this tool has a model for. + static java.util.List activeConfiguration(String pomText) { java.util.List out = new java.util.ArrayList<>(); if (pomText == null) { return out; } + out.add(withoutElement(pomText, "profiles")); + int at = pomText.indexOf(""); + while (at >= 0) { + int close = pomText.indexOf("", at); + if (close < 0) { + break; + } + String profile = pomText.substring(at, close); + if (profile.indexOf("true") >= 0) { + out.add(profile); + } + at = pomText.indexOf("", close); + } + return out; + } + + private static void collectDeclaredRoots(String pomText, java.util.List out) { // Scoped to the elements that actually declare a compile root. // `` and `` are ordinary words: another plugin // naming src/main/templates in one of them is not saying it is @@ -2422,7 +2456,6 @@ static java.util.List declaredSourceRoots(String pomText) { // plugin's compile and test-compile executions need. collectRoots(compileGoalConfiguration(helper, "add-source"), "source", out); } - return out; } /// The parts of a plugin element whose configuration applies to the MAIN @@ -2624,7 +2657,7 @@ private java.util.List mainSourceRoots(String projectDir) { // THIS module rather than the module that declared it. for (String pom : pomChain()) { for (String declared : declaredSourceRoots(pom)) { - String expanded = expandProjectPaths(declared, projectDir, buildDirectory()); + String expanded = expandProjectPaths(declared, projectDir, buildDirectory(projectDir)); if (expanded == null) { continue; } @@ -2721,17 +2754,42 @@ static String normalizePath(String path) { /// The build directory the POM chain configures, nearest first, or null for /// Maven's own default. - private String buildDirectory() { + private String buildDirectory(String projectDir) { for (String pom : pomChain()) { - String configured = configuredBuildDirectory(pom); - if (configured != null && !configured.trim().isEmpty() - && configured.indexOf('$') < 0) { - return configured; + for (String active : activeConfiguration(pom)) { + String configured = configuredBuildDirectory(active); + if (configured == null || configured.trim().isEmpty()) { + continue; + } + // `${project.basedir}/out` is legal and + // resolvable -- discarding it sent the search to `target` for a + // project that compiles somewhere else. Only the basedir family + // is applied here: the value being read IS the build directory, + // so expanding a reference to it would be circular. + String expanded = expandBasedir(configured.trim(), projectDir); + if (expanded != null) { + return expanded; + } } } return null; } + /// The basedir family only, which is what a build directory may name. + /// + /// Not `${project.build.directory}`: the value being read IS the build + /// directory, so expanding a reference to it would be circular -- and the + /// general expander resolves that one to `target`, which would quietly make + /// a self-reference mean the default. + static String expandBasedir(String value, String projectDir) { + String out = value; + out = replaceLiteral(out, "${project.basedir}", projectDir); + out = replaceLiteral(out, "${project.baseDir}", projectDir); + out = replaceLiteral(out, "${basedir}", projectDir); + out = replaceLiteral(out, "${pom.basedir}", projectDir); + return out.indexOf('$') >= 0 ? null : out; + } + /// The bound POM's text, read once per session. private String pomText() { if (!pomTextRead) { @@ -3439,7 +3497,12 @@ private String declaredSourceEncoding() { // multi-module Codename One project puts it -- so looking only at // the bound POM found nothing in the standard layout. for (String pom : pomChain()) { - sourceEncoding = declaredSourceEncoding(pom); + for (String active : activeConfiguration(pom)) { + sourceEncoding = declaredSourceEncoding(active); + if (sourceEncoding != null) { + break; + } + } if (sourceEncoding != null) { break; } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 0f7ffad5c15..9ef1bc7c6ec 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1893,4 +1893,51 @@ public void theConfiguredBuildDirectoryIsUsed() { assertEquals("/elsewhere/gen", CodenameOneSettings.expandProjectPaths( "${project.build.directory}/gen", "/p/common", "/elsewhere")); } + + /// A profile this reader cannot evaluate is left out rather than merged in. + /// An inactive `src/preview` was read as + /// a production root, so a type kept there shadowed the real annotation -- + /// and activation depends on properties, files, the JDK and the OS, none of + /// which this tool has a model for. + @Test + public void anInactiveProfileIsNotTheBuild() { + String pom = "appsrc" + + "" + + "previewpreview" + + "" + + "src/preview" + + "always" + + "true" + + "src/always" + + ""; + java.util.List roots = CodenameOneSettings.declaredSourceRoots(pom); + assertTrue(roots.contains("appsrc"), roots.toString()); + // Active by default is knowable, so it counts. + assertTrue(roots.contains("src/always"), roots.toString()); + // Conditionally active is not, so it does not. + assertFalse(roots.contains("src/preview"), roots.toString()); + } + + /// `${project.basedir}/out` is legal and resolvable; + /// discarding it sent the search to `target` for a project that compiles + /// somewhere else. + @Test + public void theBuildDirectoryMayUseAnExpression() { + assertEquals("${project.basedir}/out", CodenameOneSettings.configuredBuildDirectory( + "${project.basedir}/out")); + assertEquals("/p/common/out/gen", CodenameOneSettings.expandProjectPaths( + "${project.build.directory}/gen", "/p/common", "/p/common/out")); + + // The expansion the build directory itself gets: the basedir family. + assertEquals("/p/common/out", + CodenameOneSettings.expandBasedir("${project.basedir}/out", "/p/common")); + assertEquals("/p/common/out", + CodenameOneSettings.expandBasedir("${basedir}/out", "/p/common")); + assertEquals("out", CodenameOneSettings.expandBasedir("out", "/p/common")); + + // NOT a reference to itself: the general expander resolves that one to + // `target`, which would quietly make a self-reference mean the default. + assertNull(CodenameOneSettings.expandBasedir("${project.build.directory}/x", "/p/common")); + assertNull(CodenameOneSettings.expandBasedir("${custom.dir}/x", "/p/common")); + } } From 9c3adfb0d9abf2142da0516536f118b8f26bbf36 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 08:38:09 +0300 Subject: [PATCH 114/115] Let Maven answer, instead of inferring the project from POM text Settings has been reading POMs to work out where a module's sources are and what they are written in, and every approximation in that reading has been a finding: an inherited declaration, an unresolved property, a plugin's executions, a configured build directory, an inactive profile. An active profile is the next one, and it is not knowable from the text at all -- activation turns on properties, files, the JDK and the OS. So the launcher says. `cn1:settings` writes the compile source roots and the source encoding Maven resolved into the binding, taking them from the reactor module whose directory is the one being edited rather than from the project the goal happens to be running on, which in a multi-module build is the root. The POM reading stays as the fallback, for a Settings launched by an older plugin or run standalone, and a binding that says nothing gets exactly that behaviour -- so this narrows the guesswork rather than replacing one guess with another. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/OpenSettingsMojo.java | 74 ++++++++++++++++++- .../codename1/maven/OpenSettingsMojoTest.java | 43 +++++++++++ .../settings/CodenameOneSettings.java | 8 ++ .../settings/project/ProjectBinding.java | 21 ++++++ .../com/codename1/settings/ProjectIOTest.java | 22 ++++++ 5 files changed, 167 insertions(+), 1 deletion(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java index f873958bda6..da4967ac0a0 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java @@ -40,6 +40,9 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.List; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.MavenProject; import java.util.UUID; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -239,7 +242,17 @@ void writeBinding(File inputFile, File projectDir) throws MojoExecutionException + "projectDir=" + projectDir.getAbsolutePath() + "\n" + "settings=" + new File(projectDir, "codenameone_settings.properties").getAbsolutePath() + "\n" + "pom=" + new File(projectDir, "pom.xml").getAbsolutePath() + "\n" - + "multimoduleRoot=" + root.getAbsolutePath() + "\n"; + + "multimoduleRoot=" + root.getAbsolutePath() + "\n" + // What Maven RESOLVED, so the tool does not have to infer it + // from POM text. It has no model: it cannot evaluate a profile + // activation, follow an inherited or expand a + // property, and every one of those has been a way for it to miss + // the main class and then offer an annotation-owned hint for + // editing. Its own reading stays as the fallback for a Settings + // launched without these -- an older plugin, or the standalone + // app. + + bindingList("sourceRoots", compileSourceRoots(moduleAt(projectDir))) + + bindingValue("sourceEncoding", sourceEncodingOf(moduleAt(projectDir))); try { FileUtils.write(inputFile, content, StandardCharsets.UTF_8); } catch (IOException ex) { @@ -247,6 +260,65 @@ void writeBinding(File inputFile, File projectDir) throws MojoExecutionException } } + /// The reactor module whose directory is `projectDir`, or the project being + /// built when the reactor has no such module. + /// + /// `cn1:settings` is normally run from the root of a multi-module project + /// while the module being EDITED is common, so the project in scope is not + /// the one whose sources matter. + private MavenProject moduleAt(File projectDir) { + if (projectDir == null) { + return project; + } + MavenSession session = getSession(); + List projects = session == null ? null : session.getProjects(); + if (projects != null) { + for (MavenProject candidate : projects) { + File basedir = candidate.getBasedir(); + if (basedir != null + && basedir.getAbsolutePath().equals(projectDir.getAbsolutePath())) { + return candidate; + } + } + } + return project; + } + + /// The source encoding Maven resolved for `module`, or null. + private static String sourceEncodingOf(MavenProject module) { + if (module == null || module.getProperties() == null) { + return null; + } + String encoding = module.getProperties().getProperty("project.build.sourceEncoding"); + if (encoding == null) { + encoding = module.getProperties().getProperty("maven.compiler.encoding"); + } + return encoding == null || encoding.trim().isEmpty() ? null : encoding.trim(); + } + + private static String bindingValue(String key, String value) { + return value == null ? "" : key + "=" + value + "\n"; + } + + private static String bindingList(String key, List values) { + if (values == null || values.isEmpty()) { + return ""; + } + StringBuilder joined = new StringBuilder(); + for (String value : values) { + if (value == null || value.trim().isEmpty()) { + continue; + } + if (joined.length() > 0) { + // A path separator, since these are paths and a comma is legal + // in a directory name. + joined.append(File.pathSeparatorChar); + } + joined.append(value.trim()); + } + return joined.length() == 0 ? "" : key + "=" + joined + "\n"; + } + File multimoduleRoot(File projectDir) { File parent = projectDir == null ? null : projectDir.getParentFile(); if (parent != null && "common".equals(projectDir.getName())) { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenSettingsMojoTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenSettingsMojoTest.java index 2dc23db7330..7dfaf2f9c61 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenSettingsMojoTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenSettingsMojoTest.java @@ -157,6 +157,49 @@ public void bindingContainsProjectFilesAndMultimoduleRoot() throws Exception { assertTrue(binding.contains("multimoduleRoot=" + root.getAbsolutePath())); } + /// What Maven RESOLVED, so the Settings tool does not have to infer it from + /// POM text. It has no model: it cannot evaluate a profile activation, + /// follow an inherited `` or expand a property, and each of + /// those has been a way for it to miss the main class and then offer an + /// annotation-owned hint for editing. + @Test + public void bindingCarriesTheResolvedSourceRootsAndEncoding() throws Exception { + File root = tmp.newFolder("resolved"); + File common = new File(root, "common"); + assertTrue(new File(common, "src/main/java").mkdirs()); + assertTrue(new File(common, "appsrc").mkdirs()); + File input = tmp.newFile("resolved.input"); + + OpenSettingsMojo mojo = new OpenSettingsMojo(); + mojo.project = projectAt(common); + mojo.project.addCompileSourceRoot(new File(common, "appsrc").getAbsolutePath()); + mojo.project.getProperties().setProperty("project.build.sourceEncoding", "Shift_JIS"); + + mojo.writeBinding(input, common); + + String binding = new String(Files.readAllBytes(input.toPath()), StandardCharsets.UTF_8); + assertTrue(binding, binding.contains("sourceRoots=")); + assertTrue(binding, binding.contains(new File(common, "src/main/java").getAbsolutePath())); + assertTrue(binding, binding.contains(new File(common, "appsrc").getAbsolutePath())); + assertTrue(binding, binding.contains("sourceEncoding=Shift_JIS")); + } + + /// A project that resolves neither says neither, and the tool falls back to + /// reading the POM itself rather than being handed an empty answer. + @Test + public void bindingOmitsWhatItCannotResolve() throws Exception { + File root = tmp.newFolder("unresolved"); + File common = new File(root, "common"); + assertTrue(common.mkdirs()); + File input = tmp.newFile("unresolved.input"); + + new OpenSettingsMojo().writeBinding(input, common); + + String binding = new String(Files.readAllBytes(input.toPath()), StandardCharsets.UTF_8); + assertFalse(binding, binding.contains("sourceRoots=")); + assertFalse(binding, binding.contains("sourceEncoding=")); + } + @Test public void pluginVersionUsesCodenameOneVersionInsteadOfApplicationVersion() { OpenSettingsMojo mojo = new OpenSettingsMojo(); diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 06256ff4ec1..3b4cb15c025 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -3492,6 +3492,14 @@ private static boolean isDeclarationModifier(String word) { private String declaredSourceEncoding() { if (!sourceEncodingRead) { sourceEncodingRead = true; + // What the launcher resolved, when it did: Maven has already applied + // the profiles, the inheritance and the properties that this reader + // can only approximate. + if (binding != null && binding.sourceEncoding() != null + && !binding.sourceEncoding().isEmpty()) { + sourceEncoding = binding.sourceEncoding(); + return sourceEncoding; + } // The chain, not the module alone: `project.build.sourceEncoding` // is normally declared once in the parent, which is where a // multi-module Codename One project puts it -- so looking only at diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java b/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java index 6b4ea49bfcf..26eceafb851 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/project/ProjectBinding.java @@ -27,6 +27,8 @@ public final class ProjectBinding { private String settings; private String pom; private String multimoduleRoot; + private String sourceRoots; + private String sourceEncoding; public String projectDir() { return projectDir; @@ -44,6 +46,23 @@ public String multimoduleRoot() { return multimoduleRoot; } + /// The compile source roots Maven RESOLVED, path-separator joined, or null + /// when the launcher did not say. + /// + /// The tool can read a POM but it has no model: it cannot evaluate a profile + /// activation, follow an inherited `` or expand an + /// arbitrary property. Where the launcher knows, it says, and the tool's own + /// reading is the fallback. + public String sourceRoots() { + return sourceRoots; + } + + /// The source encoding Maven resolved, or null when the launcher did not + /// say. + public String sourceEncoding() { + return sourceEncoding; + } + public boolean isValid() { return settings != null && settings.length() > 0; } @@ -70,6 +89,8 @@ public static ProjectBinding parse(String content) { case "settings" -> b.settings = val; case "pom" -> b.pom = val; case "multimoduleRoot" -> b.multimoduleRoot = val; + case "sourceRoots" -> b.sourceRoots = val; + case "sourceEncoding" -> b.sourceEncoding = val; default -> { } } diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/ProjectIOTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/ProjectIOTest.java index a10ff1da57b..8ef2bfb8fac 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/ProjectIOTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/ProjectIOTest.java @@ -65,4 +65,26 @@ public void bindingParserKeepsWindowsBackslashesVerbatim() { assertEquals("C:\\Users\\John Smith\\My App", b.projectDir()); assertEquals("C:\\Users\\John Smith\\My App\\codenameone_settings.properties", b.settings()); } + + /// What Maven RESOLVED, so the tool does not have to infer it from POM + /// text: it has no model for profile activation, inheritance or property + /// expansion, and each of those has been a way for it to miss the main + /// class and then offer an annotation-owned hint for editing. + @Test + public void theBindingCarriesTheResolvedSourceRootsAndEncoding() { + ProjectBinding b = ProjectBinding.parse( + "projectDir=/p/common\n" + + "settings=/p/common/codenameone_settings.properties\n" + + "sourceRoots=/p/common/src/main/java:/p/common/appsrc\n" + + "sourceEncoding=Shift_JIS\n"); + assertEquals("/p/common/src/main/java:/p/common/appsrc", b.sourceRoots()); + assertEquals("Shift_JIS", b.sourceEncoding()); + + // A binding written by an older plugin says neither, and the tool falls + // back to reading the POM itself. + ProjectBinding older = ProjectBinding.parse( + "projectDir=/p/common\nsettings=/p/common/codenameone_settings.properties\n"); + assertNull(older.sourceRoots()); + assertNull(older.sourceEncoding()); + } } From 05b28d09acc212d85343aa41250f60c268df6fdb Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 25 Aug 2026 08:46:39 +0300 Subject: [PATCH 115/115] Read the roots the last commit published, and the plugin's encoding The previous commit wrote the resolved source roots into the binding and never read them: the edit that consumed them was lost and only the encoding half was re-applied, so the fix looked complete and did nothing. `mainSourceRoots` prefers them now, falling back to the POM reading when the binding says nothing. And Maven does not copy a plugin parameter into the project's properties, so a POM setting `` inside maven-compiler-plugin -- in a profile, say, which is the case this was added for -- published nothing. The effective plugin configuration is read as well, from the plugin and from an execution bound to `compile` rather than testCompile. `Character.isLetter` turned out to be outside the API subset this class compiles against; the bytecode compliance gate caught it, and the drive letter it was testing for is ASCII anyway. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/codename1/maven/OpenSettingsMojo.java | 68 +++++++++++++++++-- .../codename1/maven/OpenSettingsMojoTest.java | 26 +++++++ .../settings/CodenameOneSettings.java | 49 +++++++++++++ .../settings/BuildHintCatalogTest.java | 17 +++++ 4 files changed, 156 insertions(+), 4 deletions(-) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java index da4967ac0a0..f4f39319f84 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenSettingsMojo.java @@ -286,16 +286,76 @@ private MavenProject moduleAt(File projectDir) { /// The source encoding Maven resolved for `module`, or null. private static String sourceEncodingOf(MavenProject module) { - if (module == null || module.getProperties() == null) { + if (module == null) { return null; } - String encoding = module.getProperties().getProperty("project.build.sourceEncoding"); - if (encoding == null) { - encoding = module.getProperties().getProperty("maven.compiler.encoding"); + String encoding = null; + if (module.getProperties() != null) { + encoding = module.getProperties().getProperty("project.build.sourceEncoding"); + if (encoding == null) { + encoding = module.getProperties().getProperty("maven.compiler.encoding"); + } + } + if (encoding == null || encoding.trim().isEmpty()) { + // Configured on the plugin rather than as a property. Maven does not + // copy a plugin parameter into the project's properties, so a POM + // that sets inside maven-compiler-plugin -- in a profile, + // say -- published nothing here and left the tool guessing. + encoding = compilerPluginEncoding(module); } return encoding == null || encoding.trim().isEmpty() ? null : encoding.trim(); } + /// The `` maven-compiler-plugin is configured with, from the + /// EFFECTIVE model -- so a profile that Maven activated is already folded in. + private static String compilerPluginEncoding(MavenProject module) { + List plugins; + try { + plugins = module.getBuildPlugins(); + } catch (RuntimeException ex) { + return null; + } + if (plugins == null) { + return null; + } + for (org.apache.maven.model.Plugin plugin : plugins) { + if (!"maven-compiler-plugin".equals(plugin.getArtifactId())) { + continue; + } + String fromPlugin = encodingIn(plugin.getConfiguration()); + if (fromPlugin != null) { + return fromPlugin; + } + if (plugin.getExecutions() == null) { + continue; + } + for (org.apache.maven.model.PluginExecution execution : plugin.getExecutions()) { + // The main compilation's, not testCompile's. + if (execution.getGoals() != null && execution.getGoals().contains("compile")) { + String fromExecution = encodingIn(execution.getConfiguration()); + if (fromExecution != null) { + return fromExecution; + } + } + } + } + return null; + } + + private static String encodingIn(Object configuration) { + if (!(configuration instanceof org.codehaus.plexus.util.xml.Xpp3Dom)) { + return null; + } + org.codehaus.plexus.util.xml.Xpp3Dom encoding = + ((org.codehaus.plexus.util.xml.Xpp3Dom) configuration).getChild("encoding"); + if (encoding == null || encoding.getValue() == null) { + return null; + } + String value = encoding.getValue().trim(); + // An unexpanded ${property} is not an encoding. + return value.isEmpty() || value.indexOf('$') >= 0 ? null : value; + } + private static String bindingValue(String key, String value) { return value == null ? "" : key + "=" + value + "\n"; } diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenSettingsMojoTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenSettingsMojoTest.java index 7dfaf2f9c61..4bdf4e061e5 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenSettingsMojoTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenSettingsMojoTest.java @@ -184,6 +184,31 @@ public void bindingCarriesTheResolvedSourceRootsAndEncoding() throws Exception { assertTrue(binding, binding.contains("sourceEncoding=Shift_JIS")); } + /// Maven does not copy a plugin parameter into the project's properties, so + /// a POM that sets `` inside maven-compiler-plugin -- in a profile, + /// say -- published nothing and left the tool guessing. + @Test + public void theCompilerPluginsEncodingIsPublishedToo() throws Exception { + File root = tmp.newFolder("plugin-encoding"); + File common = new File(root, "common"); + assertTrue(new File(common, "src/main/java").mkdirs()); + File input = tmp.newFile("plugin-encoding.input"); + + OpenSettingsMojo mojo = new OpenSettingsMojo(); + mojo.project = projectAt(common); + org.apache.maven.model.Plugin compiler = new org.apache.maven.model.Plugin(); + compiler.setArtifactId("maven-compiler-plugin"); + compiler.setConfiguration(org.codehaus.plexus.util.xml.Xpp3DomBuilder.build( + new java.io.StringReader( + "Shift_JIS"))); + mojo.project.getBuild().addPlugin(compiler); + + mojo.writeBinding(input, common); + + String binding = new String(Files.readAllBytes(input.toPath()), StandardCharsets.UTF_8); + assertTrue(binding, binding.contains("sourceEncoding=Shift_JIS")); + } + /// A project that resolves neither says neither, and the tool falls back to /// reading the POM itself rather than being handed an empty answer. @Test @@ -245,6 +270,7 @@ private File jarWithIcon(String name) throws Exception { private MavenProject projectAt(File basedir) { MavenProject project = new MavenProject(); + project.setBuild(new org.apache.maven.model.Build()); project.setFile(new File(basedir, "pom.xml")); project.addCompileSourceRoot(new File(basedir, "src/main/java").getAbsolutePath()); return project; diff --git a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java index 3b4cb15c025..b88faa8b799 100644 --- a/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java +++ b/scripts/settings/common/src/main/java/com/codename1/settings/CodenameOneSettings.java @@ -2644,6 +2644,19 @@ private static java.util.List elementValues(String xml, String name) { /// Those of them that are there, plus whatever the POM declares. private java.util.List mainSourceRoots(String projectDir) { FileSystemStorage fs = FileSystemStorage.getInstance(); + // What the launcher resolved, when it did. Reading the POM is guesswork + // by comparison -- it cannot evaluate a profile activation, follow an + // inherited or expand an arbitrary property -- so + // where Maven has already answered, that answer is used. + java.util.List resolved = new java.util.ArrayList<>(); + for (String root : splitRoots(binding == null ? null : binding.sourceRoots())) { + if (fs.isDirectory(ProjectIO.fsUrl(root))) { + resolved.add(root); + } + } + if (!resolved.isEmpty()) { + return resolved; + } boolean hasSrcMain = projectDir != null && fs.isDirectory(ProjectIO.fsUrl(projectDir + "/src/main")); java.util.List candidates = @@ -2752,6 +2765,42 @@ static String normalizePath(String path) { return out.toString(); } + /// The roots a binding line names, or empty when it names none. + /// + /// Path-separated, since a comma is legal in a directory name. Both + /// separators are accepted so a binding written on one platform can be read + /// on another, and a bare drive letter is rejoined rather than read as a + /// root of its own. + static java.util.List splitRoots(String joined) { + java.util.List out = new java.util.ArrayList<>(); + if (joined == null || joined.trim().isEmpty()) { + return out; + } + for (String piece : com.codename1.util.StringUtil.tokenize(joined, ":;")) { + String root = piece.trim(); + if (root.isEmpty()) { + continue; + } + // Character.isLetter is outside the Codename One API subset this + // class compiles against, the same reason the name predicate and the + // hex reader are hand-rolled; a drive letter is ASCII anyway. + char first = root.charAt(0); + if (root.length() == 1 + && ((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z'))) { + // `C:/x` tokenizes as `C` then `/x`: the drive letter belongs to + // the path that follows it. + out.add(root + ":"); + continue; + } + if (!out.isEmpty() && out.get(out.size() - 1).endsWith(":")) { + out.set(out.size() - 1, out.get(out.size() - 1) + root); + continue; + } + out.add(root); + } + return out; + } + /// The build directory the POM chain configures, nearest first, or null for /// Maven's own default. private String buildDirectory(String projectDir) { diff --git a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java index 9ef1bc7c6ec..6a13cd6ab00 100644 --- a/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java +++ b/scripts/settings/common/src/test/java/com/codename1/settings/BuildHintCatalogTest.java @@ -1940,4 +1940,21 @@ public void theBuildDirectoryMayUseAnExpression() { assertNull(CodenameOneSettings.expandBasedir("${project.build.directory}/x", "/p/common")); assertNull(CodenameOneSettings.expandBasedir("${custom.dir}/x", "/p/common")); } + + /// The roots the launcher resolved are read, not merely carried. Path + /// separated, since a comma is legal in a directory name. + @Test + public void theResolvedRootsAreParsed() { + assertEquals(java.util.Arrays.asList("/p/common/src/main/java", "/p/common/appsrc"), + CodenameOneSettings.splitRoots("/p/common/src/main/java:/p/common/appsrc")); + assertEquals(java.util.Arrays.asList("/a", "/b"), + CodenameOneSettings.splitRoots("/a;/b")); + + // A Windows drive letter belongs to the path that follows it. + assertEquals(java.util.Arrays.asList("C:/p/src", "D:/other"), + CodenameOneSettings.splitRoots("C:/p/src;D:/other")); + + assertTrue(CodenameOneSettings.splitRoots(null).isEmpty()); + assertTrue(CodenameOneSettings.splitRoots(" ").isEmpty()); + } }