001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.meta.nbt;
019
020import java.lang.reflect.Field;
021import java.lang.reflect.ParameterizedType;
022import java.util.ArrayList;
023import java.util.List;
024
025import net.tridentsdk.reflect.FastClass;
026import net.tridentsdk.reflect.FastField;
027import net.tridentsdk.util.TridentLogger;
028
029public final class NBTSerializer {
030    public static <T> T deserialize(Class<T> clzz, CompoundTag tag) {
031        if (!NBTSerializable.class.isAssignableFrom(clzz)) {
032            TridentLogger.get().error(new IllegalArgumentException("Provided object is not serializable!"));
033        }
034
035        FastClass cls = FastClass.get(clzz);
036        T instance = cls.constructor().newInstance();
037
038        return NBTSerializer.deserialize(instance, tag);
039    }
040
041    public static <T> T deserialize(T instance, CompoundTag tag) {
042        if (!NBTSerializable.class.isAssignableFrom(instance.getClass())) {
043            TridentLogger.get().error(new IllegalArgumentException("Provided object is not serializable!"));
044            return null;
045        }
046
047        FastClass cls = FastClass.get(instance.getClass());
048
049        for (FastField field : cls.fields()) {
050            Field f = field.toField();
051
052            if (!f.isAnnotationPresent(NBTField.class)) {
053                continue;
054            }
055
056            NBTField nf = f.getAnnotation(NBTField.class);
057            String tagName = nf.name();
058            TagType type = nf.type();
059            NBTTag value;
060
061            if (!tag.containsTag(tagName)) {
062                if (nf.required()) {
063                    TridentLogger.get().error(new UnsupportedOperationException("Provided NBT does not contain tag: " + tagName));
064                    return null;
065                }
066                value = new NullTag(tagName);
067            } else {
068                value = tag.getTag(tagName);
069            }
070
071            if (value.type() != type) {
072                continue;
073            }
074
075            field.set(instance, NBTSerializer.findJavaValue(value, field, f));
076        }
077
078        // This allows NBTSerializable classes to process their newly-found data. Useful for caching stuff into fields.
079        ((NBTSerializable) instance).process();
080
081        return instance;
082    }
083
084    private static Object findJavaValue(NBTTag value, FastField field, Field f) {
085
086        NBTField nf = field.toField().getAnnotation(NBTField.class);
087
088        switch (value.type()) {
089            case BYTE:
090
091                if (field.toField().getType() == boolean.class) {
092                    return value.asType(ByteTag.class).value() == 1;
093                } else {
094                    return value.asType(ByteTag.class).value();
095                }
096
097            case BYTE_ARRAY:
098                return value.asType(ByteArrayTag.class).value();
099
100            case COMPOUND:
101                if (nf != null && nf.asClass() != null && nf.asClass() != NBTSerializable.class) {
102                    return NBTSerializer.deserialize(nf.asClass(), value.asType(CompoundTag.class));
103                } else if (NBTSerializable.class.isAssignableFrom(field.toField().getType())) {
104                    return NBTSerializer.deserialize(field.toField().getType(), value.asType(CompoundTag.class));
105                } else {
106                    return value;
107                }
108
109            case DOUBLE:
110                return value.asType(DoubleTag.class).value();
111
112            case FLOAT:
113                return value.asType(FloatTag.class).value();
114
115            case INT:
116                return value.asType(IntTag.class).value();
117
118            case INT_ARRAY:
119                return value.asType(IntArrayTag.class).value();
120
121            case LONG:
122                return value.asType(LongTag.class).value();
123
124            case SHORT:
125                return value.asType(ShortTag.class).value();
126
127            case LIST:
128                ListTag list = value.asType(ListTag.class);
129                Class<?> listType = (Class<?>) ((ParameterizedType) f.getGenericType())
130                        .getActualTypeArguments()[0];
131                boolean isPrimitive = listType.isPrimitive() || listType.equals(String.class);
132
133                if (!NBTSerializable.class.isAssignableFrom(listType) && !isPrimitive) {
134                    break;
135                }
136
137                List<Object> l = new ArrayList<>();
138
139                for (NBTTag t : list.listTags()) {
140                    if (isPrimitive) {
141                        l.add(NBTSerializer.findJavaValue(t, null, null));
142                        continue;
143                    }
144
145                    CompoundTag compound = t.asType(CompoundTag.class);
146
147                    l.add(NBTSerializer.deserialize(listType, compound));
148                }
149
150                return f.getType().cast(l);
151
152            case STRING:
153                return value.asType(StringTag.class).value();
154
155            case NULL:
156            default:
157                return null;
158        }
159
160        return null;
161    }
162
163    public static CompoundTag serialize(NBTSerializable serializable, String name) {
164        FastClass cls = FastClass.get(serializable.getClass());
165        CompoundTagBuilder<NBTBuilder> builder = NBTBuilder.newBase(name);
166
167        for (FastField field : cls.fields()) {
168            Field f = field.toField();
169
170            if (!f.isAnnotationPresent(NBTField.class)) {
171                continue;
172            }
173
174            String tagName = f.getAnnotation(NBTField.class).name();
175            TagType tagType = f.getAnnotation(NBTField.class).type();
176            Object value = field.get(serializable);
177
178            switch (tagType) {
179                case BYTE:
180                    builder.byteTag(tagName, (byte) value);
181                    break;
182
183                case BYTE_ARRAY:
184                    builder.byteArrayTag(tagName, (byte[]) value);
185                    break;
186
187                case COMPOUND:
188                    builder.compoundTag((CompoundTag) value);
189                    break;
190
191                case DOUBLE:
192                    builder.doubleTag(tagName, (double) value);
193                    break;
194
195                case FLOAT:
196                    builder.floatTag(tagName, (float) value);
197                    break;
198
199                case INT:
200                    builder.intTag(tagName, (int) value);
201                    break;
202
203                case INT_ARRAY:
204                    builder.intArrayTag(tagName, (int[]) value);
205                    break;
206
207                case LONG:
208                    builder.longTag(tagName, (long) value);
209                    break;
210
211                case SHORT:
212                    builder.shortTag(tagName, (short) value);
213                    break;
214
215                case LIST:
216                    builder.listTag((ListTag) value);
217                    break;
218
219                case STRING:
220                    builder.stringTag(tagName, (String) value);
221                    break;
222
223                case NULL:
224                    builder.nullTag(tagName);
225                    break;
226
227                default:
228                    break;
229            }
230        }
231
232        return builder.endCompoundTag().build();
233    }
234
235    public static CompoundTag serialize(NBTSerializable serializable) {
236        return NBTSerializer.serialize(serializable, serializable.getClass().getSimpleName());
237    }
238}