From 246dafffdab398f2552bd0e1c82763f60f9a9358 Mon Sep 17 00:00:00 2001 From: Algent Date: Fri, 14 Aug 2026 19:52:28 +0200 Subject: [PATCH] refactor: drop unused IDWriter and JPartialOcclusion Scala bridges The downstream ABI inventory scanned 240 mod jars in GTNH daily 2026-08-14+678 and found no consumer of either bridge. Both were written speculatively, before the inventory existed. IDWriter had no downstream reference at all, so its four Scala function accessors are removed and the class now selects a carrier width directly. That also removes the Integer boxing and Function1/Function2 allocation per read and write that the first port had preserved. Calling write or read before setMax now throws IllegalStateException rather than NullPointerException; no caller reaches that state. JPartialOcclusion$class had no downstream reference either. The two consumers that touch JPartialOcclusion only call getPartialOcclusionBoxes, and the two jars that implement the interface emit no call to the helper. The interface and both of its method descriptors are unchanged. The ReferenceScalaPartialOcclusion fixture is removed with the helper it existed to verify. Validated with 27 plain-JVM tests, a clean checkstyleTest build with Spotless re-enabled, and the Java 8 Forge dedicated-server suite. --- JAVA_MIGRATION.md | 3 + JAVA_MIGRATION_DIVERGENCES.md | 35 ++++++ .../java/codechicken/multipart/IDWriter.java | 109 +++++------------- .../multipart/JPartialOcclusion$class.java | 14 --- src/test/fixtures/README.md | 22 +++- .../ReferenceScalaPartialOcclusion.scala | 11 -- .../IDWriterCharacterizationTest.java | 14 ++- ...rtialOcclusionBinaryCompatibilityTest.java | 47 -------- .../ReferenceScalaPartialOcclusion.class.b64 | 1 - 9 files changed, 99 insertions(+), 157 deletions(-) delete mode 100644 src/main/java/codechicken/multipart/JPartialOcclusion$class.java delete mode 100644 src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaPartialOcclusion.scala delete mode 100644 src/test/java/codechicken/multipart/JPartialOcclusionBinaryCompatibilityTest.java delete mode 100644 src/test/resources/compat/ReferenceScalaPartialOcclusion.class.b64 diff --git a/JAVA_MIGRATION.md b/JAVA_MIGRATION.md index 81bd569..3945be6 100644 --- a/JAVA_MIGRATION.md +++ b/JAVA_MIGRATION.md @@ -433,3 +433,6 @@ These ranges were formed before designing the characterization suite and should - Answered open decision 4: Scala runtime removal is not achievable for the first Java release. ProjRed, OpenComputers, ProjectBlue, and ForgeRelocationFMP link against 16 static methods on 8 trait `$class` helpers plus 17 companion `MODULE$` singletons. - Found only five referenced descriptors containing Scala types, and `TileMultipart.jPartList()` already outweighs `partList()` in downstream use. The Scala bridge surface is much smaller than the initial audit assumed. - Found zero downstream references to `IDWriter`, so its four retained Scala function accessors are not load-bearing and can be dropped. +- Removed both speculative bridges the inventory proved dead: the four `IDWriter` Scala function accessors and the whole `JPartialOcclusion$class` helper, along with the `ReferenceScalaPartialOcclusion` fixture that only existed to verify the helper. `JPartialOcclusion` itself and both of its method descriptors are unchanged. +- `IDWriter` now selects a carrier width instead of storing Scala closures, removing the per-call `Integer` boxing and `Function1`/`Function2` allocation that the first port had preserved. The nine encoding cases still pass unchanged. +- Established the working rule for the remaining phases: check `JAVA_MIGRATION_ABI_INVENTORY.md` before writing a bridge, rather than writing one reflexively for every converted file. diff --git a/JAVA_MIGRATION_DIVERGENCES.md b/JAVA_MIGRATION_DIVERGENCES.md index f6b3ba8..2dd86f4 100644 --- a/JAVA_MIGRATION_DIVERGENCES.md +++ b/JAVA_MIGRATION_DIVERGENCES.md @@ -50,3 +50,38 @@ Accepted divergence: the five generated `PartialOcclusionTest$$anonfun$*` classe - Clean `checkstyleTest build`: passing. - Java 8 Forge dedicated-server suite: 2 tests, 0 failures, 0 errors. - `PartialOcclusionTest` class-file version: 52 (Java 8). + +## 2026-08-14 — Removal of unused Scala bridges + +The downstream ABI inventory (`JAVA_MIGRATION_ABI_INVENTORY.md`) scanned 240 mod jars and found no consumer of either +bridge below. Both were written speculatively before the inventory existed. + +### Removed API + +- `IDWriter.write()`, `IDWriter.write_$eq(Function2)`, `IDWriter.read()`, and `IDWriter.read_$eq(Function1)`. + No jar in the pack references `IDWriter` at all. Both in-repo callers, `MultiPartRegistry` and + `MicroMaterialRegistry`, already use `write(MCDataOutput, int)` and `read(MCDataInput)`. +- `JPartialOcclusion$class`, including the `allowCompleteOcclusion(JPartialOcclusion)` and `$init$(JPartialOcclusion)` + static descriptors. No jar references the helper. The two consumers that touch `JPartialOcclusion`, + ForgeRelocationFMP and WitchingGadgets, only call `getPartialOcclusionBoxes()Ljava/lang/Iterable;`, and the two + jars that implement the interface, WR-CBE and extrautilities, emit no call to the helper. The interface itself and + both of its method descriptors are unchanged. + +The `ReferenceScalaPartialOcclusion` binary fixture and `JPartialOcclusionBinaryCompatibilityTest` were removed with +the helper they existed to verify. + +### Observable behavior + +`IDWriter` no longer stores Scala closures, so carrier selection and encoding happen without boxing an `Integer` or +allocating a `Function1`/`Function2` per call. Byte, unsigned-short, and integer selection and the encoded bytes are +unchanged, including negative maximums selecting the byte carrier. + +Accepted divergence: calling `write` or `read` before `setMax` now throws `IllegalStateException` instead of +`NullPointerException`. No caller reaches this state; the change only improves the failure message. + +### Validation + +- `IDWriterCharacterizationTest`: 9 tests, 0 failures, 0 errors. +- Complete plain-JVM suite: 27 tests, 0 failures, 0 errors. +- Clean `checkstyleTest build` with Spotless re-enabled: passing. +- Java 8 Forge dedicated-server suite: 2 tests, 0 failures, 0 errors. diff --git a/src/main/java/codechicken/multipart/IDWriter.java b/src/main/java/codechicken/multipart/IDWriter.java index a50d230..27d7648 100644 --- a/src/main/java/codechicken/multipart/IDWriter.java +++ b/src/main/java/codechicken/multipart/IDWriter.java @@ -2,97 +2,50 @@ package codechicken.multipart; import codechicken.lib.data.MCDataInput; import codechicken.lib.data.MCDataOutput; -import scala.Function1; -import scala.Function2; -import scala.runtime.AbstractFunction1; -import scala.runtime.AbstractFunction2; -import scala.runtime.BoxedUnit; /** Class for reading and writing IDs, widening the carrier data type as necessary. */ public class IDWriter { - private Function2 write; - private Function1 read; - - @Deprecated - public Function2 write() { - return write; - } - - @Deprecated - public void write_$eq(Function2 write) { - this.write = write; - } - - @Deprecated - public Function1 read() { - return read; - } - - @Deprecated - public void read_$eq(Function1 read) { - this.read = read; - } - - public void write(MCDataOutput data, int id) { - write.apply(data, id); - } - - public int read(MCDataInput data) { - return (Integer) read.apply(data); - } + private int width; public void setMax(int value) { // The reference Scala implementation widens negative values as signed values. long maximum = value; if (maximum > 0xffffL) { - write_$eq(new AbstractFunction2() { - - @Override - public BoxedUnit apply(MCDataOutput data, Object id) { - data.writeInt((Integer) id); - return BoxedUnit.UNIT; - } - }); - read_$eq(new AbstractFunction1() { - - @Override - public Object apply(MCDataInput data) { - return data.readInt(); - } - }); + width = 4; } else if (maximum > 0xffL) { - write_$eq(new AbstractFunction2() { - - @Override - public BoxedUnit apply(MCDataOutput data, Object id) { - data.writeShort((Integer) id); - return BoxedUnit.UNIT; - } - }); - read_$eq(new AbstractFunction1() { - - @Override - public Object apply(MCDataInput data) { - return data.readUShort(); - } - }); + width = 2; } else { - write_$eq(new AbstractFunction2() { + width = 1; + } + } - @Override - public BoxedUnit apply(MCDataOutput data, Object id) { - data.writeByte((Integer) id); - return BoxedUnit.UNIT; - } - }); - read_$eq(new AbstractFunction1() { + public void write(MCDataOutput data, int id) { + switch (width) { + case 1: + data.writeByte(id); + break; + case 2: + data.writeShort(id); + break; + case 4: + data.writeInt(id); + break; + default: + throw new IllegalStateException("setMax was not called before writing an ID"); + } + } - @Override - public Object apply(MCDataInput data) { - return (int) data.readUByte(); - } - }); + public int read(MCDataInput data) { + switch (width) { + case 1: + return data.readUByte(); + case 2: + return data.readUShort(); + case 4: + return data.readInt(); + default: + throw new IllegalStateException("setMax was not called before reading an ID"); } } } diff --git a/src/main/java/codechicken/multipart/JPartialOcclusion$class.java b/src/main/java/codechicken/multipart/JPartialOcclusion$class.java deleted file mode 100644 index e969463..0000000 --- a/src/main/java/codechicken/multipart/JPartialOcclusion$class.java +++ /dev/null @@ -1,14 +0,0 @@ -package codechicken.multipart; - -/** Binary bridge for Scala implementations compiled against the original trait. */ -@Deprecated -public abstract class JPartialOcclusion$class { - - private JPartialOcclusion$class() {} - - public static boolean allowCompleteOcclusion(JPartialOcclusion instance) { - return false; - } - - public static void $init$(JPartialOcclusion instance) {} -} diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index fcc5d5e..5dd9394 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -1,7 +1,21 @@ -# Precompiled compatibility fixtures +# Compatibility fixtures -Sources below this directory document binary consumers compiled against the untouched `1.7.12` reference dev jar. They are not part of a Gradle source set and must not be recompiled against the current project during normal tests. +Files below this directory are frozen baselines, not a Gradle source set. They must be reviewed when they change +rather than regenerated automatically, otherwise an accidental change silently redefines the baseline. -`ReferenceScalaPartialOcclusion.scala` was compiled with Scala 2.11.5 against reference revision `f10595d062bfc1e6dd57a5320fb5b48c383a4f38` and dev jar SHA-256 `bc6e06a01ca8a50b7532aa7d173895c9e89bd2989e43ed977e9eddd3adba57a5`. Its class-file SHA-256 is `c29f2fe296d0a50352b1de1d12ab7a95ac75848a14352ddae06b7aa9640d404a`. +## `abi/` -The class is stored as `src/test/resources/compat/ReferenceScalaPartialOcclusion.class.b64` so the repository retains a fixed binary compiled against the reference API. The test decodes and defines that class directly; compiling the documented source against the port would invalidate the compatibility check. +`gtnh-daily-678-consumers.txt` is the constant-pool scan of every mod jar in GTNH daily `2026-08-14+678`, listing +every downstream reference into `codechicken/multipart` and `codechicken/microblock`. Regenerate with +`tools/AbiScan.java` and diff against this file at every public-API phase; a member present here but absent from the +port is a linkage break in a shipping mod. See `JAVA_MIGRATION_ABI_INVENTORY.md` for the analysis. + +## Precompiled binary consumers + +When a port changes descriptors that the ABI inventory shows are load-bearing, freeze a consumer compiled against the +reference dev jar here as a class file and load it directly in a test. Recompiling a consumer against the port would +hide exactly the linkage failure the fixture exists to catch. + +None are currently retained. `ReferenceScalaPartialOcclusion` was removed together with the `JPartialOcclusion$class` +bridge once the inventory showed no downstream consumer of that helper. The trait helpers that do need this treatment +are listed in `JAVA_MIGRATION_ABI_INVENTORY.md`. diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaPartialOcclusion.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaPartialOcclusion.scala deleted file mode 100644 index e5acd81..0000000 --- a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaPartialOcclusion.scala +++ /dev/null @@ -1,11 +0,0 @@ -package codechicken.multipart.compat - -import java.util.Collections - -import codechicken.lib.vec.Cuboid6 -import codechicken.multipart.JPartialOcclusion - -final class ReferenceScalaPartialOcclusion extends JPartialOcclusion { - override def getPartialOcclusionBoxes: java.lang.Iterable[Cuboid6] = - Collections.emptyList[Cuboid6]() -} diff --git a/src/test/java/codechicken/multipart/IDWriterCharacterizationTest.java b/src/test/java/codechicken/multipart/IDWriterCharacterizationTest.java index b28bf45..0904ee8 100644 --- a/src/test/java/codechicken/multipart/IDWriterCharacterizationTest.java +++ b/src/test/java/codechicken/multipart/IDWriterCharacterizationTest.java @@ -2,6 +2,7 @@ package codechicken.multipart; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -9,6 +10,7 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.lang.reflect.Proxy; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -36,7 +38,7 @@ class IDWriterCharacterizationTest { private static byte[] encode(IDWriter writer, int value) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); MCDataOutput output = new MCDataOutputWrapper(new DataOutputStream(bytes)); - writer.write().apply(output, value); + writer.write(output, value); return bytes.toByteArray(); } @@ -58,11 +60,19 @@ class IDWriterCharacterizationTest { } }); - int value = (Integer) writer.read().apply(input); + int value = writer.read(input); assertEquals(0, data.available(), "The selected reader must consume its whole carrier"); return value; } + @Test + void rejectsUseBeforeSetMax() { + IDWriter writer = new IDWriter(); + + assertThrows(IllegalStateException.class, () -> writer.write(null, 0)); + assertThrows(IllegalStateException.class, () -> writer.read(null)); + } + private static byte[] hex(String value) { String[] octets = value.split(" "); byte[] bytes = new byte[octets.length]; diff --git a/src/test/java/codechicken/multipart/JPartialOcclusionBinaryCompatibilityTest.java b/src/test/java/codechicken/multipart/JPartialOcclusionBinaryCompatibilityTest.java deleted file mode 100644 index 901f5c1..0000000 --- a/src/test/java/codechicken/multipart/JPartialOcclusionBinaryCompatibilityTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package codechicken.multipart; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Objects; -import java.util.Scanner; - -import org.junit.jupiter.api.Test; - -class JPartialOcclusionBinaryCompatibilityTest { - - @Test - void loadsScalaImplementorCompiledAgainstReferenceJar() throws Exception { - byte[] bytecode = loadFixture(); - Class fixtureClass = new FixtureClassLoader(JPartialOcclusion.class.getClassLoader()).define(bytecode); - Object fixture = fixtureClass.getDeclaredConstructor().newInstance(); - - assertTrue(fixture instanceof JPartialOcclusion); - JPartialOcclusion part = (JPartialOcclusion) fixture; - assertFalse(part.allowCompleteOcclusion()); - assertFalse(part.getPartialOcclusionBoxes().iterator().hasNext()); - } - - private static byte[] loadFixture() { - InputStream input = Objects.requireNonNull( - JPartialOcclusionBinaryCompatibilityTest.class - .getResourceAsStream("/compat/ReferenceScalaPartialOcclusion.class.b64")); - try (Scanner scanner = new Scanner(input, StandardCharsets.US_ASCII.name()).useDelimiter("\\A")) { - return Base64.getMimeDecoder().decode(scanner.next()); - } - } - - private static final class FixtureClassLoader extends ClassLoader { - - private FixtureClassLoader(ClassLoader parent) { - super(parent); - } - - private Class define(byte[] bytecode) { - return defineClass(null, bytecode, 0, bytecode.length); - } - } -} diff --git a/src/test/resources/compat/ReferenceScalaPartialOcclusion.class.b64 b/src/test/resources/compat/ReferenceScalaPartialOcclusion.class.b64 deleted file mode 100644 index 1b9fe06..0000000 --- a/src/test/resources/compat/ReferenceScalaPartialOcclusion.class.b64 +++ /dev/null @@ -1 +0,0 @@ -yv66vgAAADIALAEAO2NvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFQYXJ0aWFsT2NjbHVzaW9uBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAJ2NvZGVjaGlja2VuL211bHRpcGFydC9KUGFydGlhbE9jY2x1c2lvbgcABQEAJFJlZmVyZW5jZVNjYWxhUGFydGlhbE9jY2x1c2lvbi5zY2FsYQEAHkxzY2FsYS9yZWZsZWN0L1NjYWxhU2lnbmF0dXJlOwEABWJ5dGVzAQFMBgExMkEhAQIDEwlxIisaNGZlFnQ3LVoqZEMyDAcrGTp1UwZkd2pZMm1rTkx3ThwGAwcRCWFhWThuYQYkKEJBAwcDJWlXDxw7amEGFEhPQwEIAy0Zdw5aM2RRJhw3Lho4BAFNGQFBAwkRBS1xUSIBBwsDNQlRYV0yYlkGTCFhBAcDDQVzF1BVM2chCQkiI0QBBRMJGUJBQQlLIQYUSC9bMW0fDhxHLl46al86RFEhBgEFAlkJYQFQNW9TUnREI0EMEQVhAVEiAQIJC2kBQRFJDgIxHSxHD1Uxc2kmDEduVDJkWVYcGG4cOENfYiw3L0YBHSENaSIFSgcCPSkRcQRJAQVZBnR3TUMBIgMRURdNXjEKBQ1yIgFDJXVLSgwnDVwzEQUVUlMiARQLBR1CE2EBPGZHKhERRkIBBFkmFBdCQRYnBR0ZVU9ZOGpJWgIBABZhbGxvd0NvbXBsZXRlT2NjbHVzaW9uAQADKClaAQAtY29kZWNoaWNrZW4vbXVsdGlwYXJ0L0pQYXJ0aWFsT2NjbHVzaW9uJGNsYXNzBwANAQAsKExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvSlBhcnRpYWxPY2NsdXNpb247KVoMAAsADwoADgAQAQAEdGhpcwEAPUxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhUGFydGlhbE9jY2x1c2lvbjsBABhnZXRQYXJ0aWFsT2NjbHVzaW9uQm94ZXMBABYoKUxqYXZhL2xhbmcvSXRlcmFibGU7AQAVamF2YS91dGlsL0NvbGxlY3Rpb25zBwAWAQAJZW1wdHlMaXN0AQASKClMamF2YS91dGlsL0xpc3Q7DAAYABkKABcAGgEABjxpbml0PgEAAygpVgwAHAAdCgAEAB4BAAYkaW5pdCQBACwoTGNvZGVjaGlja2VuL211bHRpcGFydC9KUGFydGlhbE9jY2x1c2lvbjspVgwAIAAhCgAOACIBAARDb2RlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAPTGluZU51bWJlclRhYmxlAQAJU2lnbmF0dXJlAQA1KClMamF2YS9sYW5nL0l0ZXJhYmxlPExjb2RlY2hpY2tlbi9saWIvdmVjL0N1Ym9pZDY7PjsBAApTb3VyY2VGaWxlAQAZUnVudGltZVZpc2libGVBbm5vdGF0aW9ucwEACFNjYWxhU2lnADEAAgAEAAEABgAAAAMAAQALAAwAAQAkAAAALwABAAEAAAAFKrgAEawAAAACACUAAAAMAAEAAAAFABIAEwAAACYAAAAGAAEAAAAIAAEAFAAVAAIAJAAAAC4AAQABAAAABLgAG7AAAAACACUAAAAMAAEAAAAEABIAEwAAACYAAAAGAAEAAAAKACcAAAACACgAAQAcAB0AAQAkAAAAMwABAAEAAAAJKrcAHyq4ACOxAAAAAgAlAAAADAABAAAACQASABMAAAAmAAAABgABAAAACAADACkAAAACAAcAKgAAAAsAAQAIAAEACXMACgArAAAAAwUAAA==