Merge commit '77d3a60869e7a780c6ae069e51530e1eacece5e2'
[fanfix.git] / src / be / nikiroo / utils / serial / SerialUtils.java
index a6a02a8e06205cb5996b0a94c1124056ce2cbf60..ad3b5d4da2a8dae4955e12ce28f2cfd9a51fc5f7 100644 (file)
@@ -1,11 +1,9 @@
 package be.nikiroo.utils.serial;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.NotSerializableException;
 import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -20,6 +18,9 @@ import java.util.UnknownFormatConversionException;
 import be.nikiroo.utils.IOUtils;
 import be.nikiroo.utils.Image;
 import be.nikiroo.utils.StringUtils;
+import be.nikiroo.utils.streams.Base64InputStream;
+import be.nikiroo.utils.streams.Base64OutputStream;
+import be.nikiroo.utils.streams.BufferedInputStream;
 import be.nikiroo.utils.streams.NextableInputStream;
 import be.nikiroo.utils.streams.NextableInputStreamStep;
 
@@ -64,8 +65,6 @@ public class SerialUtils {
                        protected void toStream(OutputStream out, Object value)
                                        throws IOException {
 
-                               // TODO: we use \n to separate, and b64 to un-\n
-                               // -- but we could use \\n ?
                                String type = value.getClass().getCanonicalName();
                                type = type.substring(0, type.length() - 2); // remove the []
 
@@ -78,11 +77,11 @@ public class SerialUtils {
                                                write(out, "\r");
                                                if (!SerialUtils.encode(out, item)) {
                                                        try {
-                                                               // TODO: bad escaping?
                                                                write(out, "B64:");
-                                                               OutputStream bout = StringUtils.base64(out,
-                                                                               false, false);
-                                                               new Exporter(bout).append(item);
+                                                               OutputStream out64 = new Base64OutputStream(
+                                                                               out, true);
+                                                               new Exporter(out64).append(item);
+                                                               out64.flush();
                                                        } catch (NotSerializableException e) {
                                                                throw new UnknownFormatConversionException(e
                                                                                .getMessage());
@@ -140,7 +139,7 @@ public class SerialUtils {
                                        val = ((URL) value).toString();
                                }
 
-                               out.write(val.getBytes("UTF-8"));
+                               out.write(StringUtils.getBytes(val));
                        }
 
                        @Override
@@ -165,7 +164,7 @@ public class SerialUtils {
                        protected void toStream(OutputStream out, Object value)
                                        throws IOException {
                                Image img = (Image) value;
-                               OutputStream encoded = StringUtils.base64(out, false, false);
+                               OutputStream encoded = new Base64OutputStream(out, true);
                                try {
                                        InputStream in = img.newInputStream();
                                        try {
@@ -188,7 +187,7 @@ public class SerialUtils {
                        protected Object fromStream(InputStream in) throws IOException {
                                try {
                                        // Cannot close it!
-                                       InputStream decoded = StringUtils.unbase64(in, false);
+                                       InputStream decoded = new Base64InputStream(in, false);
                                        return new Image(decoded);
                                } catch (IOException e) {
                                        throw new UnknownFormatConversionException(e.getMessage());
@@ -358,7 +357,7 @@ public class SerialUtils {
                                                continue;
                                        }
 
-                                       write(out, "\n");
+                                       write(out, "\n^");
                                        write(out, field.getName());
                                        write(out, ":");
 
@@ -417,26 +416,28 @@ public class SerialUtils {
                } else if (value instanceof Boolean) {
                        write(out, value);
                } else if (value instanceof Byte) {
-                       write(out, value);
                        write(out, "b");
+                       write(out, value);
                } else if (value instanceof Character) {
-                       encodeString(out, "" + value);
                        write(out, "c");
+                       encodeString(out, "" + value);
                } else if (value instanceof Short) {
-                       write(out, value);
                        write(out, "s");
+                       write(out, value);
                } else if (value instanceof Integer) {
+                       write(out, "i");
                        write(out, value);
                } else if (value instanceof Long) {
+                       write(out, "l");
                        write(out, value);
-                       write(out, "L");
                } else if (value instanceof Float) {
+                       write(out, "f");
                        write(out, value);
-                       write(out, "F");
                } else if (value instanceof Double) {
-                       write(out, value);
                        write(out, "d");
+                       write(out, value);
                } else if (value instanceof Enum) {
+                       write(out, "E:");
                        String type = value.getClass().getCanonicalName();
                        write(out, type);
                        write(out, ".");
@@ -449,12 +450,83 @@ public class SerialUtils {
                return true;
        }
 
+       static boolean isDirectValue(BufferedInputStream encodedValue)
+                       throws IOException {
+               if (CustomSerializer.isCustom(encodedValue)) {
+                       return false;
+               }
+
+               for (String fullValue : new String[] { "NULL", "null", "true", "false" }) {
+                       if (encodedValue.is(fullValue)) {
+                               return true;
+                       }
+               }
+
+               for (String prefix : new String[] { "c\"", "\"", "b", "s", "i", "l",
+                               "f", "d", "E:" }) {
+                       if (encodedValue.startsWith(prefix)) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
        /**
         * Decode the data into an equivalent supported source object.
         * <p>
         * A supported object in this context means an object we can directly
-        * encode, like an Integer or a String. Custom objects and arrays are also
-        * considered supported, but <b>compound objects are not supported here</b>.
+        * encode, like an Integer or a String (see
+        * {@link SerialUtils#decode(String)}.
+        * <p>
+        * Custom objects and arrays are also considered supported here, but
+        * <b>compound objects are not</b>.
+        * <p>
+        * For compound objects, you should use {@link Importer}.
+        * 
+        * @param encodedValue
+        *            the encoded data, cannot be NULL
+        * 
+        * @return the object (can be NULL for NULL encoded values)
+        * 
+        * @throws IOException
+        *             if the content cannot be converted
+        */
+       static Object decode(BufferedInputStream encodedValue) throws IOException {
+               if (CustomSerializer.isCustom(encodedValue)) {
+                       // custom^TYPE^ENCODED_VALUE
+                       NextableInputStream content = new NextableInputStream(encodedValue,
+                                       new NextableInputStreamStep('^'));
+                       try {
+                               content.next();
+                               @SuppressWarnings("unused")
+                               String custom = IOUtils.readSmallStream(content);
+                               content.next();
+                               String type = IOUtils.readSmallStream(content);
+                               content.nextAll();
+                               if (customTypes.containsKey(type)) {
+                                       return customTypes.get(type).decode(content);
+                               }
+                               content.end();
+                               throw new IOException("Unknown custom type: " + type);
+                       } finally {
+                               content.close(false);
+                               encodedValue.end();
+                       }
+               }
+
+               String encodedString = IOUtils.readSmallStream(encodedValue);
+               return decode(encodedString);
+       }
+
+       /**
+        * Decode the data into an equivalent supported source object.
+        * <p>
+        * A supported object in this context means an object we can directly
+        * encode, like an Integer or a String.
+        * <p>
+        * For custom objects and arrays, you should use
+        * {@link SerialUtils#decode(InputStream)} or directly {@link Importer}.
         * <p>
         * For compound objects, you should use {@link Importer}.
         * 
@@ -470,48 +542,36 @@ public class SerialUtils {
                try {
                        String cut = "";
                        if (encodedValue.length() > 1) {
-                               cut = encodedValue.substring(0, encodedValue.length() - 1);
+                               cut = encodedValue.substring(1);
                        }
 
-                       if (CustomSerializer.isCustom(encodedValue)) {
-                               // custom:TYPE_NAME:"content is String-encoded"
-                               String type = CustomSerializer.typeOf(encodedValue);
-                               if (customTypes.containsKey(type)) {
-                                       // TODO: we should start with a stream
-                                       InputStream streamEncodedValue = new ByteArrayInputStream(
-                                                       encodedValue.getBytes("UTF-8"));
-                                       try {
-                                               return customTypes.get(type).decode(streamEncodedValue);
-                                       } finally {
-                                               streamEncodedValue.close();
-                                       }
-                               }
-                               throw new IOException("Unknown custom type: " + type);
-                       } else if (encodedValue.equals("NULL")
-                                       || encodedValue.equals("null")) {
+                       if (encodedValue.equals("NULL") || encodedValue.equals("null")) {
                                return null;
-                       } else if (encodedValue.endsWith("\"")) {
+                       } else if (encodedValue.startsWith("\"")) {
                                return decodeString(encodedValue);
                        } else if (encodedValue.equals("true")) {
                                return true;
                        } else if (encodedValue.equals("false")) {
                                return false;
-                       } else if (encodedValue.endsWith("b")) {
+                       } else if (encodedValue.startsWith("b")) {
                                return Byte.parseByte(cut);
-                       } else if (encodedValue.endsWith("c")) {
+                       } else if (encodedValue.startsWith("c")) {
                                return decodeString(cut).charAt(0);
-                       } else if (encodedValue.endsWith("s")) {
+                       } else if (encodedValue.startsWith("s")) {
                                return Short.parseShort(cut);
-                       } else if (encodedValue.endsWith("L")) {
+                       } else if (encodedValue.startsWith("l")) {
                                return Long.parseLong(cut);
-                       } else if (encodedValue.endsWith("F")) {
+                       } else if (encodedValue.startsWith("f")) {
                                return Float.parseFloat(cut);
-                       } else if (encodedValue.endsWith("d")) {
+                       } else if (encodedValue.startsWith("d")) {
                                return Double.parseDouble(cut);
-                       } else if (encodedValue.endsWith(";")) {
-                               return decodeEnum(encodedValue);
+                       } else if (encodedValue.startsWith("i")) {
+                               return Integer.parseInt(cut);
+                       } else if (encodedValue.startsWith("E:")) {
+                               cut = cut.substring(1);
+                               return decodeEnum(cut);
                        } else {
-                               return Integer.parseInt(encodedValue);
+                               throw new IOException("Unrecognized value: " + encodedValue);
                        }
                } catch (Exception e) {
                        if (e instanceof IOException) {
@@ -534,12 +594,7 @@ public class SerialUtils {
         *             in case of I/O error
         */
        static void write(OutputStream out, Object data) throws IOException {
-               try {
-                       out.write(data.toString().getBytes("UTF-8"));
-               } catch (UnsupportedEncodingException e) {
-                       // A conforming JVM is required to support UTF-8
-                       e.printStackTrace();
-               }
+               out.write(StringUtils.getBytes(data.toString()));
        }
 
        /**
@@ -611,28 +666,13 @@ public class SerialUtils {
        static void encodeString(OutputStream out, String raw) throws IOException {
                // TODO: not. efficient.
                out.write('\"');
-               // TODO !! utf-8 required
                for (char car : raw.toCharArray()) {
                        encodeString(out, car);
                }
                out.write('\"');
        }
 
-       // aa bb -> "aa\tbb"
-       static void encodeString(OutputStream out, InputStream raw)
-                       throws IOException {
-               out.write('\"');
-               byte buffer[] = new byte[4096];
-               for (int len = 0; (len = raw.read(buffer)) > 0;) {
-                       for (int i = 0; i < len; i++) {
-                               // TODO: not 100% correct, look up howto for UTF-8
-                               encodeString(out, (char) buffer[i]);
-                       }
-               }
-               out.write('\"');
-       }
-
-       // for encode string, NOT to encode a char by itself!
+       // for encoding string, NOT to encode a char by itself!
        static void encodeString(OutputStream out, char raw) throws IOException {
                switch (raw) {
                case '\\':