X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fserial%2FSerialUtils.java;h=be18769ee06967cd5c5a78439fcfe2afccb27d50;hb=refs%2Ftags%2Fnikiroo-utils-2.0.0;hp=3770c588ce53bc25e77b623ee715da6aa58b937e;hpb=aad145868879e293dbe391c005c1a6885341529b;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/serial/SerialUtils.java b/src/be/nikiroo/utils/serial/SerialUtils.java index 3770c58..be18769 100644 --- a/src/be/nikiroo/utils/serial/SerialUtils.java +++ b/src/be/nikiroo/utils/serial/SerialUtils.java @@ -1,15 +1,20 @@ package be.nikiroo.utils.serial; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; +import java.awt.image.BufferedImage; +import java.io.IOException; import java.io.NotSerializableException; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; +import java.util.UnknownFormatConversionException; + +import be.nikiroo.utils.ImageUtils; /** - * Small class to help serialise/deserialise objects. + * Small class to help with serialisation. *

* Note that we do not support inner classes (but we do support nested classes) * and all objects require an empty constructor to be deserialised. @@ -21,9 +26,90 @@ public class SerialUtils { static { customTypes = new HashMap(); - // TODO: add "default" custom serialisers + + // Array types: + customTypes.put("[]", new CustomSerializer() { + @Override + protected String toString(Object value) { + String type = value.getClass().getCanonicalName(); + type = type.substring(0, type.length() - 2); // remove the [] + + StringBuilder builder = new StringBuilder(); + builder.append(type).append("\n"); + try { + for (int i = 0; true; i++) { + Object item = Array.get(value, i); + // encode it normally if direct value + if (!SerialUtils.encode(builder, item)) { + try { + // use ZIP: if not + builder.append(new Exporter().append(item) + .toString(true)); + } catch (NotSerializableException e) { + throw new UnknownFormatConversionException(e + .getMessage()); + } + } + builder.append("\n"); + } + } catch (ArrayIndexOutOfBoundsException e) { + // Done. + } + + return builder.toString(); + } + + @Override + protected String getType() { + return "[]"; + } + + @Override + protected Object fromString(String content) { + String[] tab = content.split("\n"); + + try { + Object array = Array.newInstance( + SerialUtils.getClass(tab[0]), tab.length - 1); + for (int i = 1; i < tab.length; i++) { + Object value = new Importer().read(tab[i]).getValue(); + Array.set(array, i - 1, value); + } + + return array; + } catch (Exception e) { + throw new UnknownFormatConversionException(e.getMessage()); + } + } + }); + + // Images (this is currently the only supported image type by default) + customTypes.put("java.awt.image.BufferedImage", new CustomSerializer() { + @Override + protected String toString(Object value) { + try { + return ImageUtils.toBase64((BufferedImage) value); + } catch (IOException e) { + throw new UnknownFormatConversionException(e.getMessage()); + } + } + + @Override + protected String getType() { + return "java.awt.image.BufferedImage"; + } + + @Override + protected Object fromString(String content) { + try { + return ImageUtils.fromBase64(content); + } catch (IOException e) { + throw new UnknownFormatConversionException(e.getMessage()); + } + } + }); } - + /** * Create an empty object of the given type. * @@ -32,18 +118,16 @@ public class SerialUtils { * * @return the new object * - * @throws NoSuchMethodException if the given class is not compatible with this code - * @throws ClassNotFoundException if the class cannot be found or created + * @throws ClassNotFoundException + * if the class cannot be found + * @throws NoSuchMethodException + * if the given class is not compatible with this code */ - public static Object createObject(String type) throws NoSuchMethodException, - ClassNotFoundException { + public static Object createObject(String type) + throws ClassNotFoundException, NoSuchMethodException { try { Class clazz = getClass(type); - if (clazz == null) { - throw new ClassNotFoundException("Class not found: " + type); - } - String className = clazz.getName(); Object[] args = null; Constructor ctor = null; @@ -60,25 +144,47 @@ public class SerialUtils { ctor.setAccessible(true); return ctor.newInstance(args); + } catch (ClassNotFoundException e) { + throw e; } catch (NoSuchMethodException e) { - throw new NoSuchMethodException( - String.format( - "Objects of type \"%s\" cannot be created by this code: maybe the class" - + " or its enclosing class doesn't have an empty constructor?", - type)); - + throw e; + } catch (Exception e) { + throw new NoSuchMethodException("Cannot instantiate: " + type); } - catch (SecurityException e) { throw new ClassNotFoundException("Cannot instantiate: " + type, e); } - catch (InstantiationException e) { throw new ClassNotFoundException("Cannot instantiate: " + type, e); } - catch (IllegalAccessException e) { throw new ClassNotFoundException("Cannot instantiate: " + type, e); } - catch (IllegalArgumentException e) { throw new ClassNotFoundException("Cannot instantiate: " + type, e); } - catch (InvocationTargetException e) { throw new ClassNotFoundException("Cannot instantiate: " + type, e); } } + /** + * Insert a custom serialiser that will take precedence over the default one + * or the target class. + * + * @param serializer + * the custom serialiser + */ static public void addCustomSerializer(CustomSerializer serializer) { customTypes.put(serializer.getType(), serializer); } + /** + * Serialise the given object into this {@link StringBuilder}. + *

+ * Important: If the operation fails (with a + * {@link NotSerializableException}), the {@link StringBuilder} will be + * corrupted (will contain bad, most probably not importable data). + * + * @param builder + * the output {@link StringBuilder} to serialise to + * @param o + * the object to serialise + * @param map + * the map of already serialised objects (if the given object or + * one of its descendant is already present in it, only an ID + * will be serialised) + * + * @throws NotSerializableException + * if the object cannot be serialised (in this case, the + * {@link StringBuilder} can contain bad, most probably not + * importable data) + */ static void append(StringBuilder builder, Object o, Map map) throws NotSerializableException { @@ -104,32 +210,41 @@ public class SerialUtils { } } - builder.append("{\nREF ").append(type).append("@").append(id); - try { - for (Field field : fields) { - field.setAccessible(true); + builder.append("{\nREF ").append(type).append("@").append(id) + .append(":"); + if (!encode(builder, o)) { // check if direct value + try { + for (Field field : fields) { + field.setAccessible(true); - if (field.getName().startsWith("this$")) { - // Do not keep this links of nested classes - continue; - } + if (field.getName().startsWith("this$") + || field.isSynthetic() + || (field.getModifiers() & Modifier.STATIC) == Modifier.STATIC) { + // Do not keep this links of nested classes + // Do not keep synthetic fields + // Do not keep final fields + continue; + } - builder.append("\n"); - builder.append(field.getName()); - builder.append(":"); - Object value; + builder.append("\n"); + builder.append(field.getName()); + builder.append(":"); + Object value; - value = field.get(o); + value = field.get(o); - if (!encode(builder, value)) { - builder.append("\n"); - append(builder, value, map); + if (!encode(builder, value)) { + builder.append("\n"); + append(builder, value, map); + } } + } catch (IllegalArgumentException e) { + e.printStackTrace(); // should not happen (see + // setAccessible) + } catch (IllegalAccessException e) { + e.printStackTrace(); // should not happen (see + // setAccessible) } - } catch (IllegalArgumentException e) { - e.printStackTrace(); // should not happen (see setAccessible) - } catch (IllegalAccessException e) { - e.printStackTrace(); // should not happen (see setAccessible) } builder.append("\n}"); } @@ -138,6 +253,8 @@ public class SerialUtils { static boolean encode(StringBuilder builder, Object value) { if (value == null) { builder.append("NULL"); + } else if (value.getClass().getCanonicalName().endsWith("[]")) { + customTypes.get("[]").encode(builder, value); } else if (customTypes.containsKey(value.getClass().getCanonicalName())) { customTypes.get(value.getClass().getCanonicalName())// .encode(builder, value); @@ -148,7 +265,7 @@ public class SerialUtils { } else if (value instanceof Byte) { builder.append(value).append('b'); } else if (value instanceof Character) { - encodeString(builder, (String) value); + encodeString(builder, "" + value); builder.append('c'); } else if (value instanceof Short) { builder.append(value).append('s'); @@ -160,6 +277,10 @@ public class SerialUtils { builder.append(value).append('F'); } else if (value instanceof Double) { builder.append(value).append('d'); + } else if (value instanceof Enum) { + String type = value.getClass().getCanonicalName(); + builder.append(type).append(".").append(((Enum) value).name()) + .append(";"); } else { return false; } @@ -179,7 +300,7 @@ public class SerialUtils { if (customTypes.containsKey(type)) { return customTypes.get(type).decode(encodedValue); } else { - throw new java.util.UnknownFormatConversionException( + throw new UnknownFormatConversionException( "Unknown custom type: " + type); } } else if (encodedValue.equals("NULL") || encodedValue.equals("null")) { @@ -202,13 +323,30 @@ public class SerialUtils { return Float.parseFloat(cut); } else if (encodedValue.endsWith("d")) { return Double.parseDouble(cut); + } else if (encodedValue.endsWith(";")) { + return decodeEnum(encodedValue); } else { return Integer.parseInt(encodedValue); } } - - static private Class getClass(String type) throws ClassNotFoundException, - NoSuchMethodException { + + /** + * Return the corresponding class or throw an {@link Exception} if it + * cannot. + * + * @param type + * the class name to look for + * + * @return the class (will never be NULL) + * + * @throws ClassNotFoundException + * if the class cannot be found + * @throws NoSuchMethodException + * if the class cannot be created (usually because it or its + * enclosing class doesn't have an empty constructor) + */ + static private Class getClass(String type) + throws ClassNotFoundException, NoSuchMethodException { Class clazz = null; try { clazz = Class.forName(type); @@ -235,9 +373,29 @@ public class SerialUtils { } } + if (clazz == null) { + throw new ClassNotFoundException("Class not found: " + type); + } + return clazz; } - + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Enum decodeEnum(String escaped) { + // escaped: be.xxx.EnumType.VALUE; + int pos = escaped.lastIndexOf("."); + String type = escaped.substring(0, pos); + String name = escaped.substring(pos + 1, escaped.length() - 1); + + try { + return Enum.valueOf((Class) getClass(type), name); + } catch (Exception e) { + e.printStackTrace(); + throw new UnknownFormatConversionException("Unknown enum: <" + type + + "> " + name); + } + } + // aa bb -> "aa\tbb" private static void encodeString(StringBuilder builder, String raw) { builder.append('\"');