Skip to content

Commit 2af6a3f

Browse files
shai-almogclaude
andcommitted
initializr/js-port: drop native-JS zip; build the Generate zip in-Java
With the translator fixes (synchronous-native classification + inherited static-field resolution), the pure-Java zip (net.sf.zipme via STORED) now runs fast on the JavaScript port -- ~100ms for the full ~1MB project zip -- instead of hanging or hitting a null DeflaterConstants static. So the native-JS zip shim (an @JSBody that hand-assembled the archive over the raw arrays) is no longer needed. - Remove buildAndDownloadZip from CodenameOneImplementation/Display/CN and the jsBuildZipDataUrl @JSBody from HTML5Implementation. - GeneratorModel.generate() builds the STORED zip in-Java on every platform (including JS) and delivers it via downloadBytesAsFile, falling back to storage + execute() when unsupported. Verified end-to-end on a locally built JS bundle: clean boot, no errors, Generate downloads a valid 63-entry project zip (unzip -t clean). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6226e4a commit 2af6a3f

5 files changed

Lines changed: 12 additions & 185 deletions

File tree

CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4527,28 +4527,6 @@ public boolean downloadBytesAsFile(String fileName, byte[] bytes) {
45274527
return false;
45284528
}
45294529

4530-
/// Builds a ZIP archive from the given entries and offers it to the user as a
4531-
/// downloadable file, performing the (byte-heavy) archive assembly natively
4532-
/// where that is dramatically faster than a pure-Java zip. This exists for
4533-
/// the JavaScript port: there, every Java method is compiled to a cooperative
4534-
/// generator, so a pure-Java DEFLATE/CRC over multi-megabyte project bytes is
4535-
/// pathologically slow (millions of generator resumes) and effectively hangs.
4536-
/// The JS port overrides this to assemble the zip in native JavaScript over
4537-
/// the underlying arrays. Returns `true` if the platform handled the build and
4538-
/// download, `false` (the default) when unsupported -- callers then fall back
4539-
/// to a normal in-Java zip + {@link #downloadBytesAsFile}.
4540-
///
4541-
/// #### Parameters
4542-
///
4543-
/// - `fileName`: the suggested file name for the download
4544-
///
4545-
/// - `names`: the entry path names, parallel to `data`
4546-
///
4547-
/// - `data`: the entry contents, parallel to `names`
4548-
public boolean buildAndDownloadZip(String fileName, String[] names, byte[][] data) {
4549-
return false;
4550-
}
4551-
45524530
/// Returns one of the density variables appropriate for this device, notice that
45534531
/// density doesn't always correspond to resolution and an implementation might
45544532
/// decide to change the density based on DPI constraints.

CodenameOne/src/com/codename1/ui/CN.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -712,24 +712,6 @@ public static boolean downloadBytesAsFile(String fileName, byte[] bytes) {
712712
return Display.impl.downloadBytesAsFile(fileName, bytes);
713713
}
714714

715-
/// Builds a ZIP from the given entries and offers it as a download, assembling
716-
/// the archive natively where that is far faster than a pure-Java zip (the
717-
/// JavaScript port, whose generator-based execution makes a pure-Java
718-
/// DEFLATE/CRC over multi-megabyte input effectively hang). Returns {@code
719-
/// true} if handled, {@code false} when unsupported -- callers then fall back
720-
/// to an in-Java zip + {@link #downloadBytesAsFile}.
721-
///
722-
/// #### Parameters
723-
///
724-
/// - `fileName`: the suggested file name for the download
725-
///
726-
/// - `names`: the entry path names, parallel to `data`
727-
///
728-
/// - `data`: the entry contents, parallel to `names`
729-
public static boolean buildAndDownloadZip(String fileName, String[] names, byte[][] data) {
730-
return Display.impl.buildAndDownloadZip(fileName, names, data);
731-
}
732-
733715

734716
/// Returns one of the density variables appropriate for this device, notice that
735717
/// density doesn't always correspond to resolution and an implementation might

CodenameOne/src/com/codename1/ui/Display.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3909,24 +3909,6 @@ public boolean downloadBytesAsFile(String fileName, byte[] bytes) {
39093909
return impl.downloadBytesAsFile(fileName, bytes);
39103910
}
39113911

3912-
/// Builds a ZIP from the given entries and offers it as a download, assembling
3913-
/// the archive natively where that is far faster than a pure-Java zip (the
3914-
/// JavaScript port). Returns {@code true} if handled, {@code false} when
3915-
/// unsupported -- callers then fall back to an in-Java zip + {@link
3916-
/// #downloadBytesAsFile}. See {@link
3917-
/// com.codename1.impl.CodenameOneImplementation#buildAndDownloadZip}.
3918-
///
3919-
/// #### Parameters
3920-
///
3921-
/// - `fileName`: the suggested file name for the download
3922-
///
3923-
/// - `names`: the entry path names, parallel to `data`
3924-
///
3925-
/// - `data`: the entry contents, parallel to `names`
3926-
public boolean buildAndDownloadZip(String fileName, String[] names, byte[][] data) {
3927-
return impl.buildAndDownloadZip(fileName, names, data);
3928-
}
3929-
39303912
/// Returns one of the density variables appropriate for this device, notice that
39313913
/// density doesn't always correspond to resolution and an implementation might
39323914
/// decide to change the density based on DPI constraints.

Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java

Lines changed: 0 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -5637,104 +5637,6 @@ public void run() {
56375637
}
56385638

56395639

5640-
/**
5641-
* JS-port-only fast path for "zip these entries and download them". A
5642-
* pure-Java zip (net.sf.zipme) is unusable here: every Java method is
5643-
* compiled to a cooperative generator, so the DEFLATE compress loop -- and
5644-
* even a STORED entry's per-byte CRC32 -- runs millions of generator
5645-
* resumes over multi-megabyte project bytes and effectively hangs. This
5646-
* assembles a STORED zip (with CRC) entirely in native JavaScript over the
5647-
* underlying arrays (a Java byte[] is a plain JS Array, so JS reads it at
5648-
* native speed with no marshalling copy), base64-encodes it into a data:
5649-
* URL, and reuses the proven save-blob-dataurl main-thread delivery (the
5650-
* same path {@link #downloadBytesAsFile} uses). Entry names are passed as
5651-
* UTF-8 byte arrays so no VM-String->JS-string conversion is needed.
5652-
*/
5653-
public boolean buildAndDownloadZip(final String fileName, String[] names, byte[][] data) {
5654-
if (fileName == null || names == null || data == null || names.length != data.length) {
5655-
return false;
5656-
}
5657-
byte[][] nameBytes = new byte[names.length][];
5658-
for (int i = 0; i < names.length; i++) {
5659-
String n = names[i] == null ? "" : names[i];
5660-
try {
5661-
nameBytes[i] = n.getBytes("UTF-8");
5662-
} catch (java.io.UnsupportedEncodingException e) {
5663-
nameBytes[i] = n.getBytes();
5664-
}
5665-
}
5666-
String dataUrl;
5667-
try {
5668-
dataUrl = jsBuildZipDataUrl(nameBytes, data);
5669-
} catch (Throwable t) {
5670-
return false;
5671-
}
5672-
if (dataUrl == null || dataUrl.length() == 0) {
5673-
return false;
5674-
}
5675-
registerSaveBlobHandlerDataUrl(fileName, dataUrl);
5676-
if (isBacksideHookAvailable()) {
5677-
addBacksideHook(new JSRunnable() {
5678-
public void run() {
5679-
fireSaveBlobHandler();
5680-
}
5681-
});
5682-
return true;
5683-
}
5684-
try {
5685-
fireSaveBlobHandler();
5686-
return true;
5687-
} catch (Throwable t) {
5688-
return false;
5689-
}
5690-
}
5691-
5692-
// Assembles a STORED zip in native JS over the plain arrays and returns it
5693-
// as a base64 "data:" URL. Runs straight through (no yield): the byte loops
5694-
// are native JS, NOT generator-wrapped Java, which is the whole point.
5695-
@JSBody(params={"nameBytes", "data"}, script=
5696-
"var n = data.length;\n" +
5697-
"var crcTab = new Int32Array(256);\n" +
5698-
"for (var c = 0; c < 256; c++) { var k = c; for (var b = 0; b < 8; b++) { k = (k & 1) ? (0xEDB88320 ^ (k >>> 1)) : (k >>> 1); } crcTab[c] = k; }\n" +
5699-
"function crc32(arr) { var crc = 0xFFFFFFFF; var len = arr.length; for (var i = 0; i < len; i++) { crc = crcTab[(crc ^ (arr[i] & 0xff)) & 0xff] ^ (crc >>> 8); } return (crc ^ 0xFFFFFFFF) >>> 0; }\n" +
5700-
"var crcs = new Array(n);\n" +
5701-
"var total = 0;\n" +
5702-
"for (var i = 0; i < n; i++) { crcs[i] = crc32(data[i]); total += 30 + nameBytes[i].length + data[i].length; total += 46 + nameBytes[i].length; }\n" +
5703-
"total += 22;\n" +
5704-
"var out = new Uint8Array(total);\n" +
5705-
"var p = 0;\n" +
5706-
"function u16(v) { out[p++] = v & 0xff; out[p++] = (v >>> 8) & 0xff; }\n" +
5707-
"function u32(v) { out[p++] = v & 0xff; out[p++] = (v >>> 8) & 0xff; out[p++] = (v >>> 16) & 0xff; out[p++] = (v >>> 24) & 0xff; }\n" +
5708-
"function bytes(arr) { var len = arr.length; for (var i = 0; i < len; i++) { out[p++] = arr[i] & 0xff; } }\n" +
5709-
"var offsets = new Array(n);\n" +
5710-
"for (var i = 0; i < n; i++) {\n" +
5711-
" offsets[i] = p;\n" +
5712-
" u32(0x04034b50); u16(20); u16(0); u16(0); u16(0); u16(0);\n" +
5713-
" u32(crcs[i]); u32(data[i].length); u32(data[i].length); u16(nameBytes[i].length); u16(0);\n" +
5714-
" bytes(nameBytes[i]); bytes(data[i]);\n" +
5715-
"}\n" +
5716-
"var cdStart = p;\n" +
5717-
"for (var i = 0; i < n; i++) {\n" +
5718-
" u32(0x02014b50); u16(20); u16(20); u16(0); u16(0); u16(0); u16(0);\n" +
5719-
" u32(crcs[i]); u32(data[i].length); u32(data[i].length); u16(nameBytes[i].length); u16(0); u16(0); u16(0); u16(0); u32(0); u32(offsets[i]);\n" +
5720-
" bytes(nameBytes[i]);\n" +
5721-
"}\n" +
5722-
"var cdSize = p - cdStart;\n" +
5723-
"u32(0x06054b50); u16(0); u16(0); u16(n); u16(n); u32(cdSize); u32(cdStart); u16(0);\n" +
5724-
"var B = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n" +
5725-
"var res = [];\n" +
5726-
"var len = out.length;\n" +
5727-
"var i = 0;\n" +
5728-
"while (i < len) {\n" +
5729-
" var b0 = out[i++];\n" +
5730-
" var have1 = i < len; var b1 = have1 ? out[i++] : 0;\n" +
5731-
" var have2 = i < len; var b2 = have2 ? out[i++] : 0;\n" +
5732-
" var t = (b0 << 16) | (b1 << 8) | b2;\n" +
5733-
" res.push(B.charAt((t >>> 18) & 63) + B.charAt((t >>> 12) & 63) + (have1 ? B.charAt((t >>> 6) & 63) : '=') + (have2 ? B.charAt(t & 63) : '='));\n" +
5734-
"}\n" +
5735-
"return 'data:application/zip;base64,' + res.join('');\n")
5736-
private static native String jsBuildZipDataUrl(byte[][] nameBytes, byte[][] data);
5737-
57385640
public boolean paintNativePeersBehind() {
57395641
return true;
57405642
}

scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -126,28 +126,12 @@ public void generate() {
126126
return;
127127
}
128128

129-
// JavaScript port: assemble the zip AND drive the download natively.
130-
// A pure-Java zip is unusable on that port -- every Java method is a
131-
// cooperative generator, so a DEFLATE compress loop (and even a STORED
132-
// entry's per-byte CRC32) over multi-MB of project bytes runs millions
133-
// of generator resumes and effectively hangs. buildAndDownloadZip does
134-
// the byte-heavy work in native JS over the underlying arrays. On other
135-
// platforms it returns false and we build the zip in-Java below.
136-
String[] names = new String[entries.size()];
137-
byte[][] data = new byte[entries.size()][];
138-
int idx = 0;
139-
for (Map.Entry<String, byte[]> e : entries.entrySet()) {
140-
names[idx] = e.getKey();
141-
data[idx] = e.getValue();
142-
idx++;
143-
}
144-
if (buildAndDownloadZip(fileName, names, data)) {
145-
return;
146-
}
147-
148-
// Other platforms: build the zip in-Java, then hand the bytes to the
149-
// platform downloader; falls through to storage + execute() when
150-
// unsupported.
129+
// Build the project zip in memory. This runs on every platform,
130+
// including the JavaScript port: the in-Java zip is fast there now that
131+
// the translator no longer wraps synchronous natives (arraycopy/CRC) in
132+
// cooperative generators and resolves inherited interface static fields
133+
// correctly. Then hand the bytes to the platform downloader; falls
134+
// through to the storage + execute() path when unsupported.
151135
byte[] bytes;
152136
try {
153137
ByteArrayOutputStream bos = new ByteArrayOutputStream();
@@ -221,8 +205,7 @@ void writeProjectZip(OutputStream outputStream) throws IOException {
221205

222206
/// Reads every entry (IDE scaffold, common files, template sources, cn1libs,
223207
/// localization, generated README/.gitignore/skills) into an ordered map of
224-
/// path -> bytes. This is the I/O phase; the zip assembly is separate so the
225-
/// JavaScript port can hand the raw entries to {@code buildAndDownloadZip}.
208+
/// path -> bytes. This is the I/O phase, kept separate from the zip assembly.
226209
Map<String, byte[]> collectProjectEntries() throws IOException {
227210
Map<String, byte[]> mergedEntries = new LinkedHashMap<String, byte[]>();
228211

@@ -244,11 +227,11 @@ Map<String, byte[]> collectProjectEntries() throws IOException {
244227
}
245228

246229
/// Writes the collected entries as a STORED (uncompressed) zip. STORED rather
247-
/// than DEFLATED because the zipme Deflater is unusably slow on the
248-
/// JavaScript port; STORED is used uniformly (the size cost is irrelevant for
249-
/// a one-off scaffold download, and other platforms only reach this in-Java
250-
/// path as a fallback). The JavaScript port does not use this method -- it
251-
/// assembles the zip natively via {@code buildAndDownloadZip}.
230+
/// than DEFLATED keeps the byte work minimal (CRC + copy, no compression
231+
/// engine); the size cost is irrelevant for a one-off scaffold download.
232+
/// Used on every platform including the JavaScript port, where it is fast now
233+
/// that the translator no longer wraps synchronous natives in generators and
234+
/// resolves inherited interface static fields correctly.
252235
void writeEntriesToZip(OutputStream outputStream, Map<String, byte[]> mergedEntries) throws IOException {
253236
try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
254237
zos.setMethod(ZipOutputStream.STORED);

0 commit comments

Comments
 (0)