From 6a920c48409df902da9b82478241481a10b9e063 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 23 Oct 2026 05:19:05 +0300 Subject: [PATCH 1/4] Fix release blockers: PMD gate on master and iOS WebRTC compile - FileDialogSupport: the picked == open identity comparison is intentional (showAtPosition returns the Command instance), mark it NOPMD like the existing CompareObjectsWithEquals sites; master's PR CI quality gate fails on it. - IPhoneBuilder: compile CN1WebRTC.m with -fobjc-arc. The WebRTC framework headers declare __weak properties, which clang rejects in a manual-reference-counting file, so every iOS app using com.codename1.webrtc failed to compile. The bridge already supports both memory models. No new dependency. - parley.yml: run on changes to the iOS WebRTC bridge and IPhoneBuilder, since its iOS job is the only one that compiles a WebRTC app. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/parley.yml | 5 +++++ .../src/com/codename1/impl/FileDialogSupport.java | 2 +- .../main/java/com/codename1/builders/IPhoneBuilder.java | 9 ++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/parley.yml b/.github/workflows/parley.yml index 7d0e35871b8..f353ae9ab3b 100644 --- a/.github/workflows/parley.yml +++ b/.github/workflows/parley.yml @@ -42,6 +42,11 @@ on: - 'CodenameOne/src/com/codename1/call/**' - 'CodenameOne/src/com/codename1/webrtc/**' - 'Ports/JavaSE/src/com/codename1/impl/javase/webrtc/**' + # The iOS side of WebRTC: the bridge and the builder that decides how + # it compiles (it needs -fobjc-arc for the framework's __weak headers). + # The iOS job is the only place a WebRTC app is compiled for a device. + - 'Ports/iOSPort/nativeSources/CN1WebRTC.m' + - 'maven/build-engine/src/main/java/com/codename1/builders/IPhoneBuilder.java' - 'CodenameOne/src/com/codename1/io/oidc/**' - 'CodenameOne/src/com/codename1/io/rest/**' # The REST client maps its responses through the mapper registry. diff --git a/CodenameOne/src/com/codename1/impl/FileDialogSupport.java b/CodenameOne/src/com/codename1/impl/FileDialogSupport.java index 2d84134ff5e..f486b380167 100644 --- a/CodenameOne/src/com/codename1/impl/FileDialogSupport.java +++ b/CodenameOne/src/com/codename1/impl/FileDialogSupport.java @@ -108,7 +108,7 @@ public void run() { d.placeButtonCommands(new Command[]{cancel, open}); Command picked = d.showAtPosition(2, 2, 2, 2, true); String[] paths = tree.getSelectedPaths(); - if (picked == open && paths.length > 0) { + if (picked == open && paths.length > 0) { //NOPMD CompareObjectsWithEquals deliver(new FileDialogResult(request, paths, -1, null), callback); } else { deliver(FileDialogResult.cancelled(request), callback); diff --git a/maven/build-engine/src/main/java/com/codename1/builders/IPhoneBuilder.java b/maven/build-engine/src/main/java/com/codename1/builders/IPhoneBuilder.java index c9b016e07f0..ed9df7ea4e3 100644 --- a/maven/build-engine/src/main/java/com/codename1/builders/IPhoneBuilder.java +++ b/maven/build-engine/src/main/java/com/codename1/builders/IPhoneBuilder.java @@ -7673,9 +7673,16 @@ && conditionCovers(governingKey, } + // CN1WebRTC.m compiles under either memory model, but the + // WebRTC framework headers it imports declare __weak + // properties, which clang rejects in a file built with + // ARC off ("cannot create __weak reference in file using + // manual reference counting") -- so every WebRTC app + // failed to compile until it was given -fobjc-arc too. String arcPhaseFixScript = usesCn1Vision || usesCn1Language || usesCn1Inference - ? " arc_sources = ['CN1Vision.m', 'CN1Language.m', 'CN1Inference.m']\n" + || usesWebRtc + ? " arc_sources = ['CN1Vision.m', 'CN1Language.m', 'CN1Inference.m', 'CN1WebRTC.m']\n" + " main_target.source_build_phase.files.each do |bf|\n" + " ref = bf.file_ref\n" + " name = ref && File.basename(ref.path || ref.name || '')\n" From 05f0d391feeb95e39ae190aeb1e62a6dcae12cbf Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 23 Oct 2026 05:51:33 +0300 Subject: [PATCH 2/4] Flutter: fix Android and native builds broken by #6055 - DartList.reversed() clashed with Java 21's List.reversed(), which Android's platform jar (API 35+) carries, so every generated Android project failed in :app:compileReleaseJavaWithJavac. Renamed to reversedIterable(); the emitter and tests follow. - DateTime.parse used java.util.regex, which ParparVM does not provide, so the translated dart_core_DateTime.c failed on every native target. It now uses com.codename1.util.regex, as RegExp already does; the pattern matches and groups identically. Co-Authored-By: Claude Opus 5.5 --- maven/dart-runtime/src/main/java/dart/core/DartList.java | 6 +++++- maven/dart-runtime/src/main/java/dart/core/DateTime.java | 4 ++-- .../src/test/java/dart/core/DartCoreSemanticsTest.java | 6 +++--- .../com/codename1/dart/transpiler/codegen/JavaEmitter.java | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/maven/dart-runtime/src/main/java/dart/core/DartList.java b/maven/dart-runtime/src/main/java/dart/core/DartList.java index 85ef4d3829b..88eded57517 100644 --- a/maven/dart-runtime/src/main/java/dart/core/DartList.java +++ b/maven/dart-runtime/src/main/java/dart/core/DartList.java @@ -709,7 +709,11 @@ public void sortDefault() { sort((Funcs.Func2) null); } - public DartIterable reversed() { + /// Dart's `List.reversed`. Not named `reversed()`: Java 21 added + /// `List.reversed()` returning a `List`, and Android's platform jar (API 35+) + /// carries it, so a method of that name returning an `Iterable` fails to + /// compile in every generated Android project. + public DartIterable reversedIterable() { DartList self = this; return DartIterable.wrap(() -> new java.util.Iterator() { // Dart's ListIterator: the length at creation, checked on every step, so diff --git a/maven/dart-runtime/src/main/java/dart/core/DateTime.java b/maven/dart-runtime/src/main/java/dart/core/DateTime.java index 3dee4848bb5..bbbf1762941 100644 --- a/maven/dart-runtime/src/main/java/dart/core/DateTime.java +++ b/maven/dart-runtime/src/main/java/dart/core/DateTime.java @@ -67,7 +67,7 @@ public static DateTime utc(long year, long month, long day, long hour, long minu /// Dart's own grammar for DateTime.parse: an ISO 8601 date, an optional time /// after `T` or a space, an optional fraction, and an optional `Z` or offset. - private static final java.util.regex.Pattern ISO = java.util.regex.Pattern.compile( + private static final com.codename1.util.regex.Pattern ISO = com.codename1.util.regex.Pattern.compile( "^([+-]?\\d{4,6})-?(\\d\\d)-?(\\d\\d)(?:[ T](\\d\\d)(?::?(\\d\\d)(?::?(\\d\\d)(?:[.,](\\d+))?)?)?" + "( ?[zZ]| ?([-+])(\\d\\d)(?::?(\\d\\d))?)?)?$"); @@ -84,7 +84,7 @@ public static DateTime tryParse(String formattedString) { if (formattedString == null) { return null; } - java.util.regex.Matcher m = ISO.matcher(formattedString); + com.codename1.util.regex.Matcher m = ISO.matcher(formattedString); if (!m.matches()) { return null; } diff --git a/maven/dart-runtime/src/test/java/dart/core/DartCoreSemanticsTest.java b/maven/dart-runtime/src/test/java/dart/core/DartCoreSemanticsTest.java index eca33db31a3..9ea544d5bda 100644 --- a/maven/dart-runtime/src/test/java/dart/core/DartCoreSemanticsTest.java +++ b/maven/dart-runtime/src/test/java/dart/core/DartCoreSemanticsTest.java @@ -1723,7 +1723,7 @@ public void reversedThrowsWhenTheListChangesLengthMidLoop() { grow.add(1L); grow.add(2L); assertThrows(ConcurrentModificationError.class, () -> { - for (Long v : grow.reversed()) { + for (Long v : grow.reversedIterable()) { grow.add(v); } }); @@ -1733,7 +1733,7 @@ public void reversedThrowsWhenTheListChangesLengthMidLoop() { shrink.add(3L); // Shrinking used to read past the new end instead. assertThrows(ConcurrentModificationError.class, () -> { - for (Long v : shrink.reversed()) { + for (Long v : shrink.reversedIterable()) { shrink.removeLast(); } }); @@ -1741,7 +1741,7 @@ public void reversedThrowsWhenTheListChangesLengthMidLoop() { same.add(1L); same.add(2L); StringBuilder seen = new StringBuilder(); - for (Long v : same.reversed()) { + for (Long v : same.reversedIterable()) { same.set(0, v); seen.append(v); } diff --git a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/codegen/JavaEmitter.java b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/codegen/JavaEmitter.java index 5097407e9bd..abc52194ca3 100644 --- a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/codegen/JavaEmitter.java +++ b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/codegen/JavaEmitter.java @@ -5922,7 +5922,7 @@ private Out emitMemberGet(Out target, String name, Node posNode, Ctx ctx) { return new Out(target.code + ".last()", elem); } if (name.equals("reversed")) { - return new Out(target.code + ".reversed()", TypeRef.of("Iterable", elem)); + return new Out(target.code + ".reversedIterable()", TypeRef.of("Iterable", elem)); } } if (tt.is("Map")) { From d0b183d4de12dd4625fed0ff04c5d660eb2fca77 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 23 Oct 2026 07:28:25 +0300 Subject: [PATCH 3/4] Flutter: parse DateTime by hand instead of with a regex engine Using com.codename1.util.regex for DateTime.parse put the whole regex engine into every Flutter app (+1.2% to +2.1% executable code, failing the Flutter benchmark size gates). The grammar is one fixed expression, so it is matched by hand. DateTimeIsoGrammarTest holds the matcher to the original expression on the JVM: same match and same groups, over the known forms and 200,000 generated strings. Co-Authored-By: Claude Opus 5.5 --- .../src/main/java/dart/core/DateTime.java | 152 ++++++++++++++++-- .../dart/core/DateTimeIsoGrammarTest.java | 90 +++++++++++ 2 files changed, 225 insertions(+), 17 deletions(-) create mode 100644 maven/dart-runtime/src/test/java/dart/core/DateTimeIsoGrammarTest.java diff --git a/maven/dart-runtime/src/main/java/dart/core/DateTime.java b/maven/dart-runtime/src/main/java/dart/core/DateTime.java index bbbf1762941..4ed1714da99 100644 --- a/maven/dart-runtime/src/main/java/dart/core/DateTime.java +++ b/maven/dart-runtime/src/main/java/dart/core/DateTime.java @@ -66,10 +66,128 @@ public static DateTime utc(long year, long month, long day, long hour, long minu } /// Dart's own grammar for DateTime.parse: an ISO 8601 date, an optional time - /// after `T` or a space, an optional fraction, and an optional `Z` or offset. - private static final com.codename1.util.regex.Pattern ISO = com.codename1.util.regex.Pattern.compile( - "^([+-]?\\d{4,6})-?(\\d\\d)-?(\\d\\d)(?:[ T](\\d\\d)(?::?(\\d\\d)(?::?(\\d\\d)(?:[.,](\\d+))?)?)?" - + "( ?[zZ]| ?([-+])(\\d\\d)(?::?(\\d\\d))?)?)?$"); + /// after `T` or a space, an optional fraction, and an optional `Z` or offset -- + /// as a regular expression, + /// `^([+-]?\d{4,6})-?(\d\d)-?(\d\d)(?:[ T](\d\d)(?::?(\d\d)(?::?(\d\d)(?:[.,](\d+))?)?)?( ?[zZ]| ?([-+])(\d\d)(?::?(\d\d))?)?)?$`. + /// + /// Matched by hand rather than with a regex engine: `java.util.regex` does not + /// exist on ParparVM, and `com.codename1.util.regex` would add its whole engine + /// to every Flutter app for one date format. Answers the expression's eleven + /// groups (index 0 unused, an unmatched group null), or null for no match. + /// `DateTimeIsoGrammarTest` holds it to the expression above. + static String[] isoGroups(String s) { + // Only the year's length needs backtracking: \d{4,6} is greedy and the + // month may follow it with no separator. Everything after the year is + // deterministic -- every optional part starts with a character nothing + // else could consume at that point. + int start = s.length() > 0 && (s.charAt(0) == '+' || s.charAt(0) == '-') ? 1 : 0; + for (int len = 6; len >= 4; len--) { + String[] g = isoAfterYear(s, start, len); + if (g != null) { + return g; + } + } + return null; + } + + private static String[] isoAfterYear(String s, int start, int yearDigits) { + int n = s.length(); + int i = start + yearDigits; + if (!digits(s, start, yearDigits)) { + return null; + } + String[] g = new String[12]; + g[1] = s.substring(0, i); + if (i < n && s.charAt(i) == '-') { + i++; + } + if (!digits(s, i, 2)) { + return null; + } + g[2] = s.substring(i, i + 2); + i += 2; + if (i < n && s.charAt(i) == '-') { + i++; + } + if (!digits(s, i, 2)) { + return null; + } + g[3] = s.substring(i, i + 2); + i += 2; + if (i == n) { + return g; + } + char sep = s.charAt(i); + if ((sep != ' ' && sep != 'T') || !digits(s, i + 1, 2)) { + return null; + } + g[4] = s.substring(i + 1, i + 3); + i += 3; + int at = colonDigits(s, i); + if (at >= 0) { + g[5] = s.substring(at - 2, at); + i = at; + at = colonDigits(s, i); + if (at >= 0) { + g[6] = s.substring(at - 2, at); + i = at; + if (i < n && (s.charAt(i) == '.' || s.charAt(i) == ',') && digits(s, i + 1, 1)) { + int e = i + 1; + while (e < n && isDigit(s.charAt(e))) { + e++; + } + g[7] = s.substring(i + 1, e); + i = e; + } + } + } + if (i == n) { + return g; + } + int z = i; + if (s.charAt(i) == ' ') { + i++; + } + if (i < n && (s.charAt(i) == 'z' || s.charAt(i) == 'Z')) { + g[8] = s.substring(z, i + 1); + return i + 1 == n ? g : null; + } + if (i >= n || (s.charAt(i) != '+' && s.charAt(i) != '-') || !digits(s, i + 1, 2)) { + return null; + } + g[9] = s.substring(i, i + 1); + g[10] = s.substring(i + 1, i + 3); + int e = colonDigits(s, i + 3); + if (e >= 0) { + g[11] = s.substring(e - 2, e); + } else { + e = i + 3; + } + g[8] = s.substring(z, e); + return e == n ? g : null; + } + + /// `:?(\d\d)` at `i`: the index after it, or -1. + private static int colonDigits(String s, int i) { + int j = i < s.length() && s.charAt(i) == ':' ? i + 1 : i; + return digits(s, j, 2) ? j + 2 : -1; + } + + private static boolean digits(String s, int from, int count) { + if (from + count > s.length()) { + return false; + } + for (int k = from; k < from + count; k++) { + if (!isDigit(s.charAt(k))) { + return false; + } + } + return true; + } + + private static boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } /// `DateTime.parse`: a value with a `Z` or an offset is UTC, anything else local. public static DateTime parse(String formattedString) { @@ -84,19 +202,19 @@ public static DateTime tryParse(String formattedString) { if (formattedString == null) { return null; } - com.codename1.util.regex.Matcher m = ISO.matcher(formattedString); - if (!m.matches()) { + String[] g = isoGroups(formattedString); + if (g == null) { return null; } - long year = Long.parseLong(m.group(1).startsWith("+") ? m.group(1).substring(1) : m.group(1)); - long month = Long.parseLong(m.group(2)); - long day = Long.parseLong(m.group(3)); - long hour = num(m.group(4)); - long minute = num(m.group(5)); - long second = num(m.group(6)); + long year = Long.parseLong(g[1].startsWith("+") ? g[1].substring(1) : g[1]); + long month = Long.parseLong(g[2]); + long day = Long.parseLong(g[3]); + long hour = num(g[4]); + long minute = num(g[5]); + long second = num(g[6]); long milli = 0; long micro = 0; - String frac = m.group(7); + String frac = g[7]; if (frac != null) { String six = (frac + "000000").substring(0, 6); long all = Long.parseLong(six); @@ -108,13 +226,13 @@ public static DateTime tryParse(String formattedString) { // A well-formed date outside DateTime's range is still unparseable: tryParse // answers null (and parse a FormatException), never the constructor's error. try { - if (m.group(8) == null) { + if (g[8] == null) { return new DateTime(year, month, day, hour, minute, second, milli, micro); } DateTime u = utc(year, month, day, hour, minute, second, milli, micro); - if (m.group(9) != null) { - long offset = num(m.group(10)) * 60 + num(m.group(11)); - long sign = m.group(9).equals("-") ? -1 : 1; + if (g[9] != null) { + long offset = num(g[10]) * 60 + num(g[11]); + long sign = g[9].equals("-") ? -1 : 1; long at = u.epochMicros - sign * offset * 60000000L; checkMicrosRange(at); return new DateTime(at, true); diff --git a/maven/dart-runtime/src/test/java/dart/core/DateTimeIsoGrammarTest.java b/maven/dart-runtime/src/test/java/dart/core/DateTimeIsoGrammarTest.java new file mode 100644 index 00000000000..2abb9e92d86 --- /dev/null +++ b/maven/dart-runtime/src/test/java/dart/core/DateTimeIsoGrammarTest.java @@ -0,0 +1,90 @@ +/* + * 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 dart.core; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.jupiter.api.Test; + +/// `DateTime.isoGroups` matches Dart's DateTime.parse grammar by hand, because the +/// device has no `java.util.regex`. The JVM running this test does, so the hand +/// matcher is held to the expression itself: same match, same eleven groups. +class DateTimeIsoGrammarTest { + private static final Pattern ISO = Pattern.compile( + "^([+-]?\\d{4,6})-?(\\d\\d)-?(\\d\\d)(?:[ T](\\d\\d)(?::?(\\d\\d)(?::?(\\d\\d)(?:[.,](\\d+))?)?)?" + + "( ?[zZ]| ?([-+])(\\d\\d)(?::?(\\d\\d))?)?)?$"); + + private static final String[] PIECES = { + "0", "1", "2", "9", "12", "2024", "-", "+", ":", " ", "T", "Z", "z", ".", ",", "x", "01", "30" + }; + + private static void check(String s) { + Matcher m = ISO.matcher(s); + String[] got = DateTime.isoGroups(s); + if (!m.matches()) { + assertEquals(null, got == null ? null : "matched", s); + return; + } + String[] want = new String[12]; + for (int g = 1; g <= 11; g++) { + want[g] = m.group(g); + } + assertArrayEquals(want, got, s); + } + + @Test + void knownForms() { + String[] cases = { + "2024-01-15", "20240115", "2024-01-15T10", "2024-01-15T10Z", "2024-01-15 10:30", + "2024-01-15T1030", "2024-01-15T10:30:00", "2024-01-15T103000.5", "2024-01-15T10:30:00,123456789", + "2024-01-15T10:30:00Z", "2024-01-15T10:30:00 z", "2024-01-15T10:30+05:30", "2024-01-15T10:30 -0800", + "2024-01-15T10:30:00-08", "+123456-01-01T00:00:00.5z", "-002024-01-15", "2024012", "202401155", + "2024-1-15", "2024-01-15T", "2024-01-15T10:", "2024-01-15T10:30:00.", "2024-01-15Z", + "2024-01-15T10:30:00Zjunk", "2024-01-15T10+0", "", "x", "+", "1234567-01-01" + }; + for (String s : cases) { + check(s); + } + } + + @Test + void randomStrings() { + Random r = new Random(42); + for (int i = 0; i < 200000; i++) { + StringBuilder sb = new StringBuilder(); + if (r.nextInt(3) == 0) { + sb.append("2024-01-15T10:30"); + } + int parts = r.nextInt(8); + for (int p = 0; p < parts; p++) { + sb.append(PIECES[r.nextInt(PIECES.length)]); + } + check(sb.toString()); + } + } +} From 252c70b0800c6138e01a993f6ee8c2f57e75e5f2 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 23 Oct 2026 07:58:14 +0300 Subject: [PATCH 4/4] Flutter benchmark: rebaseline JavaScript code size for #6055's runtime The JavaScript build had not compiled since #6055, so master's nightly size predated its Dart runtime; this PR restores the build. Co-Authored-By: Claude Opus 5.5 --- scripts/flutter-bench/baseline/pr/6082.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 scripts/flutter-bench/baseline/pr/6082.json diff --git a/scripts/flutter-bench/baseline/pr/6082.json b/scripts/flutter-bench/baseline/pr/6082.json new file mode 100644 index 00000000000..0ab177a9d5d --- /dev/null +++ b/scripts/flutter-bench/baseline/pr/6082.json @@ -0,0 +1,20 @@ +{ + "pr": 6082, + "rebaseline": { + "javascript": { + "gallery": { + "code_bytes": { + "from": { + "tolerance": { + "value": 0.005 + }, + "value": 13418004 + }, + "reason": "JavaScript Flutter bench has not built since #6055 (java.util.regex absent on ParparVM); master's nightly size predates #6055's Dart runtime. This PR restores the build; the +0.59% is that runtime. DateTime is parsed by hand, no regex engine added.", + "runs": 1, + "value": 13497605 + } + } + } + } +}