X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fserial%2FImporter.java;h=6718fb8ecf623b8bd66350be1d1f51685836b2cf;hb=d251f3dd38a8f9d369a7cf627185eacc4f66ece5;hp=b3307fdc7220c4fa757b04e9038e720106ddc8a4;hpb=db31c35860081535d6e7ddc83ab4af573bb0522e;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/serial/Importer.java b/src/be/nikiroo/utils/serial/Importer.java index b3307fd..6718fb8 100644 --- a/src/be/nikiroo/utils/serial/Importer.java +++ b/src/be/nikiroo/utils/serial/Importer.java @@ -1,14 +1,17 @@ package be.nikiroo.utils.serial; -import java.lang.reflect.Constructor; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; -import java.util.Scanner; +import java.util.zip.GZIPInputStream; import be.nikiroo.utils.IOUtils; -import be.nikiroo.utils.StringUtils; +import be.nikiroo.utils.streams.Base64InputStream; +import be.nikiroo.utils.streams.BufferedInputStream; +import be.nikiroo.utils.streams.NextableInputStream; +import be.nikiroo.utils.streams.NextableInputStreamStep; /** * A simple class that can accept the output of {@link Exporter} to recreate @@ -28,6 +31,9 @@ public class Importer { private String currentFieldName; + /** + * Create a new {@link Importer}. + */ public Importer() { map = new HashMap(); map.put("NULL", null); @@ -37,38 +43,107 @@ public class Importer { this.map = map; } - public Importer readLine(String line) { - try { - processLine(line); - } catch (Exception e) { - throw new IllegalArgumentException(e); - } - return this; - } + /** + * Read some data into this {@link Importer}: it can be the full serialised + * content, or a number of lines of it (any given line MUST be + * complete though) and accumulate it with the already present data. + * + * @param in + * the data to parse + * + * @return itself so it can be chained + * + * @throws NoSuchFieldException + * if the serialised data contains information about a field + * which does actually not exist in the class we know of + * @throws NoSuchMethodException + * if a class described in the serialised data cannot be created + * because it is not compatible with this code + * @throws ClassNotFoundException + * if a class described in the serialised data cannot be found + * @throws IOException + * if the content cannot be read (for instance, corrupt data) + * @throws NullPointerException + * if the stream is empty + */ + public Importer read(InputStream in) throws NoSuchFieldException, + NoSuchMethodException, ClassNotFoundException, IOException, + NullPointerException { + + NextableInputStream stream = new NextableInputStream(in, + new NextableInputStreamStep('\n')); - public Importer read(String data) { try { - if (data.startsWith("ZIP:")) { - data = StringUtils.unzip64(data.substring("ZIP:".length())); + if (in == null) { + throw new NullPointerException("InputStream is null"); } - Scanner scan = new Scanner(data); - scan.useDelimiter("\n"); - while (scan.hasNext()) { - processLine(scan.next()); + + boolean first = true; + while (stream.next()) { + if (stream.eof()) { + if (first) { + throw new NullPointerException( + "InputStream empty, normal termination"); + } + return this; + } + first = false; + + boolean zip = stream.startsWith("ZIP:"); + boolean b64 = stream.startsWith("B64:"); + + if (zip || b64) { + stream.skip("XXX:".length()); + + InputStream decoded = stream.open(); + if (zip) { + decoded = new GZIPInputStream(decoded); + } + decoded = new Base64InputStream(decoded, false); + + try { + read(decoded); + } finally { + decoded.close(); + } + } else { + processLine(stream); + } } - scan.close(); - } catch (Exception e) { - throw new IllegalArgumentException(e); + } finally { + stream.close(false); } + return this; } - public boolean processLine(String line) throws IllegalArgumentException, - NoSuchFieldException, SecurityException, IllegalAccessException, - NoSuchMethodException, InstantiationException, ClassNotFoundException, InvocationTargetException { + /** + * Read a single (whole) line of serialised data into this {@link Importer} + * and accumulate it with the already present data. + * + * @param in + * the line to parse + * + * @return TRUE if we are just done with one object or sub-object + * + * @throws NoSuchFieldException + * if the serialised data contains information about a field + * which does actually not exist in the class we know of + * @throws NoSuchMethodException + * if a class described in the serialised data cannot be created + * because it is not compatible with this code + * @throws ClassNotFoundException + * if a class described in the serialised data cannot be found + * @throws IOException + * if the content cannot be read (for instance, corrupt data) + */ + private boolean processLine(BufferedInputStream in) + throws NoSuchFieldException, NoSuchMethodException, + ClassNotFoundException, IOException { + // Defer to latest child if any if (child != null) { - if (child.processLine(line)) { + if (child.processLine(in)) { if (currentFieldName != null) { setField(currentFieldName, child.getValue()); currentFieldName = null; @@ -79,25 +154,62 @@ public class Importer { return false; } - if (line.equals("{")) { // START: new child if needed + // Start/Stop object + if (in.is("{")) { // START: new child if needed if (link != null) { child = new Importer(map); } - } else if (line.equals("}")) { // STOP: report self to parent + in.end(); + return false; + } else if (in.is("}")) { // STOP: report self to parent + in.end(); return true; - } else if (line.startsWith("REF ")) { // REF: create/link self - String ref = line.substring(4).split("@")[1]; + } + + // Custom objects + if (CustomSerializer.isCustom(in)) { + // not a field value but a direct value + String line = IOUtils.readSmallStream(in); + me = SerialUtils.decode(line); + return false; + } + + // TODO use the stream, Luke + // .. at least for REF + String line = IOUtils.readSmallStream(in); + + if (line.startsWith("REF ")) { // REF: create/link self + // TODO: here, line is REF type@999:xxx + // xxx is optional + // note: use .end() when containsKey(ref) + String[] tab = line.substring("REF ".length()).split("@"); + String type = tab[0]; + tab = tab[1].split(":"); + String ref = tab[0]; + link = map.containsKey(ref); if (link) { me = map.get(ref); } else { - me = createSelf(line.substring(4).split("@")[0]); + if (line.endsWith(":")) { + // construct + me = SerialUtils.createObject(type); + } else { + // direct value + int pos = line.indexOf(":"); + String encodedValue = line.substring(pos + 1); + me = SerialUtils.decode(encodedValue); + } map.put(ref, me); } - } else { // FIELD: new field + } else { // FIELD: new field *or* direct simple value if (line.endsWith(":")) { // field value is compound currentFieldName = line.substring(0, line.length() - 1); + } else if (line.startsWith(":") || !line.contains(":") + || line.startsWith("\"")) { + // not a field value but a direct value + me = SerialUtils.decode(line); } else { // field value is direct int pos = line.indexOf(":"); @@ -118,92 +230,8 @@ public class Importer { return false; } - /** - * Create an empty object of the given type. - * - * @param type - * the object type - * @return the object - * - * @throws NoSuchMethodException - * @throws SecurityException - * @throws InstantiationException - * @throws IllegalAccessException - * @throws ClassNotFoundException - * @throws IllegalArgumentException - * @throws InvocationTargetException - */ - private Object createSelf(String type) throws NoSuchMethodException, - SecurityException, InstantiationException, IllegalAccessException, - ClassNotFoundException, IllegalArgumentException, - InvocationTargetException { - - 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; - if (className.contains("$")) { - Object javaParent = createSelf(className.substring(0, - className.lastIndexOf('$'))); - args = new Object[] { javaParent }; - ctor = clazz.getDeclaredConstructor(new Class[] { javaParent - .getClass() }); - } else { - args = new Object[] {}; - ctor = clazz.getDeclaredConstructor(); - } - - ctor.setAccessible(true); - return ctor.newInstance(args); - } 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)); - - } - } - - private Class getClass(String type) throws ClassNotFoundException, - NoSuchMethodException { - Class clazz = null; - try { - clazz = Class.forName(type); - } catch (ClassNotFoundException e) { - int pos = type.length(); - pos = type.lastIndexOf(".", pos); - if (pos >= 0) { - String parentType = type.substring(0, pos); - String nestedType = type.substring(pos + 1); - Class javaParent = null; - try { - javaParent = getClass(parentType); - parentType = javaParent.getName(); - clazz = Class.forName(parentType + "$" + nestedType); - } catch (Exception ee) { - } - - if (javaParent == null) { - throw new NoSuchMethodException( - "Class not found: " - + type - + " (the enclosing class cannot be created: maybe it doesn't have an empty constructor?)"); - } - } - } - - return clazz; - } - private void setField(String name, Object value) - throws NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException { + throws NoSuchFieldException { try { Field field = me.getClass().getDeclaredField(name); @@ -214,9 +242,18 @@ public class Importer { throw new NoSuchFieldException(String.format( "Field \"%s\" was not found in object of type \"%s\".", name, me.getClass().getCanonicalName())); + } catch (Exception e) { + throw new NoSuchFieldException(String.format( + "Internal error when setting \"%s.%s\": %s", me.getClass() + .getCanonicalName(), name, e.getMessage())); } } + /** + * Return the current deserialised value. + * + * @return the current value + */ public Object getValue() { return me; }