package com.cardshifter.api.serial; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.*; import com.cardshifter.api.CardshifterSerializationException; import com.cardshifter.api.LogInterface; import com.cardshifter.api.config.PlayerConfig; public class FieldsCollection<T> { private final List<ReflField> fields; private final LogInterface logger; private final ReflectionInterface refl; public FieldsCollection(List<ReflField> fields, LogInterface logger, ReflectionInterface refl) { this.fields = Collections.unmodifiableList(fields); this.logger = logger; this.refl = refl; } public static <T> FieldsCollection<T> gather(T object, LogInterface logger, ReflectionInterface refl) { List<ReflField> fields = new ArrayList<ReflField>(); Class<?> clazz = object.getClass(); while (clazz != null) { addFields(refl, fields, clazz); clazz = clazz.getSuperclass(); } return new FieldsCollection<T>(fields, logger, refl); } private static void addFields(ReflectionInterface refl, List<ReflField> fields, Class<?> clazz) { for (ReflField field : refl.getFields(clazz)) { if (!field.isStatic()) { fields.add(field); } } } public byte[] serialize(T message) throws CardshifterSerializationException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos); try { for (ReflField field : fields) { serialize(field, message, out); } } catch (CardshifterSerializationException e) { throw e; } catch (Exception e) { throw new CardshifterSerializationException(e); } byte[] data = baos.toByteArray(); baos = new ByteArrayOutputStream(); out = new DataOutputStream(baos); try { out.writeInt(data.length); baos.write(data); } catch (IOException e) { throw new CardshifterSerializationException(e); } return baos.toByteArray(); } private Object deserialize(Class<?> type, DataInputStream data, ReflField field) throws IOException, CardshifterSerializationException { if (type == int.class || type == Integer.class) { int value = data.readInt(); return value; } else if (type == String[].class) { int count = data.readInt(); String[] str = new String[count]; for (int i = 0; i < str.length; i++) { str[i] = (String) deserialize(String.class, data, null); } return str; } else if (type == int[].class) { int count = data.readInt(); int[] array = new int[count]; for (int i = 0; i < array.length; i++) { array[i] = (Integer) deserialize(Integer.class, data, null); } return array; } else if (type == Boolean.class) { byte boolValue = data.readByte(); Boolean bool = null; if (boolValue != 2) { bool = (boolValue == 1); } return bool; } else if (type == boolean.class) { byte boolValue = data.readByte(); return boolValue == 1; } else if (type == String.class) { int length = data.readInt(); StringBuilder str = new StringBuilder(length); for (int i = 0; i < length; i++) { str.append(data.readChar()); } return str.toString(); } else if (refl.isEnum(type)) { Object[] values = type.getEnumConstants(); int ordinal = data.readInt(); return values[ordinal]; } else if (type == Map.class) { if (field == null) { throw new NullPointerException("Field cannot be null when deserializing Map"); } Class<?> keyClass = field.getGenericType(0); Class<?> valueClass = field.getGenericType(1); Map<Object, Object> map = new HashMap<Object, Object>(); int size = data.readInt(); for (int i = 0; i < size; i++) { Object key = deserialize(keyClass, data, null); Object value = deserialize(valueClass, data, null); map.put(key, value); } return map; } else if (typeNameRequired(type)) { String clazzName = (String) deserialize(String.class, data, null); try { Class<?> clazz = refl.forName(clazzName); Object obj = deserialize(clazz, data, field); // logger.debug("Deserialized object: " + obj); return obj; } catch (Exception e) { throw new CardshifterSerializationException(e); } } else { logger.info("Using recursive deserialization for " + type); try { Object obj = refl.create(type); FieldsCollection<Object> fields = FieldsCollection.gather(obj, logger, refl); fields = fields.orderByName(); data.readInt(); // length of upcoming data, ignored on recursive deserialization fields.read(obj, data); return obj; } catch (Exception e) { throw new CardshifterSerializationException(e); } } } private void deserialize(ReflField field, Object message, DataInputStream data) throws Exception { // logger.debug("read field " + field + " for " + message); field.setAccessible(true); Class<?> type = field.getType(); field.set(message, deserialize(type, data, field)); } private void serialize(Class<?> type, Object value, DataOutputStream out, ReflField field) throws IOException, CardshifterSerializationException, IllegalArgumentException, IllegalAccessException { if (type == int.class || type == Integer.class) { out.writeInt((Integer) value); } else if (type == String.class) { String str = (String) value; out.writeInt(str.length()); for (int i = 0; i < str.length(); i++) { out.writeChar(str.charAt(i)); } } else if (type == Boolean.class) { Boolean bool = (Boolean) value; int boolValue = bool == null ? 2 : bool ? 1 : 0; out.writeByte(boolValue); } else if (type == boolean.class) { boolean bool = (Boolean) value; int boolValue = bool ? 1 : 0; out.writeByte(boolValue); } else if (type == String[].class) { String[] arr = (String[]) value; out.writeInt(arr.length); for (int i = 0; i < arr.length; i++) { serialize(String.class, arr[i], out, null); } } else if (type == int[].class) { int[] array = (int[]) value; out.writeInt(array.length); for (int i = 0; i < array.length; i++) { serialize(int.class, array[i], out, null); } } else if (refl.isEnum(type)) { Enum<?> enumValue = (Enum<?>) value; out.writeInt(enumValue.ordinal()); } else if (type == Map.class) { if (field == null) { throw new NullPointerException("Field cannot be null when serializing Map"); } Class<?> keyClass = field.getGenericType(0); Class<?> valueClass = field.getGenericType(1); Map<Object, Object> map = (Map<Object, Object>) value; out.writeInt(map.size()); for (Map.Entry<Object, Object> ee : map.entrySet()) { serialize(keyClass, ee.getKey(), out, null); serialize(valueClass, ee.getValue(), out, null); } } else if (typeNameRequired(type)) { String clazzName = (String) value.getClass().getName(); serialize(String.class, clazzName, out, null); serialize(value.getClass(), value, out, null); } else { logger.info("Using recursive serialization for " + type); FieldsCollection<Object> fields = FieldsCollection.gather(value, logger, refl); fields = fields.orderByName(); byte[] b = fields.serialize(value); out.write(b); } } private boolean typeNameRequired(Class<?> type) { // .isInterface method and other useful methods are not officially supported by LibGDX return type == Object.class || type == PlayerConfig.class; } private void serialize(ReflField field, T obj, DataOutputStream out) throws Exception { field.setAccessible(true); Class<?> type = field.getType(); Object value = field.get(obj); serialize(type, value, out, field); } public FieldsCollection<T> orderByName() { List<ReflField> myFields = new ArrayList<ReflField>(fields); Collections.sort(myFields, new Comparator<ReflField>() { @Override public int compare(ReflField o1, ReflField o2) { return o1.getName().compareTo(o2.getName()); } }); return new FieldsCollection<T>(myFields, logger, refl); } public FieldsCollection<T> putFirst(String fieldName) { List<ReflField> myFields = new ArrayList<ReflField>(fields); for (ReflField field : myFields) { if (field.getName().equals(fieldName)) { myFields.remove(field); myFields.add(0, field); return new FieldsCollection<T>(myFields, logger, refl); } } throw new IllegalArgumentException("Field name not found: " + fieldName); } public void read(Object message, DataInputStream data) throws CardshifterSerializationException { try { for (ReflField field : fields) { deserialize(field, message, data); } } catch (Exception ex) { throw new CardshifterSerializationException(ex); } } public FieldsCollection<T> skipFirst() { List<ReflField> myFields = new ArrayList<ReflField>(fields); myFields.remove(0); return new FieldsCollection<T>(myFields, logger, refl); } }