cn.annoreg.mc.s11n.SerializationManager.java Source code

Java tutorial

Introduction

Here is the source code for cn.annoreg.mc.s11n.SerializationManager.java

Source

/**
 * 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);
        }
    }
}