diff --git a/paper-server/src/main/java/io/papermc/paper/util/Holderable.java b/paper-server/src/main/java/io/papermc/paper/util/Holderable.java index 404ab0df12..3401d0073f 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/Holderable.java +++ b/paper-server/src/main/java/io/papermc/paper/util/Holderable.java @@ -1,8 +1,21 @@ package io.papermc.paper.util; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import net.kyori.adventure.key.Key; import net.minecraft.core.Holder; +import net.minecraft.resources.RegistryOps; +import org.bukkit.Keyed; +import org.bukkit.Registry; +import org.bukkit.craftbukkit.CraftRegistry; import org.bukkit.craftbukkit.util.Handleable; +import org.intellij.lang.annotations.Subst; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public interface Holderable extends Handleable { Holder getHolder(); @@ -11,4 +24,55 @@ public interface Holderable extends Handleable { default M getHandle() { return this.getHolder().value(); } + + static @Nullable T fromBukkitSerializationObject(final Object deserialized, final Codec> codec, final Registry registry) { // TODO remove Keyed + return switch (deserialized) { + case @Subst("key:value") final String string -> { + if (!(Key.parseable(string))) { + yield null; + } + yield registry.get(Key.key(string)); + } + case JsonObjectWrapper(final JsonObject element) -> { + if (!(registry instanceof final CraftRegistry craftRegistry) || !craftRegistry.supportsDirectHolders()) { + throw new IllegalArgumentException("Cannot deserialize direct holders for " + registry); + } + final RegistryOps ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE); + final Holder holder = codec.decode(ops, element).getOrThrow().getFirst(); + yield ((CraftRegistry) registry).convertDirectHolder(holder); + } + default -> throw new IllegalArgumentException("Cannot deserialize " + deserialized); + }; + } + + default Object toBukkitSerializationObject(final Codec> codec) { + return switch (this.getHolder()) { + case final Holder.Direct direct -> { + final RegistryOps ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE); + yield new JsonObjectWrapper(codec.encodeStart(ops, direct).getOrThrow().getAsJsonObject()); + } + case final Holder.Reference reference -> reference.key().location().toString(); + default -> throw new IllegalArgumentException("Cannot serialize " + this.getHolder()); + }; + } + + /** + * All implementations should use this as their hashCode implementation + */ + default int implHashCode() { + return this.getHolder().hashCode(); + } + + /** + * All implementations should use this as their equals implementation + */ + default boolean implEquals(final @Nullable Object o) { + if (o == null || this.getClass() != o.getClass()) return false; + final Holderable that = (Holderable) o; + return this.getHolder().equals(that.getHolder()); + } + + default String implToString() { + return "%s{holder=%s}".formatted(this.getClass().getSimpleName(), this.getHolder().toString()); + } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/JsonObjectWrapper.java b/paper-server/src/main/java/io/papermc/paper/util/JsonObjectWrapper.java new file mode 100644 index 0000000000..4522eb172b --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/JsonObjectWrapper.java @@ -0,0 +1,34 @@ +package io.papermc.paper.util; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.util.Map; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record JsonObjectWrapper(JsonObject element) implements ConfigurationSerializable { + + private static final String KEY = "value"; + private static final Gson GSON = new Gson(); + + static { + ConfigurationSerialization.registerClass(JsonObjectWrapper.class); + } + + public JsonObjectWrapper(final JsonElement element) { + this(element.getAsJsonObject()); + } + + public JsonObjectWrapper(final Map input) { + this(JsonParser.parseString((String) input.get(KEY)).getAsJsonObject()); + } + + @Override + public Map serialize() { + return Map.of(KEY, GSON.toJson(this.element)); + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java index 2838ef9d89..aa19ac7513 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java @@ -10,14 +10,14 @@ import org.bukkit.Registry; import org.bukkit.craftbukkit.util.Handleable; import org.jetbrains.annotations.NotNull; -public class CraftMusicInstrument extends MusicInstrument implements Handleable { +public class CraftMusicInstrument extends MusicInstrument implements io.papermc.paper.util.Holderable { public static MusicInstrument minecraftToBukkit(Instrument minecraft) { return CraftRegistry.minecraftToBukkit(minecraft, Registries.INSTRUMENT, Registry.INSTRUMENT); } public static MusicInstrument minecraftHolderToBukkit(Holder minecraft) { - return CraftMusicInstrument.minecraftToBukkit(minecraft.value()); + return CraftRegistry.minecraftHolderToBukkit(minecraft, Registry.INSTRUMENT); // Paper - switch to Holder } public static Instrument bukkitToMinecraft(MusicInstrument bukkit) { @@ -25,41 +25,51 @@ public class CraftMusicInstrument extends MusicInstrument implements Handleable< } public static Holder bukkitToMinecraftHolder(MusicInstrument bukkit) { - Preconditions.checkArgument(bukkit != null); - - net.minecraft.core.Registry registry = CraftRegistry.getMinecraftRegistry(Registries.INSTRUMENT); - - if (registry.wrapAsHolder(CraftMusicInstrument.bukkitToMinecraft(bukkit)) instanceof Holder.Reference holder) { - return holder; - } - - throw new IllegalArgumentException("No Reference holder found for " + bukkit - + ", this can happen if a plugin creates its own instrument without properly registering it."); + return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.INSTRUMENT); // Paper - switch to Holder } - public static String bukkitToString(MusicInstrument bukkit) { + public static Object bukkitToString(MusicInstrument bukkit) { // Paper - switch to Holder Preconditions.checkArgument(bukkit != null); - return bukkit.getKey().toString(); + return ((CraftMusicInstrument) bukkit).toBukkitSerializationObject(Instrument.CODEC); // Paper - switch to Holder } - public static MusicInstrument stringToBukkit(String string) { + public static MusicInstrument stringToBukkit(Object string) { // Paper - switch to Holder Preconditions.checkArgument(string != null); - return Registry.INSTRUMENT.get(NamespacedKey.fromString(string)); + return io.papermc.paper.util.Holderable.fromBukkitSerializationObject(string, Instrument.CODEC, Registry.INSTRUMENT); // Paper - switch to Holder } private final NamespacedKey key; private final Instrument handle; - public CraftMusicInstrument(NamespacedKey key, Instrument handle) { - this.key = key; - this.handle = handle; + // Paper start - switch to Holder + @Override + public boolean equals(final Object o) { + return this.implEquals(o); } @Override - public Instrument getHandle() { - return this.handle; + public int hashCode() { + return this.implHashCode(); + } + + @Override + public String toString() { + return this.implToString(); + } + + private final Holder holder; + public CraftMusicInstrument(Holder holder) { + this.holder = holder; + this.key = holder.unwrapKey().map(io.papermc.paper.util.MCUtil::fromResourceKey).orElse(null); + this.handle = holder.value(); + // Paper end - switch to Holder + } + + @Override + public Holder getHolder() { // Paper - switch to Holder + return this.holder; // Paper - switch to Holder } @NotNull @@ -79,26 +89,5 @@ public class CraftMusicInstrument extends MusicInstrument implements Handleable< } // Paper end - add translationKey methods - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - - if (!(other instanceof CraftMusicInstrument)) { - return false; - } - - return this.getKey().equals(((MusicInstrument) other).getKey()); - } - - @Override - public int hashCode() { - return this.getKey().hashCode(); - } - - @Override - public String toString() { - return "CraftMusicInstrument{key=" + this.key + "}"; - } + // Paper - switch to Holder } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java index 6532bdaf6c..8f2276fcd2 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java @@ -54,21 +54,17 @@ public class CraftMetaArmor extends CraftMetaItem implements ArmorMeta { Map trimData = SerializableMeta.getObject(Map.class, map, CraftMetaArmor.TRIM.BUKKIT, true); if (trimData != null) { - String materialKeyString = SerializableMeta.getString(trimData, CraftMetaArmor.TRIM_MATERIAL.BUKKIT, true); - String patternKeyString = SerializableMeta.getString(trimData, CraftMetaArmor.TRIM_PATTERN.BUKKIT, true); + Object materialKeyString = SerializableMeta.getObject(Object.class, trimData, CraftMetaArmor.TRIM_MATERIAL.BUKKIT, true); // Paper - switch to Holder + Object patternKeyString = SerializableMeta.getObject(Object.class, trimData, CraftMetaArmor.TRIM_PATTERN.BUKKIT, true); // Paper - switch to Holder if (materialKeyString != null && patternKeyString != null) { - NamespacedKey materialKey = NamespacedKey.fromString(materialKeyString); - NamespacedKey patternKey = NamespacedKey.fromString(patternKeyString); - - if (materialKey != null && patternKey != null) { - TrimMaterial trimMaterial = Registry.TRIM_MATERIAL.get(materialKey); - TrimPattern trimPattern = Registry.TRIM_PATTERN.get(patternKey); - - if (trimMaterial != null && trimPattern != null) { - this.trim = new ArmorTrim(trimMaterial, trimPattern); - } + // Paper start - switch to Holder + TrimMaterial trimMaterial = CraftTrimMaterial.objectToBukkit(materialKeyString); + TrimPattern trimPattern = CraftTrimPattern.objectToBukkit(patternKeyString); + if (trimMaterial != null && trimPattern != null) { + this.trim = new ArmorTrim(trimMaterial, trimPattern); } + // Paper end - switch to Holder } } } @@ -133,9 +129,9 @@ public class CraftMetaArmor extends CraftMetaItem implements ArmorMeta { super.serialize(builder); if (this.hasTrim()) { - Map trimData = new HashMap<>(); - trimData.put(CraftMetaArmor.TRIM_MATERIAL.BUKKIT, this.trim.getMaterial().getKey().toString()); - trimData.put(CraftMetaArmor.TRIM_PATTERN.BUKKIT, this.trim.getPattern().getKey().toString()); + Map trimData = new HashMap<>(); // Paper - switch to Holder + trimData.put(CraftMetaArmor.TRIM_MATERIAL.BUKKIT, CraftTrimMaterial.bukkitToObject(this.trim.getMaterial())); // Paper - switch to Holder + trimData.put(CraftMetaArmor.TRIM_PATTERN.BUKKIT, CraftTrimPattern.bukkitToObject(this.trim.getPattern())); // Paper - switch to Holder builder.put(CraftMetaArmor.TRIM.BUKKIT, trimData); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java index 8423c6b67a..e6c4a08c91 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java @@ -37,7 +37,7 @@ public class CraftMetaMusicInstrument extends CraftMetaItem implements MusicInst CraftMetaMusicInstrument(Map map) { super(map); - String instrumentString = SerializableMeta.getString(map, CraftMetaMusicInstrument.GOAT_HORN_INSTRUMENT.BUKKIT, true); + Object instrumentString = SerializableMeta.getObject(Object.class, map, CraftMetaMusicInstrument.GOAT_HORN_INSTRUMENT.BUKKIT, true); // Paper - switch to Holder if (instrumentString != null) { this.instrument = CraftMusicInstrument.stringToBukkit(instrumentString); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java index 38578ef887..afee459c4d 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java @@ -11,14 +11,14 @@ import org.bukkit.craftbukkit.util.Handleable; import org.bukkit.inventory.meta.trim.TrimMaterial; import org.jetbrains.annotations.NotNull; -public class CraftTrimMaterial implements TrimMaterial, Handleable { +public class CraftTrimMaterial implements TrimMaterial, io.papermc.paper.util.Holderable { // Paper - switch to Holder public static TrimMaterial minecraftToBukkit(net.minecraft.world.item.equipment.trim.TrimMaterial minecraft) { return CraftRegistry.minecraftToBukkit(minecraft, Registries.TRIM_MATERIAL, Registry.TRIM_MATERIAL); } public static TrimMaterial minecraftHolderToBukkit(Holder minecraft) { - return CraftTrimMaterial.minecraftToBukkit(minecraft.value()); + return CraftRegistry.minecraftHolderToBukkit(minecraft, Registry.TRIM_MATERIAL); // Paper - switch to Holder } public static net.minecraft.world.item.equipment.trim.TrimMaterial bukkitToMinecraft(TrimMaterial bukkit) { @@ -26,29 +26,52 @@ public class CraftTrimMaterial implements TrimMaterial, Handleable bukkitToMinecraftHolder(TrimMaterial bukkit) { - Preconditions.checkArgument(bukkit != null); - - net.minecraft.core.Registry registry = CraftRegistry.getMinecraftRegistry(Registries.TRIM_MATERIAL); - - if (registry.wrapAsHolder(CraftTrimMaterial.bukkitToMinecraft(bukkit)) instanceof Holder.Reference holder) { - return holder; - } - - throw new IllegalArgumentException("No Reference holder found for " + bukkit - + ", this can happen if a plugin creates its own trim material without properly registering it."); + return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.TRIM_MATERIAL); // Paper - switch to Holder } private final NamespacedKey key; private final net.minecraft.world.item.equipment.trim.TrimMaterial handle; - public CraftTrimMaterial(NamespacedKey key, net.minecraft.world.item.equipment.trim.TrimMaterial handle) { - this.key = key; - this.handle = handle; + // Paper start - switch to Holder + private final Holder holder; + + public static Object bukkitToObject(TrimMaterial bukkit) { + Preconditions.checkArgument(bukkit != null); + + return ((CraftTrimMaterial) bukkit).toBukkitSerializationObject(net.minecraft.world.item.equipment.trim.TrimMaterial.CODEC); // Paper - switch to Holder + } + + public static TrimMaterial objectToBukkit(Object object) { + Preconditions.checkArgument(object != null); + + return io.papermc.paper.util.Holderable.fromBukkitSerializationObject(object, net.minecraft.world.item.equipment.trim.TrimMaterial.CODEC, Registry.TRIM_MATERIAL); // Paper - switch to Holder } @Override - public net.minecraft.world.item.equipment.trim.TrimMaterial getHandle() { - return this.handle; + public boolean equals(final Object o) { + return this.implEquals(o); + } + + @Override + public int hashCode() { + return this.implHashCode(); + } + + @Override + public String toString() { + return this.implToString(); + } + + public CraftTrimMaterial(final Holder holder) { + this.key = holder.unwrapKey().map(io.papermc.paper.util.MCUtil::fromResourceKey).orElse(null); + this.handle = holder.value(); + this.holder = holder; + // Paper end - switch to Holder + } + + @Override + public Holder getHolder() { // Paper - switch to Holder + return this.holder; // Paper - switch to Holder } @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java index 36df80a8be..da951bd7e4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java @@ -11,14 +11,14 @@ import org.bukkit.craftbukkit.util.Handleable; import org.bukkit.inventory.meta.trim.TrimPattern; import org.jetbrains.annotations.NotNull; -public class CraftTrimPattern implements TrimPattern, Handleable { +public class CraftTrimPattern implements TrimPattern, io.papermc.paper.util.Holderable { // Paper - switch to Holder public static TrimPattern minecraftToBukkit(net.minecraft.world.item.equipment.trim.TrimPattern minecraft) { return CraftRegistry.minecraftToBukkit(minecraft, Registries.TRIM_PATTERN, Registry.TRIM_PATTERN); } public static TrimPattern minecraftHolderToBukkit(Holder minecraft) { - return CraftTrimPattern.minecraftToBukkit(minecraft.value()); + return CraftRegistry.minecraftHolderToBukkit(minecraft, Registry.TRIM_PATTERN); // Paper - switch to Holder } public static net.minecraft.world.item.equipment.trim.TrimPattern bukkitToMinecraft(TrimPattern bukkit) { @@ -26,29 +26,52 @@ public class CraftTrimPattern implements TrimPattern, Handleable bukkitToMinecraftHolder(TrimPattern bukkit) { - Preconditions.checkArgument(bukkit != null); - - net.minecraft.core.Registry registry = CraftRegistry.getMinecraftRegistry(Registries.TRIM_PATTERN); - - if (registry.wrapAsHolder(CraftTrimPattern.bukkitToMinecraft(bukkit)) instanceof Holder.Reference holder) { - return holder; - } - - throw new IllegalArgumentException("No Reference holder found for " + bukkit - + ", this can happen if a plugin creates its own trim pattern without properly registering it."); + return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.TRIM_PATTERN); // Paper - switch to Holder } private final NamespacedKey key; private final net.minecraft.world.item.equipment.trim.TrimPattern handle; - public CraftTrimPattern(NamespacedKey key, net.minecraft.world.item.equipment.trim.TrimPattern handle) { - this.key = key; - this.handle = handle; + // Paper start - switch to Holder + private final Holder holder; // Paper - switch to Holder + + public static Object bukkitToObject(TrimPattern bukkit) { + Preconditions.checkArgument(bukkit != null); + + return ((CraftTrimPattern) bukkit).toBukkitSerializationObject(net.minecraft.world.item.equipment.trim.TrimPattern.CODEC); // Paper - switch to Holder + } + + public static TrimPattern objectToBukkit(Object object) { + Preconditions.checkArgument(object != null); + + return io.papermc.paper.util.Holderable.fromBukkitSerializationObject(object, net.minecraft.world.item.equipment.trim.TrimPattern.CODEC, Registry.TRIM_PATTERN); // Paper - switch to Holder } @Override - public net.minecraft.world.item.equipment.trim.TrimPattern getHandle() { - return this.handle; + public boolean equals(final Object o) { + return this.implEquals(o); + } + + @Override + public int hashCode() { + return this.implHashCode(); + } + + @Override + public String toString() { + return this.implToString(); + } + + public CraftTrimPattern(Holder handle) { + this.key = handle.unwrapKey().map(io.papermc.paper.util.MCUtil::fromResourceKey).orElse(null); + this.handle = handle.value(); + this.holder = handle; + // Paper end - switch to Holder + } + + @Override + public Holder getHolder() { // Paper - switch to Holder + return this.holder; // Paper - switch to Holder } @Override diff --git a/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java b/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java index 01e351f4e2..092b88e0ac 100644 --- a/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java +++ b/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java @@ -269,6 +269,7 @@ public class RegistryConversionTest { Class craftClazz, Class minecraftClazz) throws IllegalAccessException { this.checkValidMinecraftToBukkit(clazz); + if (type == io.papermc.paper.registry.RegistryKey.TRIM_MATERIAL || type == io.papermc.paper.registry.RegistryKey.TRIM_PATTERN || type == io.papermc.paper.registry.RegistryKey.INSTRUMENT) return; // Paper - manually skip for now try { Object minecraft = mock(minecraftClazz);