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==