From 2bb5e7ffc9332bfff51e5517d682d5165f6fbb60 Mon Sep 17 00:00:00 2001 From: Algent Date: Fri, 4 Sep 2026 23:30:54 +0200 Subject: [PATCH] feat: add Java material enumeration --- JAVA_MIGRATION.md | 3 +- JAVA_MIGRATION_CONSUMER_AUDIT.md | 20 ++++++ JAVA_MIGRATION_HANDOFF.md | 22 ++++-- README.md | 4 ++ docs/api/MATERIAL_ENUMERATION.md | 70 +++++++++++++++++++ docs/migration/HISTORY.md | 17 +++++ ...MicroblockPacketHandlerFunctionalTest.java | 20 ++++++ .../microblock/MicroMaterialRegistry$.java | 7 ++ .../microblock/MicroMaterialRegistry.java | 46 ++++++++++++ ...oMaterialRegistryCharacterizationTest.java | 47 ++++++++++++- .../examples/MaterialEnumerationExample.java | 21 ++++++ 11 files changed, 267 insertions(+), 10 deletions(-) create mode 100644 docs/api/MATERIAL_ENUMERATION.md create mode 100644 src/test/java/codechicken/microblock/examples/MaterialEnumerationExample.java diff --git a/JAVA_MIGRATION.md b/JAVA_MIGRATION.md index cd9ba42..4b4e038 100644 --- a/JAVA_MIGRATION.md +++ b/JAVA_MIGRATION.md @@ -529,7 +529,7 @@ their sibling and only need the annotation and javadoc. | --- | --- | --- | | `TileMultipart.partList(): scala.collection.Seq` | `jPartList(): java.util.List` | Sibling already exists | | `MultiPartRegistry.registerParts(IPartFactory2, scala.collection.Seq)` | `registerParts(IPartFactory2, String...)` | Sibling already exists | -| `MicroMaterialRegistry.getIdMap(): scala.Tuple2[]` | `materialCount(): int` plus existing `materialName(int)` and `getMaterial(int)` | The array index **is** the material ID, so no pair or map type is needed | +| `MicroMaterialRegistry.getIdMap(): scala.Tuple2[]` | `materialCount(): int` plus existing `materialName(int)` and `getMaterial(int)` | Implemented with static/companion deprecation; [guide and compiling example](docs/api/MATERIAL_ENUMERATION.md). Consumer release/adoption remains pending | | `TileMultipart.operate(scala.Function1)` | `forEachPart(java.util.function.Consumer)` | Preserve the existing skip-unbound-part behavior | | `TileMultipart.occlusionTest(scala.collection.Seq, TMultiPart)` | `occlusionTest(Collection, TMultiPart)` | | | `TileMultipart.loadParts(scala.collection.Iterable)` | `loadParts(Collection)` | Schematica and GuideNH reflect the Scala descriptor; see Phase 10 | @@ -544,6 +544,7 @@ same-name overload there invites a silent wrong-overload bind. - [ ] Add the missing Java-shaped siblings over shared behavior, preserving legacy override dispatch and avoiding recursive forwarding as required by the API migration design. +- [x] Add and document `materialCount()` with existing indexed lookups; retain and deprecate both `getIdMap()` entries. - [ ] Mark all nine rows `@Deprecated` with javadoc naming the replacement. - [ ] Confirm every original descriptor still exists in the ABI fixture after the change. - [ ] Document the supported API with compiling usage examples and an old-to-new migration guide. Validate Java diff --git a/JAVA_MIGRATION_CONSUMER_AUDIT.md b/JAVA_MIGRATION_CONSUMER_AUDIT.md index cce4d47..7d64466 100644 --- a/JAVA_MIGRATION_CONSUMER_AUDIT.md +++ b/JAVA_MIGRATION_CONSUMER_AUDIT.md @@ -488,6 +488,26 @@ behavior: API, trait, generator, registry, or tile change. Add UtilitiesInExcess as a compile/runtime fixture now even though it is not yet in the pack. +## Java API adoption ledger + +This ledger tracks migration of a specific legacy contract, not completion of an entire consumer's migration. +The FMP material-enumeration addition is implemented on `algent/java`, with the +[documented Java replacement](docs/api/MATERIAL_ENUMERATION.md) and a compiling example. It is not yet a released +minimum dependency version. Unlisted contracts remain governed by the inventories above. + +| Legacy contract | Consumer and inspected source | Supported replacement | Consumer migration/release | Target-pack adoption and removal gate | +| --- | --- | --- | --- | --- | +| `MicroMaterialRegistry.getIdMap(): scala.Tuple2[]` | UtilitiesInExcess `3e107a1fe9bc15fcb6a808242ffda1354dac7c3a`: `FMPRecipeLoader.run`, `UEMultipartItem.getSubItems` | `materialCount()` with `materialName(int)` and, when needed, `getMaterial(int)` | Source patch and first released version pending; checkout used as reference only | No migrated pack version verified; retain the bridge | +| `MicroMaterialRegistry.getIdMap(): scala.Tuple2[]` | Extra Utilities 1.2.12 decompiled reference: both NEI microblock handlers and `multipart.microblock.ItemMicroBlock` | Same ID-based Java enumeration | No editable upstream consumer in scope; retirement/replacement remains pending | Verify Extra Utilities is absent and its replacement uses the new API before retiring this dependency | + +Evidence for the FMP addition is under ignored `run/migration-material-enumeration-reference/`. The original +reference-compiled Scala consumer still exercises the companion and tuple-array descriptor. The new compiling Java +example has no Scala imports or bytecode references. Runtime checks cover initialized ID lookup and handshake order; +they do not establish that an updated UtilitiesInExcess release has shipped or entered the pack. + +FMP's own `ItemMicroPart` and `MicroRecipe$` still use the legacy array internally. Their migration, any additional +retained companion users, and the other Scala-facing contracts remain separate removal gates. + ## Practical priority for the current branch 1. Keep the existing `+678` ABI fixture as the exact binary floor and correct failures before source cleanup. diff --git a/JAVA_MIGRATION_HANDOFF.md b/JAVA_MIGRATION_HANDOFF.md index b7fbd9a..732788e 100644 --- a/JAVA_MIGRATION_HANDOFF.md +++ b/JAVA_MIGRATION_HANDOFF.md @@ -16,7 +16,7 @@ migration checkout; `codex/tile-compatibility-fixes` was deleted after its fixes ## Current state and next target -**530 plain-JVM tests and 237 Java 8 Forge tests pass, with zero failures/errors/skips.** Sources total **224 Java +**535 plain-JVM tests and 238 Java 8 Forge tests pass, with zero failures/errors/skips.** Sources total **224 Java files and 9 Scala files / 747 nonblank Scala lines**. The packaged inventory has 443 classes. Review follow-up: restored packet-scheduler callback mutation behavior with the original Scala hash-map traversal, @@ -31,17 +31,25 @@ use, but no override triggering these three regressions was found. All 443 class payloads and 116 generated dumps are retained. The agreed next milestone is the documented consumer-facing Java API, followed by consumer release/adoption and final Scala removal; see the plan's API migration design and Phases 8–10. -Latest bounded target: `ScalaSignature.MethodSymbol.info` delegates info-ID evaluation to -`ScalaSignatureParser.methodSymbolInfo`. Seven characterization tests were committed first as `9859aa8`. They pin -infoId/eval order, repeated uncached reads, exact and null results, the result cast, failures, unused symbol/type members -and the frozen Scala caller descriptor. All 519 frozen JVM consumers pass; checks preserve 442 retained class/member -APIs, all 17 ScalaSignature payloads, 3,744 unrelated method bodies and all 116 generated dumps. No class is added or -removed; the total stays 443 classes. Evidence: `run/migration-method-symbol-info-reference/`. +Latest API addition: `MicroMaterialRegistry.materialCount()` completes ID-based enumeration with the existing +`materialName(int)` and `getMaterial(int)` methods. Both `getIdMap()` entries are deprecated with unchanged descriptors +and live-array behavior. Two baseline cases were committed first as `9b78099`; three further JVM cases and one Forge +case cover the new API and a compiling Java example. All 532 pre-change compiled tests pass with their recorded +version. The 443 class APIs retain all existing members, with one public static method added; 17 ScalaSignature +payloads, 3,746 existing method bodies and 116 generated dumps are unchanged apart from expected deprecation metadata +and build-version literals. Evidence: `run/migration-material-enumeration-reference/`. + +The [material enumeration guide](docs/api/MATERIAL_ENUMERATION.md) documents lifecycle, server ID ordering and failure +boundaries. The consumer audit's adoption ledger records the inspected UtilitiesInExcess revision and Extra Utilities +1.2.12; their source migrations, releases and pack adoption remain pending. The supplied checkouts remain reference-only. **Next priority: close the Java API gaps in Phases 2 and 9 and map consumer migrations in Phase 10.** Reuse the existing Java surface; supply missing capabilities, precise contracts, migration guidance and compiling examples. Cover subclass/override behavior and generated extensions as well as ordinary calls. ProjectRed's illuminated microblocks are the representative external extension case. Consumer mods may remain Scala internally while adopting this API. +The next bounded API candidate is `TileMultipart` collection/traversal access, preserving legacy getter and callback +overrides, captured-sequence mutation behavior and detached-part filtering. Do not repeat material enumeration as an +unimplemented gap or treat its FMP-side completion as consumer adoption. Pause mechanical extraction of retained Scala shells unless it enables that API, fixes a demonstrated issue or has a measured benefit. `ScalaSignature.ClassSymbolRef.info` remains an optional bounded extraction, not the default next diff --git a/README.md b/README.md index f2b6d19..9bdda68 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,7 @@ If you only want to use the API and not modify it: * Place both mods in your /libs/ folder and link them as libraries in your IDE. Implementation of all parts of the API should be clean and easy in Java. The minecraft multipart mod is implemented in java as a test case. + +Java API migration guides: + +* [Enumerating microblock materials](docs/api/MATERIAL_ENUMERATION.md), including a compiling example and legacy API replacements. diff --git a/docs/api/MATERIAL_ENUMERATION.md b/docs/api/MATERIAL_ENUMERATION.md new file mode 100644 index 0000000..431f6c6 --- /dev/null +++ b/docs/api/MATERIAL_ENUMERATION.md @@ -0,0 +1,70 @@ +# Enumerating microblock materials from Java + +The `algent/java` branch adds `MicroMaterialRegistry.materialCount()`. Use it with the existing +`materialName(int)` and `getMaterial(int)` methods to enumerate materials without importing Scala types. +This API is not yet tied to a published release; consumers must require an FMP version containing the addition. + +## Example + +The following method copies the active material names in numeric ID order: + +```java +public static List materialNames() { + int count = MicroMaterialRegistry.materialCount(); + List names = new ArrayList<>(count); + for (int id = 0; id < count; id++) { + names.add(MicroMaterialRegistry.materialName(id)); + } + return names; +} +``` + +Imports and the enclosing class are in the [compiling example](../../src/test/java/codechicken/microblock/examples/MaterialEnumerationExample.java). +It is compiled and exercised by the normal `test` task. It also compiles with only the FMP dev jar and Java 8 APIs on +the compile classpath; this does not remove FMP's runtime dependencies. Use `MicroMaterialRegistry.getMaterial(id)` in the loop when +you also need the `IMicroMaterial`, for example to obtain its item. That returns the registered instance, not a copy. +The example's list is an independent copy; editing it does not change the registry. + +## Timing, order and identity + +- Enumerate after ForgeMicroblock has built its ID map during post-initialization. A consumer's own post-init handler + needs suitable mod ordering if it enumerates there; later lifecycle/game callbacks can use the initialized map. + Consumers must not call `setupIDMap()` themselves to force readiness. +- On a multiplayer client, enumeration of the connected server's IDs requires a completed, successful FMP handshake. + FMP also rebuilds the local map before server startup. Reacquire IDs after map replacement. +- Use the game/lifecycle thread. The calls are live lookups, not an atomic snapshot: do not rebuild the map or perform + registration/handshake work from inside an enumeration loop. +- Local setup orders IDs by material name. A multiplayer client adopts server order; do not sort or filter IDs when + preserving a mapping. Store material names in saved data and resolve their numeric IDs for the active map. +- `materialCount()` counts ID slots, including the missing-material placeholder when present. It throws + `IllegalStateException` before initialization and returns zero for an initialized empty map. +- A failed handshake can leave unresolved slots. The count includes them; `materialName(id)` and `getMaterial(id)` + retain their existing `NullPointerException` for such slots. FMP disconnects the client for missing mappings. + The count is not a handshake-success indicator, and callers should not silently skip unresolved IDs. +- The existing indexed lookups throw `ArrayIndexOutOfBoundsException` outside the active ID range and + `NullPointerException` before map initialization. + +## Migrating existing consumers + +| Legacy expression | Java API | +| --- | --- | +| `getIdMap().length` | `materialCount()` | +| `getIdMap()[id]._1()` | `materialName(id)` | +| `getIdMap()[id]._2()` | `getMaterial(id)` | +| Loop over the tuple array | Loop over IDs from zero to `materialCount()`, exclusive | + +For UtilitiesInExcess, apply this to `FMPRecipeLoader.run()` and `UEMultipartItem.getSubItems()`: replace each tuple +loop with an ID loop and use `materialName(id)` wherever the tuple name was used. Preserve its recipe/item ordering, +filters and material-name NBT. A material object, when needed, is available from `getMaterial(id)`. +Extra Utilities' NEI handlers use both tuple fields; both indexed replacements are available. + +The static and companion `getIdMap()` methods are deprecated, with their original descriptors and live-array +behavior retained. This change does not authorize removal of either method. The +[consumer adoption ledger](../../JAVA_MIGRATION_CONSUMER_AUDIT.md#java-api-adoption-ledger) records the migrations and +released pack versions still needed. + +## Validation + +The JVM tests cover the legacy array contract, initialization, server ordering, empty maps, unresolved slots, the +compiled Java example and the frozen Scala consumer. The Forge test checks that the Java enumeration matches the +initialized registry and the material-ID handshake payload. The public addition does not change packet or NBT formats. diff --git a/docs/migration/HISTORY.md b/docs/migration/HISTORY.md index ba40fae..3eed760 100644 --- a/docs/migration/HISTORY.md +++ b/docs/migration/HISTORY.md @@ -1724,3 +1724,20 @@ differences belong in the [divergence ledger](../../JAVA_MIGRATION_DIVERGENCES.m - ProjectRed, Chisel and ExtraUtilities extend `BlockMicroMaterial`; no supplied consumer overrides the affected accessors. These are verified extension-contract regressions, without a demonstrated current-pack gameplay failure. Evidence is under ignored `run/conversion-review/`; migration-plan changes remain deferred. + +### 2026-09-04 — Java material enumeration API + +- Committed two baseline cases first as `9b78099`, pinning the shared legacy array, uninitialized access, server ID + ordering, missing slots and empty maps. Saved the pre-API jar and all 532 compiled tests before implementation. +- Added `MicroMaterialRegistry.materialCount()` beside the existing indexed name/material lookups. It reports the + active ID-map length and explicitly rejects an uninitialized map. The static and companion `getIdMap()` methods + keep their original descriptors and live-array behavior, with deprecation documentation pointing at the Java API. +- Added three JVM cases, a compiling Java enumeration example and one Forge handshake/registry case. Normal/clean build/style + checks, 535 JVM tests, 238 Java 8 Forge tests and all 532 frozen pre-change tests pass. The example compiles against + only the dev jar and Java 8 APIs and has no Scala bytecode references; FMP's runtime dependencies remain in place. +- Binary comparison retains all 443 class APIs with exactly one new public static method and two deprecations. + All 17 ScalaSignature payloads, 3,746 existing method bodies and 116 generated dumps are unchanged, allowing only + the expected deprecation metadata and build-version literals. No production class or dependency is added. +- Added the material-enumeration guide and the first consumer adoption ledger rows. UtilitiesInExcess's two source + call sites and Extra Utilities' tuple-array readers have supported replacements; consumer changes, releases and + pack adoption remain pending. Evidence is under ignored `run/migration-material-enumeration-reference/`. diff --git a/src/functionalTest/java/codechicken/multipart/test/MicroblockPacketHandlerFunctionalTest.java b/src/functionalTest/java/codechicken/multipart/test/MicroblockPacketHandlerFunctionalTest.java index 7aeb2e9..f153584 100644 --- a/src/functionalTest/java/codechicken/multipart/test/MicroblockPacketHandlerFunctionalTest.java +++ b/src/functionalTest/java/codechicken/multipart/test/MicroblockPacketHandlerFunctionalTest.java @@ -3,6 +3,8 @@ package codechicken.multipart.test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.network.NetHandlerPlayServer; @@ -22,6 +24,24 @@ import scala.Tuple2; class MicroblockPacketHandlerFunctionalTest { + @Test + void javaEnumerationUsesTheSameIdsAsTheInitializedRegistryAndHandshake() { + PacketCustom outgoing = new PacketCustom("test", 1); + MicroMaterialRegistry.writeIDMap(outgoing); + PacketCustom incoming = new PacketCustom(outgoing.getByteBuf().copy()); + int count = MicroMaterialRegistry.materialCount(); + assertTrue(count > 0); + assertEquals(count, incoming.readInt()); + for (int id = 0; id < count; id++) { + String name = MicroMaterialRegistry.materialName(id); + assertEquals(name, incoming.readString()); + assertEquals(id, MicroMaterialRegistry.materialID(name)); + assertNotNull(MicroMaterialRegistry.getMaterial(id)); + assertSame(MicroMaterialRegistry.getMaterial(name), MicroMaterialRegistry.getMaterial(id)); + } + assertEquals(0, incoming.getByteBuf().readableBytes()); + } + @Test void sendsTheCompleteMaterialIdMapDuringTheServerHandshake() { MinecraftServer server = MinecraftServer.getServer(); diff --git a/src/main/scala/codechicken/microblock/MicroMaterialRegistry$.java b/src/main/scala/codechicken/microblock/MicroMaterialRegistry$.java index b540b2e..31b80eb 100644 --- a/src/main/scala/codechicken/microblock/MicroMaterialRegistry$.java +++ b/src/main/scala/codechicken/microblock/MicroMaterialRegistry$.java @@ -89,6 +89,13 @@ public final class MicroMaterialRegistry$ { return MicroMaterialRegistry.getMaterial(id); } + /** + * Returns the same legacy backing array as {@link MicroMaterialRegistry#getIdMap()}. + * + * @deprecated For enumeration, use {@link MicroMaterialRegistry#materialCount()}, + * {@link MicroMaterialRegistry#materialName(int)} and {@link MicroMaterialRegistry#getMaterial(int)}. + */ + @Deprecated public Tuple2[] getIdMap() { return MicroMaterialRegistry.getIdMap(); } diff --git a/src/main/scala/codechicken/microblock/MicroMaterialRegistry.java b/src/main/scala/codechicken/microblock/MicroMaterialRegistry.java index 782fe2b..7ae9dbb 100644 --- a/src/main/scala/codechicken/microblock/MicroMaterialRegistry.java +++ b/src/main/scala/codechicken/microblock/MicroMaterialRegistry.java @@ -241,6 +241,15 @@ public final class MicroMaterialRegistry { return idWriter.read(data); } + /** + * Returns the persistent name at a numeric ID in the active material map. IDs may change when the map is rebuilt or + * synchronized with a server; store material names rather than numeric IDs in persistent data. + * + * @param id an ID from zero (inclusive) to {@link #materialCount()} (exclusive) + * @throws ArrayIndexOutOfBoundsException if the ID is outside the active map + * @throws NullPointerException if the map is uninitialized or the ID is unresolved after a failed + * handshake + */ public static String materialName(int id) { return idMap[id]._1(); } @@ -258,10 +267,47 @@ public final class MicroMaterialRegistry { return typeMap.get(remapped(name)); } + /** + * Returns the registered material instance at an ID in the active material map; no copy is made. + * + * @param id an ID from zero (inclusive) to {@link #materialCount()} (exclusive) + * @throws ArrayIndexOutOfBoundsException if the ID is outside the active map + * @throws NullPointerException if the map is uninitialized or the ID is unresolved after a failed + * handshake + */ public static IMicroMaterial getMaterial(int id) { return idMap[id]._2(); } + /** + * Returns the number of numeric ID slots in the active material map, including the missing-material placeholder + * when present. Enumerate IDs from zero (inclusive) to this count (exclusive) with {@link #materialName(int)} and + * {@link #getMaterial(int)}. + * + * FMP builds the map during post-initialization and before server startup. A multiplayer handshake replaces the + * client map with server ID order. Enumerate on the game/lifecycle thread after initialization; multiplayer clients + * must also finish the handshake successfully. The map must not be rebuilt during enumeration. Do not cache numeric + * IDs across map replacements. A failed handshake can leave unresolved slots, which this count includes; it is not + * a readiness check. + * + * @return the active map length, or zero for an initialized empty map + * @throws IllegalStateException if FMP has not initialized the material ID map + */ + public static int materialCount() { + if (idMap == null) { + throw new IllegalStateException("The material ID map has not been initialized."); + } + return idMap.length; + } + + /** + * Returns the legacy live backing array, or null before ID-map initialization. A map replacement leaves earlier + * array references pointing at the old map; unresolved IDs after a failed handshake contain null. + * + * @deprecated For enumeration, use {@link #materialCount()}, {@link #materialName(int)} and + * {@link #getMaterial(int)}. The Scala array descriptor is retained for existing consumers. + */ + @Deprecated public static Tuple2[] getIdMap() { return idMap; } diff --git a/src/test/java/codechicken/microblock/MicroMaterialRegistryCharacterizationTest.java b/src/test/java/codechicken/microblock/MicroMaterialRegistryCharacterizationTest.java index 6b05424..c9da518 100644 --- a/src/test/java/codechicken/microblock/MicroMaterialRegistryCharacterizationTest.java +++ b/src/test/java/codechicken/microblock/MicroMaterialRegistryCharacterizationTest.java @@ -35,11 +35,12 @@ import codechicken.lib.vec.Cuboid6; import codechicken.lib.vec.Vector3; import codechicken.microblock.MicroMaterialRegistry.IMicroHighlightRenderer; import codechicken.microblock.MicroMaterialRegistry.IMicroMaterial; +import codechicken.microblock.examples.MaterialEnumerationExample; /** * The registry is global mutable state, so everything is registered once in {@link #registerAndFreezeIdMap()} and the - * cases below only read it. Error paths are not covered: they call the microblock logger, which is null until - * MicroblockProxy.preInit runs, so they cannot execute headless. + * cases below restore any temporary state changes. Logger-backed error paths require MicroblockProxy.preInit and cannot + * execute headless. */ class MicroMaterialRegistryCharacterizationTest { @@ -129,6 +130,48 @@ class MicroMaterialRegistryCharacterizationTest { assertEquals(java.lang.reflect.Array.getLength(original), MicroMaterialRegistry.getIdMap().length); } + @Test + void javaExampleEnumeratesTheSameMaterialsWithoutExposingTheLegacyArray() throws Exception { + String[] names = Arrays.stream(MicroMaterialRegistry.getIdMap()).map(entry -> entry._1()) + .toArray(String[]::new); + assertEquals(names.length, MicroMaterialRegistry.materialCount()); + assertEquals(Arrays.asList(names), MaterialEnumerationExample.materialNames()); + assertTrue(MicroMaterialRegistry.class.getMethod("getIdMap").isAnnotationPresent(Deprecated.class)); + assertTrue(MicroMaterialRegistry$.class.getMethod("getIdMap").isAnnotationPresent(Deprecated.class)); + } + + @Test + void javaEnumerationTracksServerOrderMissingSlotsAndEmptyMaps() { + try { + MicroMaterialRegistry.readIDMap(incomingMap("test:stone", "test:glass")); + assertEquals(2, MicroMaterialRegistry.materialCount()); + assertEquals(Arrays.asList("test:stone", "test:glass"), MaterialEnumerationExample.materialNames()); + + MicroMaterialRegistry.readIDMap(incomingMap("test:stone", "server:unavailable", "test:glass")); + assertEquals(3, MicroMaterialRegistry.materialCount()); + assertThrows(NullPointerException.class, MaterialEnumerationExample::materialNames); + + MicroMaterialRegistry.readIDMap(incomingMap()); + assertEquals(0, MicroMaterialRegistry.materialCount()); + assertEquals(Collections.emptyList(), MaterialEnumerationExample.materialNames()); + } finally { + MicroMaterialRegistry$.MODULE$.setupIDMap(); + } + } + + @Test + void materialCountRejectsAnUninitializedMapInsteadOfReportingAnEmptyRegistry() throws Exception { + Field idMap = MicroMaterialRegistry.class.getDeclaredField("idMap"); + idMap.setAccessible(true); + Object original = idMap.get(null); + try { + idMap.set(null, null); + assertThrows(IllegalStateException.class, MicroMaterialRegistry::materialCount); + } finally { + idMap.set(null, original); + } + } + @Test void getMaterialByNameResolvesRemapsAndReturnsNullWhenUnknown() { assertSame(GLASS, MicroMaterialRegistry.getMaterial("test:glass")); diff --git a/src/test/java/codechicken/microblock/examples/MaterialEnumerationExample.java b/src/test/java/codechicken/microblock/examples/MaterialEnumerationExample.java new file mode 100644 index 0000000..d5eabc5 --- /dev/null +++ b/src/test/java/codechicken/microblock/examples/MaterialEnumerationExample.java @@ -0,0 +1,21 @@ +package codechicken.microblock.examples; + +import java.util.ArrayList; +import java.util.List; + +import codechicken.microblock.MicroMaterialRegistry; + +/** Compiling example for docs/api/MATERIAL_ENUMERATION.md; call after material IDs are ready. */ +public final class MaterialEnumerationExample { + + private MaterialEnumerationExample() {} + + public static List materialNames() { + int count = MicroMaterialRegistry.materialCount(); + List names = new ArrayList<>(count); + for (int id = 0; id < count; id++) { + names.add(MicroMaterialRegistry.materialName(id)); + } + return names; + } +}