From 38e4c013b66d2a870e83fe61b2da6bad608c69af Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Sat, 1 Dec 2018 20:26:23 +1100 Subject: SPIGOT-4347: Add API to allow storing arbitrary values on ItemStacks --- .../inventory/CraftCustomTagTypeRegistry.java | 217 +++++++++++++++++++++ .../craftbukkit/inventory/CraftMetaItem.java | 43 +++- .../tags/CraftCustomItemTagContainer.java | 133 +++++++++++++ .../inventory/tags/CraftItemTagAdapterContext.java | 24 +++ .../util/CraftNBTTagConfigSerializer.java | 80 ++++++++ 5 files changed, 496 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/bukkit/craftbukkit/inventory/CraftCustomTagTypeRegistry.java create mode 100644 src/main/java/org/bukkit/craftbukkit/inventory/tags/CraftCustomItemTagContainer.java create mode 100644 src/main/java/org/bukkit/craftbukkit/inventory/tags/CraftItemTagAdapterContext.java create mode 100644 src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java (limited to 'src/main') diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftCustomTagTypeRegistry.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftCustomTagTypeRegistry.java new file mode 100644 index 00000000..22c0abb9 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftCustomTagTypeRegistry.java @@ -0,0 +1,217 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.primitives.Primitives; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import net.minecraft.server.NBTBase; +import net.minecraft.server.NBTTagByte; +import net.minecraft.server.NBTTagByteArray; +import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.NBTTagDouble; +import net.minecraft.server.NBTTagFloat; +import net.minecraft.server.NBTTagInt; +import net.minecraft.server.NBTTagIntArray; +import net.minecraft.server.NBTTagLong; +import net.minecraft.server.NBTTagLongArray; +import net.minecraft.server.NBTTagShort; +import net.minecraft.server.NBTTagString; +import org.apache.commons.lang3.Validate; +import org.bukkit.craftbukkit.inventory.tags.CraftCustomItemTagContainer; +import org.bukkit.inventory.meta.tags.CustomItemTagContainer; + +/** + * This class represents a registry that contains the used adapters for. + */ +public final class CraftCustomTagTypeRegistry { + + private final Function CREATE_ADAPTER = this::createAdapter; + + private class CustomTagAdapter { + + private final Function builder; + private final Function extractor; + + private final Class primitiveType; + private final Class nbtBaseType; + + public CustomTagAdapter(Class primitiveType, Class nbtBaseType, Function builder, Function extractor) { + this.primitiveType = primitiveType; + this.nbtBaseType = nbtBaseType; + this.builder = builder; + this.extractor = extractor; + } + + /** + * This method will extract the value stored in the tag, according to + * the expected primitive type. + * + * @param base the base to extract from + * @return the value stored inside of the tag + * @throws ClassCastException if the passed base is not an instanced of + * the defined base type and therefore is not applicable to the + * extractor function + */ + T extract(NBTBase base) { + Validate.isInstanceOf(nbtBaseType, base, "The provided NBTBase was of the type %s. Expected type %s", base.getClass().getSimpleName(), nbtBaseType.getSimpleName()); + return this.extractor.apply(nbtBaseType.cast(base)); + } + + /** + * Builds a tag instance wrapping around the provided value object. + * + * @param value the value to store inside the created tag + * @return the new tag instance + * @throws ClassCastException if the passed value object is not of the + * defined primitive type and therefore is not applicable to the builder + * function + */ + Z build(Object value) { + Validate.isInstanceOf(primitiveType, value, "The provided value was of the type %s. Expected type %s", value.getClass().getSimpleName(), primitiveType.getSimpleName()); + return this.builder.apply(primitiveType.cast(value)); + } + + /** + * Returns if the tag instance matches the adapters one. + * + * @param base the base to check + * @return if the tag was an instance of the set type + */ + boolean isInstance(NBTBase base) { + return this.nbtBaseType.isInstance(base); + } + } + + private final Map adapters = new HashMap<>(); + + /** + * Creates a suitable adapter instance for the primitive class type + * + * @param type the type to create an adapter for + * @param the generic type of that class + * @return the created adapter instance + * @throws IllegalArgumentException if no suitable tag type adapter for this + * type was found + */ + private CustomTagAdapter createAdapter(Class type) { + if (!Primitives.isWrapperType(type)) { + type = Primitives.wrap(type); //Make sure we will always "switch" over the wrapper types + } + + /* + Primitives + */ + if (Objects.equals(Byte.class, type)) { + return createAdapter(Byte.class, NBTTagByte.class, NBTTagByte::new, NBTTagByte::g); // PAIL: rename asByte + } + if (Objects.equals(Short.class, type)) { + return createAdapter(Short.class, NBTTagShort.class, NBTTagShort::new, NBTTagShort::f); // PAIL: rename asShort + } + if (Objects.equals(Integer.class, type)) { + return createAdapter(Integer.class, NBTTagInt.class, NBTTagInt::new, NBTTagInt::e); // PAIL: rename asInteger + } + if (Objects.equals(Long.class, type)) { + return createAdapter(Long.class, NBTTagLong.class, NBTTagLong::new, NBTTagLong::d); // PAIL: rename asLong + } + if (Objects.equals(Float.class, type)) { + return createAdapter(Float.class, NBTTagFloat.class, NBTTagFloat::new, NBTTagFloat::i); // PAIL: rename asFloat + } + if (Objects.equals(Double.class, type)) { + return createAdapter(Double.class, NBTTagDouble.class, NBTTagDouble::new, NBTTagDouble::asDouble); + } + + /* + String + */ + if (Objects.equals(String.class, type)) { + return createAdapter(String.class, NBTTagString.class, NBTTagString::new, NBTTagString::b_); // PAIL: rename getString + } + + /* + Primitive Arrays + */ + if (Objects.equals(byte[].class, type)) { + return createAdapter(byte[].class, NBTTagByteArray.class, array -> new NBTTagByteArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.c(), n.size())); // PAIL: rename getByteArray + } + if (Objects.equals(int[].class, type)) { + return createAdapter(int[].class, NBTTagIntArray.class, array -> new NBTTagIntArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.d(), n.size())); // PAIL: rename getIntegerArray + } + if (Objects.equals(long[].class, type)) { + return createAdapter(long[].class, NBTTagLongArray.class, array -> new NBTTagLongArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.d(), n.size())); // PAIL: rename getLongArray + } + + /* + Note that this will map the interface CustomItemTagContainer directly to the CraftBukkit implementation + Passing any other instance of this form to the tag type registry will throw a ClassCastException as defined in CustomTagAdapter#build + */ + if (Objects.equals(CustomItemTagContainer.class, type)) { + return createAdapter(CraftCustomItemTagContainer.class, NBTTagCompound.class, CraftCustomItemTagContainer::toTagCompound, tag -> { + CraftCustomItemTagContainer container = new CraftCustomItemTagContainer(this); + for (String key : tag.getKeys()) { + container.put(key, tag.get(key)); + } + return container; + }); + } + + throw new IllegalArgumentException("Could not find a valid CustomTagAdapter implementation for the requested type " + type.getSimpleName()); + } + + private CustomTagAdapter createAdapter(Class primitiveType, Class nbtBaseType, Function builder, Function extractor) { + return new CustomTagAdapter<>(primitiveType, nbtBaseType, builder, extractor); + } + + /** + * Wraps the passed value into a tag instance. + * + * @param type the type of the passed value + * @param value the value to be stored in the tag + * @param the generic type of the value + * @return the created tag instance + * @throws IllegalArgumentException if no suitable tag type adapter for this + * type was found + */ + public NBTBase wrap(Class type, T value) { + return this.adapters.computeIfAbsent(type, CREATE_ADAPTER).build(value); + } + + /** + * Returns if the tag instance matches the provided primitive type. + * + * @param type the type of the primitive value + * @param base the base instance to check + * @param the generic type of the type + * @return if the base stores values of the primitive type passed + * @throws IllegalArgumentException if no suitable tag type adapter for this + * type was found + */ + public boolean isInstanceOf(Class type, NBTBase base) { + return this.adapters.computeIfAbsent(type, CREATE_ADAPTER).isInstance(base); + } + + /** + * Extracts the value out of the provided tag. + * + * @param type the type of the value to extract + * @param tag the tag to extract the value from + * @param the generic type of the value stored inside the tag + * @return the extracted value + * @throws IllegalArgumentException if the passed base is not an instanced + * of the defined base type and therefore is not applicable to the extractor + * function + * @throws IllegalArgumentException if the found object is not of type + * passed + * @throws IllegalArgumentException if no suitable tag type adapter for this + * type was found + */ + public T extract(Class type, NBTBase tag) throws ClassCastException, IllegalArgumentException { + CustomTagAdapter adapter = this.adapters.computeIfAbsent(type, CREATE_ADAPTER); + Validate.isTrue(adapter.isInstance(tag), "`The found tag instance cannot store %s as it is a %s", type.getSimpleName(), tag.getClass().getSimpleName()); + + Object foundValue = adapter.extract(tag); + Validate.isInstanceOf(type, foundValue, "The found object is of the type %s. Expected type %s", foundValue.getClass().getSimpleName(), type.getSimpleName()); + return type.cast(foundValue); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java index 60cdd1dc..19e52317 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -42,8 +42,10 @@ import org.bukkit.craftbukkit.Overridden; import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; import org.bukkit.craftbukkit.attribute.CraftAttributeMap; import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey.Specific; +import org.bukkit.craftbukkit.inventory.tags.CraftCustomItemTagContainer; import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNBTTagConfigSerializer; import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.EquipmentSlot; @@ -51,6 +53,7 @@ import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.Repairable; +import org.bukkit.inventory.meta.tags.CustomItemTagContainer; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -244,6 +247,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { static final ItemMetaKey UNBREAKABLE = new ItemMetaKey("Unbreakable"); @Specific(Specific.To.NBT) static final ItemMetaKey DAMAGE = new ItemMetaKey("Damage"); + static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey("PublicBukkitValues"); private IChatBaseComponent displayName; private IChatBaseComponent locName; @@ -256,9 +260,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { private int damage; private static final Set HANDLED_TAGS = Sets.newHashSet(); + private static final CraftCustomTagTypeRegistry TAG_TYPE_REGISTRY = new CraftCustomTagTypeRegistry(); private NBTTagCompound internalTag; private final Map unhandledTags = new HashMap(); + private final CraftCustomItemTagContainer publicItemTagContainer = new CraftCustomItemTagContainer(TAG_TYPE_REGISTRY); CraftMetaItem(CraftMetaItem meta) { if (meta == null) { @@ -285,6 +291,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { this.unbreakable = meta.unbreakable; this.damage = meta.damage; this.unhandledTags.putAll(meta.unhandledTags); + this.publicItemTagContainer.putAll(meta.publicItemTagContainer.getRaw()); this.internalTag = meta.internalTag; if (this.internalTag != null) { @@ -339,6 +346,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { if (tag.hasKey(DAMAGE.NBT)) { damage = tag.getInt(DAMAGE.NBT); } + if (tag.hasKey(BUKKIT_CUSTOM_TAG.NBT)) { + NBTTagCompound compound = tag.getCompound(BUKKIT_CUSTOM_TAG.NBT); + Set keys = compound.getKeys(); + for (String key : keys) { + publicItemTagContainer.put(key, compound.get(key)); + } + } Set keys = tag.getKeys(); for (String key : keys) { @@ -476,6 +490,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); } } + + Map nbtMap = SerializableMeta.getObject(Map.class, map, BUKKIT_CUSTOM_TAG.BUKKIT, true); + if (nbtMap != null) { + this.publicItemTagContainer.putAll((NBTTagCompound) CraftNBTTagConfigSerializer.deserialize(nbtMap)); + } } void deserializeInternal(NBTTagCompound tag, Object context) { @@ -575,6 +594,16 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { for (Map.Entry e : unhandledTags.entrySet()) { itemTag.set(e.getKey(), e.getValue()); } + + if (!publicItemTagContainer.isEmpty()) { + NBTTagCompound bukkitCustomCompound = new NBTTagCompound(); + Map rawPublicMap = publicItemTagContainer.getRaw(); + + for (Map.Entry nbtBaseEntry : rawPublicMap.entrySet()) { + bukkitCustomCompound.set(nbtBaseEntry.getKey(), nbtBaseEntry.getValue()); + } + itemTag.set(BUKKIT_CUSTOM_TAG.NBT, bukkitCustomCompound); + } } static NBTTagList createStringList(List list) { @@ -659,7 +688,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { @Overridden boolean isEmpty() { - return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); + return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || !publicItemTagContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); } public String getDisplayName() { @@ -926,6 +955,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { return removed > 0; } + @Override + public CustomItemTagContainer getCustomTagContainer() { + return this.publicItemTagContainer; + } + private static boolean compareModifiers(Multimap first, Multimap second) { if (first == null || second == null) { return false; @@ -986,6 +1020,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { && (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost()) && (this.hasAttributeModifiers() ? that.hasAttributeModifiers() && compareModifiers(this.attributeModifiers, that.attributeModifiers) : !that.hasAttributeModifiers()) && (this.unhandledTags.equals(that.unhandledTags)) + && (this.publicItemTagContainer.equals(that.publicItemTagContainer)) && (this.hideFlag == that.hideFlag) && (this.isUnbreakable() == that.isUnbreakable()) && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()); @@ -1015,6 +1050,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { hash = 61 * hash + (hasEnchants() ? this.enchantments.hashCode() : 0); hash = 61 * hash + (hasRepairCost() ? this.repairCost : 0); hash = 61 * hash + unhandledTags.hashCode(); + hash = 61 * hash + (!publicItemTagContainer.isEmpty() ? publicItemTagContainer.hashCode() : 0); hash = 61 * hash + hideFlag; hash = 61 * hash + (isUnbreakable() ? 1231 : 1237); hash = 61 * hash + (hasDamage() ? this.damage : 0); @@ -1104,6 +1140,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { } } + if (!publicItemTagContainer.isEmpty()) { // Store custom tags, wrapped in their compound + builder.put(BUKKIT_CUSTOM_TAG.BUKKIT, publicItemTagContainer.serialize()); + } + return builder; } @@ -1199,6 +1239,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { HIDEFLAGS.NBT, UNBREAKABLE.NBT, DAMAGE.NBT, + BUKKIT_CUSTOM_TAG.NBT, ATTRIBUTES.NBT, ATTRIBUTES_IDENTIFIER.NBT, ATTRIBUTES_NAME.NBT, diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/tags/CraftCustomItemTagContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/tags/CraftCustomItemTagContainer.java new file mode 100644 index 00000000..fe663bba --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/tags/CraftCustomItemTagContainer.java @@ -0,0 +1,133 @@ +package org.bukkit.craftbukkit.inventory.tags; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import net.minecraft.server.NBTBase; +import net.minecraft.server.NBTTagCompound; +import org.apache.commons.lang.Validate; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.inventory.CraftCustomTagTypeRegistry; +import org.bukkit.craftbukkit.util.CraftNBTTagConfigSerializer; +import org.bukkit.inventory.meta.tags.CustomItemTagContainer; +import org.bukkit.inventory.meta.tags.ItemTagAdapterContext; +import org.bukkit.inventory.meta.tags.ItemTagType; + +public final class CraftCustomItemTagContainer implements CustomItemTagContainer { + + private final Map customTags = new HashMap<>(); + private final CraftCustomTagTypeRegistry tagTypeRegistry; + private final CraftItemTagAdapterContext adapterContext; + + public CraftCustomItemTagContainer(Map customTags, CraftCustomTagTypeRegistry tagTypeRegistry) { + this(tagTypeRegistry); + this.customTags.putAll(customTags); + } + + public CraftCustomItemTagContainer(CraftCustomTagTypeRegistry tagTypeRegistry) { + this.tagTypeRegistry = tagTypeRegistry; + this.adapterContext = new CraftItemTagAdapterContext(this.tagTypeRegistry); + } + + @Override + public void setCustomTag(NamespacedKey key, ItemTagType type, Z value) { + Validate.notNull(key, "The provided key for the custom value was null"); + Validate.notNull(type, "The provided type for the custom value was null"); + Validate.notNull(value, "The provided value for the custom value was null"); + + this.customTags.put(key.toString(), tagTypeRegistry.wrap(type.getPrimitiveType(), type.toPrimitive(value, adapterContext))); + } + + @Override + public boolean hasCustomTag(NamespacedKey key, ItemTagType type) { + Validate.notNull(key, "The provided key for the custom value was null"); + Validate.notNull(type, "The provided type for the custom value was null"); + + NBTBase value = this.customTags.get(key.toString()); + if (value == null) { + return false; + } + + return tagTypeRegistry.isInstanceOf(type.getPrimitiveType(), value); + } + + @Override + public Z getCustomTag(NamespacedKey key, ItemTagType type) { + Validate.notNull(key, "The provided key for the custom value was null"); + Validate.notNull(type, "The provided type for the custom value was null"); + + NBTBase value = this.customTags.get(key.toString()); + if (value == null) { + return null; + } + + return type.fromPrimitive(tagTypeRegistry.extract(type.getPrimitiveType(), value), adapterContext); + } + + @Override + public void removeCustomTag(NamespacedKey key) { + Validate.notNull(key, "The provided key for the custom value was null"); + + this.customTags.remove(key.toString()); + } + + @Override + public boolean isEmpty() { + return this.customTags.isEmpty(); + } + + @Override + public ItemTagAdapterContext getAdapterContext() { + return this.adapterContext; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CraftCustomItemTagContainer)) { + return false; + } + + Map myRawMap = getRaw(); + Map theirRawMap = ((CraftCustomItemTagContainer) obj).getRaw(); + + return Objects.equals(myRawMap, theirRawMap); + } + + public NBTTagCompound toTagCompound() { + NBTTagCompound tag = new NBTTagCompound(); + for (Entry entry : this.customTags.entrySet()) { + tag.set(entry.getKey(), entry.getValue()); + } + return tag; + } + + public void put(String key, NBTBase base) { + this.customTags.put(key, base); + } + + public void putAll(Map map) { + this.customTags.putAll(map); + } + + public void putAll(NBTTagCompound compound) { + for (String key : compound.getKeys()) { + this.customTags.put(key, compound.get(key)); + } + } + + public Map getRaw() { + return this.customTags; + } + + @Override + public int hashCode() { + int hashCode = 3; + hashCode += this.customTags.hashCode(); // We will simply add the maps hashcode + return hashCode; + } + + public Map serialize() { + return (Map) CraftNBTTagConfigSerializer.serialize(toTagCompound()); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/tags/CraftItemTagAdapterContext.java b/src/main/java/org/bukkit/craftbukkit/inventory/tags/CraftItemTagAdapterContext.java new file mode 100644 index 00000000..5b81cad1 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/tags/CraftItemTagAdapterContext.java @@ -0,0 +1,24 @@ +package org.bukkit.craftbukkit.inventory.tags; + +import org.bukkit.craftbukkit.inventory.CraftCustomTagTypeRegistry; +import org.bukkit.inventory.meta.tags.CustomItemTagContainer; +import org.bukkit.inventory.meta.tags.ItemTagAdapterContext; + +public final class CraftItemTagAdapterContext implements ItemTagAdapterContext { + + private final CraftCustomTagTypeRegistry registry; + + public CraftItemTagAdapterContext(CraftCustomTagTypeRegistry registry) { + this.registry = registry; + } + + /** + * Creates a new and empty tag container instance + * + * @return the fresh container instance + */ + @Override + public CustomItemTagContainer newTagContainer() { + return new CraftCustomItemTagContainer(this.registry); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java b/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java new file mode 100644 index 00000000..da81d782 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java @@ -0,0 +1,80 @@ +package org.bukkit.craftbukkit.util; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import net.minecraft.server.MojangsonParser; +import net.minecraft.server.NBTBase; +import net.minecraft.server.NBTList; +import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.NBTTagList; +import net.minecraft.server.NBTTagString; + +public class CraftNBTTagConfigSerializer { + + private static final Pattern ARRAY = Pattern.compile("^\\[.*]"); + private static final MojangsonParser MOJANGSON_PARSER = new MojangsonParser(new StringReader("")); + + public static Object serialize(NBTBase base) { + if (base instanceof NBTTagCompound) { + Map innerMap = new HashMap<>(); + for (String key : ((NBTTagCompound) base).getKeys()) { + innerMap.put(key, serialize(((NBTTagCompound) base).get(key))); + } + + return innerMap; + } else if (base instanceof NBTTagList) { + List baseList = new ArrayList<>(); + for (int i = 0; i < ((NBTList) base).size(); i++) { + baseList.add(serialize(((NBTList) base).get(i))); + } + + return baseList; + } else if (base instanceof NBTTagString) { + return base.b_(); //PAIL Rename getString + } + + return base.toString(); + } + + public static NBTBase deserialize(Object object) { + if (object instanceof Map) { + NBTTagCompound compound = new NBTTagCompound(); + for (Map.Entry entry : ((Map) object).entrySet()) { + compound.set(entry.getKey(), deserialize(entry.getValue())); + } + + return compound; + } else if (object instanceof List) { + List list = (List) object; + if (list.isEmpty()) { + return new NBTTagList(); // Default + } + + NBTTagList tagList = new NBTTagList(); + for (Object tag : list) { + tagList.add(deserialize(tag)); + } + + return tagList; + } else if (object instanceof String) { + String string = (String) object; + + if (ARRAY.matcher(string).matches()) { + try { + return new MojangsonParser(new StringReader(string)).h(); // PAIL Rename parseTagList + } catch (CommandSyntaxException e) { + throw new RuntimeException("Could not deserialize found list ", e); + } + } else { + return MOJANGSON_PARSER.b(string); // PAIL Rename parse tagBase + } + } + + throw new RuntimeException("Could not deserialize NBTBase"); + } +} -- cgit v1.2.3