/**
* Copyright (c) Lambda Innovation, 2013-2015
* 本作品版权由Lambda Innovation所有。
* http://www.li-dev.cn/
*
* This project is open-source, and it is distributed under
* the terms of GNU General Public License. You can modify
* and distribute freely as long as you follow the license.
* 本项目是一个开源项目,且遵循GNU通用公共授权协议。
* 在遵照该协议的情况下,您可以自由传播和修改。
* http://www.gnu.org/licenses/gpl.html
*/
package cn.annoreg.mc.s11n;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagByte;
import net.minecraft.nbt.NBTTagByteArray;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagDouble;
import net.minecraft.nbt.NBTTagFloat;
import net.minecraft.nbt.NBTTagInt;
import net.minecraft.nbt.NBTTagIntArray;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagLong;
import net.minecraft.nbt.NBTTagShort;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import org.apache.commons.lang3.ArrayUtils;
import cn.annoreg.ARModContainer;
import cn.annoreg.mc.SideHelper;
import cn.annoreg.mc.network.Future;
import cn.annoreg.mc.network.NetworkTerminal;
public class SerializationManager {
public static final SerializationManager INSTANCE = new SerializationManager();
static {
INSTANCE.initInternalSerializers();
}
private Map<Class, DataSerializer> dataSerializers = new HashMap();
private Map<Class, InstanceSerializer> instanceSerializers = new HashMap();
private Map<String, InstanceSerializer> instanceSerializersFromId = new HashMap();
// should only return NBTTagCompound
public NBTBase serialize(Object obj, StorageOption.Option option) {
if(obj == null) { //Allow to pass over null instances.
NBTTagCompound tag = new NBTTagCompound();
tag.setBoolean("__isNull__", true);
return tag;
}
Class clazz = obj.getClass();
DataSerializer d = getDataSerializer(clazz);
InstanceSerializer i = getInstanceSerializer(clazz);
NBTTagCompound ret = new NBTTagCompound();
ret.setString("class", clazz.getName());
ret.setInteger("option", option.ordinal());
switch (option) {
case NULL:
return ret;
case DATA:
try {
if (d == null) {
if (obj instanceof Collection) {
Collection coll = (Collection) obj;
NBTTagList list = new NBTTagList();
for (Object element : coll) {
list.appendTag(serialize(element, StorageOption.Option.DATA));
}
ret.setBoolean("__isCollection__", true);
ret.setTag("collection", list);
return ret;
} else {
throw new RuntimeException("Serializer not found.");
}
}
ret.setTag("data", d.writeData(obj));
return ret;
} catch (Exception e) {
ARModContainer.log.error("Failed in data serialization. Class: {}.", clazz.getCanonicalName());
e.printStackTrace();
return null;
}
case INSTANCE:
case NULLABLE_INSTANCE:
try {
if (i == null) {
if (obj instanceof Collection) {
Collection coll = (Collection) obj;
NBTTagList list = new NBTTagList();
for (Object element : coll) {
list.appendTag(serialize(element, StorageOption.Option.INSTANCE));
}
ret.setBoolean("__isCollection__", true);
ret.setTag("collection", list);
return ret;
} else {
throw new RuntimeException("Serializer not found.");
}
}
ret.setTag("instance", i.writeInstance(obj));
//we need to remember the class of i (also the id of i), not of obj.
ret.setString("class", i.getClass().getName());
return ret;
} catch (Exception e) {
ARModContainer.log.error("Failed in instance serialization. Class: {}, Object: {}", clazz.getCanonicalName(), obj.toString());
e.printStackTrace();
return null;
}
case UPDATE:
try {
if (i == null || d == null) {
throw new RuntimeException("Serializer not found.");
}
ret.setTag("instance", i.writeInstance(obj));
ret.setTag("data", d.writeData(obj));
return ret;
} catch (Exception e) {
ARModContainer.log.error("Failed in update serialization. Class: {}.", clazz.getCanonicalName());
e.printStackTrace();
return null;
}
default:
ARModContainer.log.error("Failed in serialization. Class: {}. Unknown option.",
clazz.getCanonicalName());
Thread.dumpStack();
return null;
}
}
//use null in obj if the instance is unknown.
public Object deserialize(Object obj, NBTBase nbt, StorageOption.Option option) {
NBTTagCompound tag = (NBTTagCompound) nbt;
Class<?> clazz = null;
if(tag.getBoolean("__isNull__")) {
return null;
}
if (tag.getBoolean("__isCollection__")) {
//create the container
try {
clazz = Class.forName(tag.getString("class"));
Collection coll = (Collection) clazz.newInstance();
NBTTagList nbtcoll = (NBTTagList) tag.getTag("collection");
for (int i = 0; i < nbtcoll.tagCount(); ++i) {
Object o = deserialize(null, nbtcoll.getCompoundTagAt(i), option);
if(o != null) coll.add(o);
}
return coll;
} catch (Exception e) {
ARModContainer.log.error("Failed in deserialization. Class: {}.", tag.getString("class"));
e.printStackTrace();
return null;
}
}
if (option == StorageOption.Option.AUTO) {
option = StorageOption.Option.values()[tag.getInteger("option")];
}
if (tag.getInteger("option") != option.ordinal()) {
ARModContainer.log.error("Failed in deserialization. Class: {}.", tag.getString("class"));
Thread.dumpStack();
}
NBTBase data = tag.getTag("data");
NBTBase ins = tag.getTag("instance");
switch (option) {
case NULL:
return null;
case DATA:
{
try {
clazz = Class.forName(tag.getString("class"));
} catch (ClassNotFoundException e) {
ARModContainer.log.error("Failed in deserialization. Class: {}.", tag.getString("class"));
e.printStackTrace();
return null;
}
DataSerializer ser = getDataSerializer(clazz);
try {
return ser.readData(data, obj);
} catch (Exception e) {
ARModContainer.log.error("Failed in data deserialization. Class: {}.",
tag.getString("class"));
e.printStackTrace();
return null;
}
}
case INSTANCE:
{
InstanceSerializer ser = instanceSerializersFromId.get(tag.getString("class"));
try {
if (ser == null)
throw new RuntimeException("Can not find instance serializer with id " + tag.getString("class"));
Object ret = ser.readInstance(ins);
if (ret == null) {
throw new NullInstanceException();
}
return ret;
} catch (NullInstanceException e) {
throw e;
} catch (Exception e) {
ARModContainer.log.error("Failed in instance deserialization. Class: {}.",
tag.getString("class"));
e.printStackTrace();
throw new RuntimeException(e);
}
}
case NULLABLE_INSTANCE:
{
InstanceSerializer ser = instanceSerializersFromId.get(tag.getString("class"));
try {
if (ser == null)
throw new RuntimeException("Can not find instance serializer with id " + tag.getString("class"));
return ser.readInstance(ins);
} catch (Exception e) {
ARModContainer.log.error("Failed in instance deserialization. Class: {}.",
tag.getString("class"));
e.printStackTrace();
throw new RuntimeException(e);
}
}
case UPDATE:
{
InstanceSerializer i = instanceSerializersFromId.get(tag.getString("class"));
try {
Object objIns = i.readInstance(ins);
if (objIns == null) {
throw new Exception("Instance is null.");
}
DataSerializer d = getDataSerializer(objIns.getClass());
d.readData(data, objIns);
return objIns;
} catch (Exception e) {
ARModContainer.log.error("Failed in update deserialization. Class: {}.",
tag.getString("class"));
e.printStackTrace();
return null;
}
}
default:
ARModContainer.log.error("Failed in deserialization. Class: {}. Unknown option.",
tag.getString("class"));
Thread.dumpStack();
return null;
}
}
public <T> InstanceSerializer<T> getInstanceSerializer(Class<T> clazz) {
//We need to search for super classes
Class c = clazz;
while (c != null) {
InstanceSerializer<T> ret = instanceSerializers.get(c);
if (ret != null) {
return ret;
}
c = c.getSuperclass();
}
return null;
}
public <T> DataSerializer<T> getDataSerializer(Class<T> clazz) {
DataSerializer<T> ser = dataSerializers.get(clazz);
if (ser == null && clazz.isAnnotationPresent(RegSerializable.class)) {
ser = createAutoSerializerFor(clazz);
}
return ser;
}
public boolean hasDataSerializer(Class clazz) {
return dataSerializers.containsKey(clazz);
}
private Set<Class> autoSerializerCreating = new HashSet();
DataSerializer createAutoSerializerFor(Class<?> clazz) {
if (autoSerializerCreating.contains(clazz)) {
throw new RuntimeException("Circular dependencies in auto serializer.");
}
autoSerializerCreating.add(clazz);
DataSerializer ret = new ReflectionAutoSerializer(clazz);
autoSerializerCreating.remove(clazz);
return ret;
}
void setDataSerializerFor(Class<?> clazz, DataSerializer serializer) {
dataSerializers.put(clazz, serializer);
}
void setInstanceSerializerFor(Class<?> clazz, InstanceSerializer serializer) {
instanceSerializers.put(clazz, serializer);
instanceSerializersFromId.put(serializer.getClass().getName(), serializer);
}
private void initInternalSerializers() {
//First part: java internal class.
{
InstanceSerializer ser = new InstanceSerializer<Enum>() {
@Override
public Enum readInstance(NBTBase nbt) throws Exception {
NBTTagCompound tag = (NBTTagCompound) nbt;
try {
Class enumClass = Class.forName(tag.getString("class"));
Object[] objs = (Object[]) enumClass.getMethod("values").invoke(null);
return (Enum) objs[tag.getInteger("ordinal")];
} catch(Exception e) {
ARModContainer.log.error("Failed in enum deserialization. Class: {}.",
tag.getString("class"));
e.printStackTrace();
return null;
}
}
@Override
public NBTBase writeInstance(Enum obj) throws Exception {
NBTTagCompound ret = new NBTTagCompound();
ret.setString("class", obj.getClass().getName());
ret.setByte("ordinal", (byte) ((Enum)obj).ordinal());
return ret;
}
};
setInstanceSerializerFor(Enum.class, ser);
}
{
DataSerializer ser = new DataSerializer<Byte>() {
@Override
public Byte readData(NBTBase nbt, Byte obj) throws Exception {
return ((NBTTagByte) nbt).func_150290_f();
}
@Override
public NBTBase writeData(Byte obj) throws Exception {
return new NBTTagByte(obj);
}
};
setDataSerializerFor(Byte.TYPE, ser);
setDataSerializerFor(Byte.class, ser);
}
{
DataSerializer ser = new DataSerializer<Byte[]>() {
@Override
public Byte[] readData(NBTBase nbt, Byte[] obj) throws Exception {
return ArrayUtils.toObject(((NBTTagByteArray) nbt).func_150292_c());
}
@Override
public NBTBase writeData(Byte[] obj) throws Exception {
return new NBTTagByteArray(ArrayUtils.toPrimitive(obj));
}
};
setDataSerializerFor(Byte[].class, ser);
}
{
DataSerializer ser = new DataSerializer<byte[]>() {
@Override
public byte[] readData(NBTBase nbt, byte[] obj) throws Exception {
return ((NBTTagByteArray) nbt).func_150292_c();
}
@Override
public NBTBase writeData(byte[] obj) throws Exception {
return new NBTTagByteArray(obj);
}
};
setDataSerializerFor(byte[].class, ser);
}
{
DataSerializer ser = new DataSerializer<Double>() {
@Override
public Double readData(NBTBase nbt, Double obj) throws Exception {
return ((NBTTagDouble) nbt).func_150286_g();
}
@Override
public NBTBase writeData(Double obj) throws Exception {
return new NBTTagDouble(obj);
}
};
setDataSerializerFor(Double.TYPE, ser);
setDataSerializerFor(Double.class, ser);
}
{
DataSerializer ser = new DataSerializer<Float>() {
@Override
public Float readData(NBTBase nbt, Float obj) throws Exception {
return ((NBTTagFloat) nbt).func_150288_h();
}
@Override
public NBTBase writeData(Float obj) throws Exception {
return new NBTTagFloat(obj);
}
};
setDataSerializerFor(Float.TYPE, ser);
setDataSerializerFor(Float.class, ser);
}
{
DataSerializer ser = new DataSerializer<Integer>() {
@Override
public Integer readData(NBTBase nbt, Integer obj) throws Exception {
return ((NBTTagInt) nbt).func_150287_d();
}
@Override
public NBTBase writeData(Integer obj) throws Exception {
return new NBTTagInt(obj);
}
};
setDataSerializerFor(Integer.TYPE, ser);
setDataSerializerFor(Integer.class, ser);
}
{
DataSerializer ser = new DataSerializer<Integer[]>() {
@Override
public Integer[] readData(NBTBase nbt, Integer[] obj) throws Exception {
return ArrayUtils.toObject(((NBTTagIntArray) nbt).func_150302_c());
}
@Override
public NBTBase writeData(Integer[] obj) throws Exception {
return new NBTTagIntArray(ArrayUtils.toPrimitive(obj));
}
};
setDataSerializerFor(Integer[].class, ser);
}
{
DataSerializer ser = new DataSerializer<int[]>() {
@Override
public int[] readData(NBTBase nbt, int[] obj) throws Exception {
return ((NBTTagIntArray) nbt).func_150302_c();
}
@Override
public NBTBase writeData(int[] obj) throws Exception {
return new NBTTagIntArray(obj);
}
};
setDataSerializerFor(int[].class, ser);
}
{
DataSerializer ser = new DataSerializer<Long>() {
@Override
public Long readData(NBTBase nbt, Long obj) throws Exception {
return ((NBTTagLong) nbt).func_150291_c();
}
@Override
public NBTBase writeData(Long obj) throws Exception {
return new NBTTagLong(obj);
}
};
setDataSerializerFor(Long.TYPE, ser);
setDataSerializerFor(Long.class, ser);
}
{
DataSerializer ser = new DataSerializer<Short>() {
@Override
public Short readData(NBTBase nbt, Short obj) throws Exception {
return ((NBTTagShort) nbt).func_150289_e();
}
@Override
public NBTBase writeData(Short obj) throws Exception {
return new NBTTagShort(obj);
}
};
setDataSerializerFor(Short.TYPE, ser);
setDataSerializerFor(Short.class, ser);
}
{
DataSerializer ser = new DataSerializer<String>() {
@Override
public String readData(NBTBase nbt, String obj) throws Exception {
return ((NBTTagString) nbt).func_150285_a_();
}
@Override
public NBTBase writeData(String obj) throws Exception {
return new NBTTagString(obj);
}
};
setDataSerializerFor(String.class, ser);
}
{
//TODO: Maybe there is a more data-friendly method?
DataSerializer ser = new DataSerializer<Boolean>() {
@Override
public Boolean readData(NBTBase nbt, Boolean obj) throws Exception {
return ((NBTTagCompound)nbt).getBoolean("v");
}
@Override
public NBTBase writeData(Boolean obj) throws Exception {
NBTTagCompound tag = new NBTTagCompound();
tag.setBoolean("v", obj);
return tag;
}
};
setDataSerializerFor(Boolean.class, ser);
setDataSerializerFor(Boolean.TYPE, ser);
}
//Second part: Minecraft objects.
{
DataSerializer ser = new DataSerializer<NBTTagCompound>() {
@Override
public NBTTagCompound readData(NBTBase nbt, NBTTagCompound obj) throws Exception {
return (NBTTagCompound) nbt;
}
@Override
public NBTBase writeData(NBTTagCompound obj) throws Exception {
return obj;
}
};
setDataSerializerFor(NBTTagCompound.class, ser);
}
{
InstanceSerializer ser = new InstanceSerializer<Entity>() {
@Override
public Entity readInstance(NBTBase nbt) throws Exception {
int[] ids = ((NBTTagIntArray) nbt).func_150302_c();
World world = SideHelper.getWorld(ids[0]);
if (world != null) {
return world.getEntityByID(ids[1]);
}
return null;
}
@Override
public NBTBase writeInstance(Entity obj) throws Exception {
return new NBTTagIntArray(new int[] { obj.dimension, obj.getEntityId() });
}
};
setInstanceSerializerFor(Entity.class, ser);
}
{
InstanceSerializer ser = new InstanceSerializer<TileEntity>() {
@Override
public TileEntity readInstance(NBTBase nbt) throws Exception {
int[] ids = ((NBTTagIntArray) nbt).func_150302_c();
World world = SideHelper.getWorld(ids[0]);
if (world != null) {
return world.getTileEntity(ids[1], ids[2], ids[3]);
}
return null;
}
@Override
public NBTBase writeInstance(TileEntity obj) throws Exception {
return new NBTTagIntArray(new int[] { obj.getWorldObj().provider.dimensionId,
obj.xCoord, obj.yCoord, obj.zCoord });
}
};
setInstanceSerializerFor(TileEntity.class, ser);
}
{
//TODO this implementation can not be used to serialize player's inventory container.
InstanceSerializer ser = new InstanceSerializer<Container>() {
@Override
public Container readInstance(NBTBase nbt) throws Exception {
int[] ids = ((NBTTagIntArray) nbt).func_150302_c();
World world = SideHelper.getWorld(ids[0]);
if (world != null) {
Entity entity = world.getEntityByID(ids[1]);
if (entity instanceof EntityPlayer) {
return SideHelper.getPlayerContainer((EntityPlayer) entity, ids[2]);
}
}
return SideHelper.getPlayerContainer(null, ids[2]);
}
@Override
public NBTBase writeInstance(Container obj) throws Exception {
EntityPlayer player = SideHelper.getThePlayer();
if (player != null) {
//This is on client. The server needs player to get the Container.
return new NBTTagIntArray(new int[] { player.worldObj.provider.dimensionId,
player.getEntityId(), obj.windowId});
} else {
//This is on server. The client doesn't need player (just use thePlayer), use MAX_VALUE here.
return new NBTTagIntArray(new int[] { Integer.MAX_VALUE, 0, obj.windowId});
}
}
};
setInstanceSerializerFor(Container.class, ser);
}
{
InstanceSerializer ser = new InstanceSerializer<World>() {
@Override
public World readInstance(NBTBase nbt) throws Exception {
return SideHelper.getWorld(((NBTTagInt) nbt).func_150287_d());
}
@Override
public NBTBase writeInstance(World obj) throws Exception {
return new NBTTagInt(obj.provider.dimensionId);
}
};
setInstanceSerializerFor(World.class, ser);
}
{
DataSerializer ser = new DataSerializer<ItemStack>() {
@Override
public ItemStack readData(NBTBase nbt, ItemStack obj) throws Exception {
if (obj == null) {
return ItemStack.loadItemStackFromNBT((NBTTagCompound) nbt);
} else {
obj.readFromNBT((NBTTagCompound) nbt);
return obj;
}
}
@Override
public NBTBase writeData(ItemStack obj) throws Exception {
NBTTagCompound nbt = new NBTTagCompound();
obj.writeToNBT(nbt);
return nbt;
}
};
setDataSerializerFor(ItemStack.class, ser);
}
{
DataSerializer ser = new DataSerializer<Vec3>() {
@Override
public Vec3 readData(NBTBase nbt, Vec3 obj) throws Exception {
NBTTagCompound tag = (NBTTagCompound) nbt;
return Vec3.createVectorHelper(tag.getFloat("x"), tag.getFloat("y"), tag.getFloat("z"));
}
@Override
public NBTBase writeData(Vec3 obj) throws Exception {
NBTTagCompound nbt = new NBTTagCompound();
nbt.setFloat("x", (float) obj.xCoord);
nbt.setFloat("y", (float) obj.yCoord);
nbt.setFloat("z", (float) obj.zCoord);
return nbt;
}
};
setDataSerializerFor(Vec3.class, ser);
}
//network part
{
DataSerializer ser = new DataSerializer<NetworkTerminal>() {
@Override
public NetworkTerminal readData(NBTBase nbt, NetworkTerminal obj) throws Exception {
return NetworkTerminal.fromNBT(nbt);
}
@Override
public NBTBase writeData(NetworkTerminal obj) throws Exception {
return obj.toNBT();
}
};
setDataSerializerFor(NetworkTerminal.class, ser);
}
{
Future.FutureSerializer ser = new Future.FutureSerializer();
setDataSerializerFor(Future.class, ser);
setInstanceSerializerFor(Future.class, ser);
}
//misc
{
DataSerializer ser = new DataSerializer<BitSet>() {
@Override
public BitSet readData(NBTBase nbt, BitSet obj)
throws Exception {
NBTTagCompound tag = (NBTTagCompound) nbt;
BitSet ret = BitSet.valueOf(tag.getByteArray("l"));
return ret;
}
@Override
public NBTBase writeData(BitSet obj) throws Exception {
NBTTagCompound tag = new NBTTagCompound();
byte[] barray = obj.toByteArray();
tag.setByteArray("l", barray);
return tag;
}
};
setDataSerializerFor(BitSet.class, ser);
}
}
}