From b4497f9b26aa6b6109f8347ab54b1adf83e7bf6b Mon Sep 17 00:00:00 2001 From: Olof Larsson Date: Sat, 10 Nov 2012 19:17:46 +0100 Subject: [PATCH] Beta version of better ItemStack serialization using a NBT <--> Gson converter. --- .../mcore4/adapter/ItemStackAdapter.java | 84 ++++- .../massivecraft/mcore4/adapter/NBType.java | 93 +++++ .../mcore4/adapter/NbtGsonConverter.java | 328 ++++++++++++++++++ src/com/massivecraft/mcore4/util/MUtil.java | 17 + 4 files changed, 503 insertions(+), 19 deletions(-) create mode 100644 src/com/massivecraft/mcore4/adapter/NBType.java create mode 100644 src/com/massivecraft/mcore4/adapter/NbtGsonConverter.java diff --git a/src/com/massivecraft/mcore4/adapter/ItemStackAdapter.java b/src/com/massivecraft/mcore4/adapter/ItemStackAdapter.java index 949de39e..586644ce 100644 --- a/src/com/massivecraft/mcore4/adapter/ItemStackAdapter.java +++ b/src/com/massivecraft/mcore4/adapter/ItemStackAdapter.java @@ -3,6 +3,10 @@ package com.massivecraft.mcore4.adapter; import java.lang.reflect.Type; import java.util.Map.Entry; +import net.minecraft.server.NBTBase; +import net.minecraft.server.NBTTagCompound; + +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; @@ -24,6 +28,7 @@ public class ItemStackAdapter implements JsonDeserializer, JsonSerial public static final String AMOUNT = "amount"; public static final String DAMAGE = "damage"; public static final String ENCHANTMENTS = "enchantments"; + public static final String TAG = "tag"; // -------------------------------------------- // // IMPLEMENTATION @@ -45,39 +50,71 @@ public class ItemStackAdapter implements JsonDeserializer, JsonSerial // JSON // -------------------------------------------- // - public static JsonObject toJson(ItemStack itemStack) + public static JsonObject toJson(ItemStack stack) { - if (itemStack == null || itemStack.getTypeId() == 0 || itemStack.getAmount() == 0) + // Check for "nothing" + if (stack == null || stack.getTypeId() == 0 || stack.getAmount() == 0) { return null; } JsonObject jsonItemStack = new JsonObject(); - jsonItemStack.addProperty(ItemStackAdapter.TYPE, itemStack.getTypeId()); + // Add type id + jsonItemStack.addProperty(TYPE, stack.getTypeId()); - if (itemStack.getAmount() != 1) + // Add amount + if (stack.getAmount() != 1) { - jsonItemStack.addProperty(ItemStackAdapter.AMOUNT, itemStack.getAmount()); + jsonItemStack.addProperty(AMOUNT, stack.getAmount()); } - if (itemStack.getDurability() != 0) // Durability is a weird name since it is the amount of damage. + + // Add damage + if (stack.getDurability() != 0) // Durability is a weird name since it is the amount of damage. { - jsonItemStack.addProperty(ItemStackAdapter.DAMAGE, itemStack.getDurability()); + jsonItemStack.addProperty(DAMAGE, stack.getDurability()); } - if (itemStack.getEnchantments().size() > 0) + + // Add enchantments + if (stack.getEnchantments().size() > 0) { JsonObject jsonEnchantments = new JsonObject(); - for (Entry entry : itemStack.getEnchantments().entrySet()) + for (Entry entry : stack.getEnchantments().entrySet()) { jsonEnchantments.addProperty(String.valueOf(entry.getKey().getId()), entry.getValue()); } jsonItemStack.add(ItemStackAdapter.ENCHANTMENTS, jsonEnchantments); } + + // Add the tag if there is one + JsonObject tag = getEnchFreeGsonTagFromItemStack(stack); + if (tag != null) + { + jsonItemStack.add(TAG, tag); + } + return jsonItemStack; } + // Used by method toJson + public static JsonObject getEnchFreeGsonTagFromItemStack(ItemStack stack) + { + if (!(stack instanceof CraftItemStack)) return null; + CraftItemStack craftItemStack = (CraftItemStack)stack; + + NBTTagCompound nbt = craftItemStack.getHandle().tag; + if (nbt == null) return null; + + JsonObject gsonbt = (JsonObject) NbtGsonConverter.nbtToGsonVal(nbt); + gsonbt.remove("ench"); + if (gsonbt.entrySet().size() == 0) return null; + + return gsonbt; + } + public static ItemStack fromJson(JsonElement json) { + // Check for "nothing" if (json == null || ! json.isJsonObject()) return null; JsonObject jsonItemStack = json.getAsJsonObject(); @@ -87,28 +124,37 @@ public class ItemStackAdapter implements JsonDeserializer, JsonSerial int amount = 1; short damage = 0; - if (jsonItemStack.has(ItemStackAdapter.TYPE)) + if (jsonItemStack.has(TYPE)) { - type = jsonItemStack.get(ItemStackAdapter.TYPE).getAsInt(); + type = jsonItemStack.get(TYPE).getAsInt(); } - if (jsonItemStack.has(ItemStackAdapter.AMOUNT)) + if (jsonItemStack.has(AMOUNT)) { - amount = jsonItemStack.get(ItemStackAdapter.AMOUNT).getAsInt(); + amount = jsonItemStack.get(AMOUNT).getAsInt(); } - if (jsonItemStack.has(ItemStackAdapter.DAMAGE)) + if (jsonItemStack.has(DAMAGE)) { - damage = jsonItemStack.get(ItemStackAdapter.DAMAGE).getAsShort(); + damage = jsonItemStack.get(DAMAGE).getAsShort(); } // Create Non enchanted stack - ItemStack stack = new ItemStack(type, amount, damage); + CraftItemStack stack = new CraftItemStack(type, amount, damage); - // Add enchantments if there are any - if (jsonItemStack.has(ItemStackAdapter.ENCHANTMENTS)) + // Add tag + if (jsonItemStack.has(TAG)) { - JsonObject jsonEnchantments = jsonItemStack.get(ItemStackAdapter.ENCHANTMENTS).getAsJsonObject(); + JsonObject jsonbt = jsonItemStack.get(TAG).getAsJsonObject(); + CraftItemStack craftItemStack = stack; + NBTBase nbt = NbtGsonConverter.gsonValToNbt(jsonbt, null, NBType.COMPOUND, NBType.UNKNOWN); + craftItemStack.getHandle().tag = (NBTTagCompound) nbt; + } + + // Add enchantments if there are any + if (jsonItemStack.has(ENCHANTMENTS)) + { + JsonObject jsonEnchantments = jsonItemStack.get(ENCHANTMENTS).getAsJsonObject(); for (Entry enchantmentEntry: jsonEnchantments.entrySet()) { int enchantmentId = Integer.valueOf(enchantmentEntry.getKey()); diff --git a/src/com/massivecraft/mcore4/adapter/NBType.java b/src/com/massivecraft/mcore4/adapter/NBType.java new file mode 100644 index 00000000..0c5783ab --- /dev/null +++ b/src/com/massivecraft/mcore4/adapter/NBType.java @@ -0,0 +1,93 @@ +package com.massivecraft.mcore4.adapter; + +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.server.NBTBase; + +import lombok.Getter; + +public enum NBType +{ + // -------------------------------------------- // + // VALUES + // -------------------------------------------- // + + END(0, "end"), + BYTE(1, "byte"), + SHORT(2, "short"), + INT(3, "int"), + LONG(4, "long"), + FLOAT(5, "float"), + DOUBLE(6, "double"), + BYTEARRAY(7, "bytearray"), + STRING(8, "string"), + LIST(9, "list"), + COMPOUND(10, "compound"), + INTARRAY(11, "intarray"), + UNKNOWN(-1, "unknown"), + ; + + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + @Getter final byte id; + @Getter final String name; + + // -------------------------------------------- // + // CONSTRUCT + // -------------------------------------------- // + + private NBType(int id, String name) + { + this.id = (byte)id; + this.name = name; + } + + // -------------------------------------------- // + // STATIC UTILS + // -------------------------------------------- // + + protected final static transient Map tagnameToEnum = new HashMap(); + protected final static transient Map byteToEnum = new HashMap(); + + static + { + for (NBType value : values()) + { + tagnameToEnum.put(value.getName(), value); + byteToEnum.put(value.getId(), value); + } + } + + public static NBType getByName(String name) + { + NBType ret = tagnameToEnum.get(name); + if (ret == null) + { + ret = UNKNOWN; + } + return ret; + } + + public static NBType getById(byte id) + { + NBType ret = byteToEnum.get(id); + if (ret == null) + { + ret = UNKNOWN; + } + return ret; + } + + public static NBType getByTag(NBTBase tag) + { + NBType ret = byteToEnum.get(tag.getTypeId()); + if (ret == null) + { + ret = UNKNOWN; + } + return ret; + } +} diff --git a/src/com/massivecraft/mcore4/adapter/NbtGsonConverter.java b/src/com/massivecraft/mcore4/adapter/NbtGsonConverter.java new file mode 100644 index 00000000..fe7e02e6 --- /dev/null +++ b/src/com/massivecraft/mcore4/adapter/NbtGsonConverter.java @@ -0,0 +1,328 @@ +package com.massivecraft.mcore4.adapter; + +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.NBTTagEnd; +import net.minecraft.server.NBTTagFloat; +import net.minecraft.server.NBTTagInt; +import net.minecraft.server.NBTTagIntArray; +import net.minecraft.server.NBTTagList; +import net.minecraft.server.NBTTagLong; +import net.minecraft.server.NBTTagShort; +import net.minecraft.server.NBTTagString; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map.Entry; + +import com.massivecraft.mcore4.xlib.gson.JsonArray; +import com.massivecraft.mcore4.xlib.gson.JsonElement; +import com.massivecraft.mcore4.xlib.gson.JsonObject; +import com.massivecraft.mcore4.xlib.gson.JsonPrimitive; + +public class NbtGsonConverter +{ + // -------------------------------------------- // + // CONSTANTS + // -------------------------------------------- // + + public final static String TYPE = "type"; + public final static String ELEMTYPE = "elemtype"; + public final static String VAL = "val"; + public final static String NAME = "name"; + + // -------------------------------------------- // + // GSON 2 NBT + // -------------------------------------------- // + + public static NBTBase gsonToNbt(JsonElement jsonElement) + { + return gsonToNbt(jsonElement, null); + } + + public static NBTBase gsonToNbt(JsonElement jsonElement, String name) + { + // Verify and cast the jsonElement into a jsonObject. + // We could have used jsonObject as parameter type but this method signature is more flexible. + if (!jsonElement.isJsonObject()) + { + // must be a json object + return null; + } + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + // Use the internal name if there is one + JsonElement nameElement = jsonObject.get(NAME); + if (nameElement != null) + { + name = nameElement.getAsString(); + } + + // Fetch the type-info + JsonElement typeElement = jsonObject.get(TYPE); + if (typeElement == null) + { + // must have a type + return null; + } + NBType type = NBType.getByName(typeElement.getAsString()); + + // Fetch the elemtype-info (used by NBTTagList only) + NBType elemtype = NBType.UNKNOWN; + if (type == NBType.LIST) + { + JsonElement elemtypeElement = jsonObject.get(ELEMTYPE); + if (elemtypeElement == null) + { + // must have an elemtype + return null; + } + elemtype = NBType.getByName(elemtypeElement.getAsString()); + } + + // Fetch the value field + JsonElement val = jsonObject.get(VAL); + + // Convert the value based on the info we gathered + return gsonValToNbt(val, name, type, elemtype); + } + + public static NBTBase gsonValToNbt(JsonElement val, String name, NBType type, NBType elemtype) + { + NBTBase ret = null; + + switch (type) + { + case END: + ret = new NBTTagEnd(); + break; + + case BYTE: + ret = new NBTTagByte(name, val.getAsByte()); + break; + + case SHORT: + ret = new NBTTagShort(name, val.getAsShort()); + break; + + case INT: + ret = new NBTTagInt(name, val.getAsInt()); + break; + + case LONG: + ret = new NBTTagLong(name, val.getAsLong()); + break; + + case FLOAT: + ret = new NBTTagFloat(name, val.getAsFloat()); + break; + + case DOUBLE: + ret = new NBTTagDouble(name, val.getAsDouble()); + break; + + case BYTEARRAY: + JsonArray jsonBytes = val.getAsJsonArray(); + int jsonBytesSize = jsonBytes.size(); + byte[] byteArray = new byte[jsonBytesSize]; + for (int index = 0 ; index < jsonBytesSize ; index++) + { + byte b = jsonBytes.get(index).getAsByte(); + byteArray[index] = b; + } + ret = new NBTTagByteArray(name, byteArray); + break; + + case INTARRAY: + JsonArray jsonInts = val.getAsJsonArray(); + int jsonIntsSize = jsonInts.size(); + int[] intArray = new int[jsonIntsSize]; + for (int index = 0 ; index < jsonIntsSize ; index++) + { + int i = jsonInts.get(index).getAsInt(); + intArray[index] = i; + } + ret = new NBTTagIntArray(name, intArray); + break; + + case STRING: + ret = new NBTTagString(name, val.getAsString()); + break; + + case LIST: + NBTTagList nbtlist = new NBTTagList(name); + + if (!val.isJsonArray()) + { + // must be an array + return null; + } + + Iterator iter = val.getAsJsonArray().iterator(); + while (iter.hasNext()) + { + nbtlist.add(gsonValToNbt(iter.next(), null, elemtype, NBType.UNKNOWN)); + } + ret = nbtlist; + break; + + case COMPOUND: + NBTTagCompound compound = new NBTTagCompound(name); + + if (!val.isJsonObject()) + { + // must be an object + return null; + } + + JsonObject jsonCompound = val.getAsJsonObject(); + + for(Entry entry : jsonCompound.entrySet()) + { + String childName = entry.getKey(); + JsonElement childJson = entry.getValue(); + NBTBase child = gsonToNbt(childJson, childName); + if (child == null) continue; + compound.set(childName, child); + } + + ret = compound; + break; + } + + return ret; + } + + // -------------------------------------------- // + // NBT TO GSON + // -------------------------------------------- // + + public static JsonObject nbtToGson(NBTBase nbt, boolean includeName) + { + JsonObject ret = new JsonObject(); + + String name = nbt.getName(); + if (includeName && name != null) + { + ret.addProperty(NAME, name); + } + + NBType type = NBType.getById(nbt.getTypeId()); + ret.addProperty(TYPE, type.getName()); + + if (type == NBType.LIST) + { + ret.addProperty(ELEMTYPE, NBType.getByTag(((NBTTagList)nbt).get(0)).getName()); + } + + JsonElement val = nbtToGsonVal(nbt); + if (val == null) + { + return null; + } + ret.add(VAL, val); + + return ret; + } + + public static JsonElement nbtToGsonVal(NBTBase nbt) + { + JsonElement val = null; + + NBType type = NBType.getByTag(nbt); + + switch (type) + { + case END: + // this should never happen + break; + + case BYTE: + val = new JsonPrimitive(((NBTTagByte) nbt).data); + break; + + case SHORT: + val = new JsonPrimitive(((NBTTagShort) nbt).data); + break; + + case INT: + val = new JsonPrimitive(((NBTTagInt) nbt).data); + break; + + case LONG: + val = new JsonPrimitive(((NBTTagLong) nbt).data); + break; + + case FLOAT: + val = new JsonPrimitive(((NBTTagFloat) nbt).data); + break; + + case DOUBLE: + val = new JsonPrimitive(((NBTTagDouble) nbt).data); + break; + + case BYTEARRAY: + JsonArray jsonBytes = new JsonArray(); + for (byte elem : ((NBTTagByteArray) nbt).data) + { + jsonBytes.add(new JsonPrimitive(elem)); + } + val = jsonBytes; + break; + + case INTARRAY: + JsonArray jsonInts = new JsonArray(); + for (int elem : ((NBTTagIntArray) nbt).data) + { + jsonInts.add(new JsonPrimitive(elem)); + } + val = jsonInts; + break; + + case STRING: + val = new JsonPrimitive(((NBTTagString) nbt).data); + break; + + case LIST: + NBTTagList nbtlist = (NBTTagList)nbt; + int size = nbtlist.size(); + if (size <= 0) + { + // NBTTagList may not be empty + return null; + } + + JsonArray jsonElems = new JsonArray(); + for (int i = 0 ; i < size ; i++) + { + jsonElems.add(nbtToGsonVal(nbtlist.get(i))); + } + val = jsonElems; + break; + + case COMPOUND: + JsonObject jsonObject = new JsonObject(); + for (NBTBase child : getCompoundChildren((NBTTagCompound)nbt)) + { + jsonObject.add(child.getName(), nbtToGson(child, false)); + } + val = jsonObject; + break; + } + + return val; + } + + // -------------------------------------------- // + // UTILS + // -------------------------------------------- // + + @SuppressWarnings("unchecked") + public static Collection getCompoundChildren(NBTTagCompound compound) + { + return (Collection)compound.c(); + } +} \ No newline at end of file diff --git a/src/com/massivecraft/mcore4/util/MUtil.java b/src/com/massivecraft/mcore4/util/MUtil.java index 44587d77..4f0db4df 100644 --- a/src/com/massivecraft/mcore4/util/MUtil.java +++ b/src/com/massivecraft/mcore4/util/MUtil.java @@ -11,6 +11,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -149,6 +150,22 @@ public class MUtil return ret; } + public static Map flippedMap(Map map) + { + Map ret = new LinkedHashMap(); + + for(Entry entry : map.entrySet()) + { + V value = entry.getValue(); + K key = entry.getKey(); + + if (value == null) continue; + ret.put(value, key); + } + + return ret; + } + // -------------------------------------------- // // LE NICE RANDOM // -------------------------------------------- //