Beta version of better ItemStack serialization using a NBT <--> Gson converter.

This commit is contained in:
Olof Larsson 2012-11-10 19:17:46 +01:00
parent 34a7323f4a
commit b4497f9b26
4 changed files with 503 additions and 19 deletions

View File

@ -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<ItemStack>, 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<ItemStack>, 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<Enchantment, Integer> entry : itemStack.getEnchantments().entrySet())
for (Entry<Enchantment, Integer> 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<ItemStack>, 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<String, JsonElement> enchantmentEntry: jsonEnchantments.entrySet())
{
int enchantmentId = Integer.valueOf(enchantmentEntry.getKey());

View File

@ -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<String, NBType> tagnameToEnum = new HashMap<String, NBType>();
protected final static transient Map<Byte, NBType> byteToEnum = new HashMap<Byte, NBType>();
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;
}
}

View File

@ -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<JsonElement> 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<String, JsonElement> 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<NBTBase> getCompoundChildren(NBTTagCompound compound)
{
return (Collection<NBTBase>)compound.c();
}
}

View File

@ -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 <K, V> Map<V, K> flippedMap(Map<K, V> map)
{
Map<V, K> ret = new LinkedHashMap<V, K>();
for(Entry<K, V> entry : map.entrySet())
{
V value = entry.getValue();
K key = entry.getKey();
if (value == null) continue;
ret.put(value, key);
}
return ret;
}
// -------------------------------------------- //
// LE NICE RANDOM
// -------------------------------------------- //