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}