From: Niki Roo Date: Sun, 28 Apr 2019 17:42:28 +0000 (+0200) Subject: server: String -> Stream X-Git-Tag: fanfix-3.0.1~29^2~97 X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=08f80ac5fa60738d3ad74c4b5390a0b79ae313d4;p=fanfix.git server: String -> Stream --- diff --git a/VERSION b/VERSION index 8f820de..1a71b82 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.7.2-dev +4.7.2-streamify-dev diff --git a/changelog.md b/changelog.md index 4792f9b..3a9ec31 100644 --- a/changelog.md +++ b/changelog.md @@ -6,8 +6,10 @@ - new: CryptUtils - new: streams classes - fix: IOUtils.readSmallStream and \n at the end +- fix: Base64 implementation changed - change: serial: SSL -> CryptUtils - change: MarkableFileInputStream moved to nikiroo.utils.streams +- change: Break compat with package utils.server (Version not used anymore) ## Version 4.7.2 diff --git a/src/be/nikiroo/utils/serial/CustomSerializer.java b/src/be/nikiroo/utils/serial/CustomSerializer.java index be89316..e5539c0 100644 --- a/src/be/nikiroo/utils/serial/CustomSerializer.java +++ b/src/be/nikiroo/utils/serial/CustomSerializer.java @@ -1,39 +1,95 @@ package be.nikiroo.utils.serial; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import be.nikiroo.utils.IOUtils; +import be.nikiroo.utils.streams.NextableInputStream; +import be.nikiroo.utils.streams.NextableInputStreamStep; +import be.nikiroo.utils.streams.ReplaceInputStream; +import be.nikiroo.utils.streams.ReplaceOutputStream; public abstract class CustomSerializer { - protected abstract String toString(Object value); + protected abstract void toStream(OutputStream out, Object value) + throws IOException; - protected abstract Object fromString(String content) throws IOException; + protected abstract Object fromStream(InputStream in) throws IOException; protected abstract String getType(); /** - * Encode the object into the given builder if possible (if supported). + * Encode the object into the given {@link OutputStream} if supported. * - * @param builder + * @param out * the builder to append to * @param value * the object to encode - * @return TRUE if success, FALSE if not (the content of the builder won't - * be changed in case of failure) + * + * @throws IOException + * in case of I/O error */ - public boolean encode(StringBuilder builder, Object value) { - int prev = builder.length(); - String customString = toString(value); - builder.append("custom^").append(getType()).append("^"); - if (!SerialUtils.encode(builder, customString)) { - builder.delete(prev, builder.length()); - return false; - } + public void encode(OutputStream out, Object value) throws IOException { + ReplaceOutputStream replace = new ReplaceOutputStream(out, // + new String[] { "\\", "\n" }, // + new String[] { "\\\\", "\\n" }); - return true; + try { + SerialUtils.write(replace, "custom^"); + SerialUtils.write(replace, getType()); + SerialUtils.write(replace, "^"); + toStream(replace, value); + } finally { + replace.close(false); + } } - public Object decode(String encodedValue) throws IOException { - return fromString((String) SerialUtils.decode(contentOf(encodedValue))); + public Object decode(InputStream in) throws IOException { + ReplaceInputStream replace = new ReplaceInputStream(in, // + new String[] { "\\\\", "\\n" }, // + new String[] { "\\", "\n" }); + + try { + NextableInputStream stream = new NextableInputStream( + replace.open(), new NextableInputStreamStep('^')); + try { + if (!stream.next()) { + throw new IOException( + "Cannot find the first custom^ element"); + } + + String custom = IOUtils.readSmallStream(stream); + if (!"custom".equals(custom)) { + throw new IOException( + "Cannot find the first custom^ element, it is: " + + custom + "^"); + } + + if (!stream.next()) { + throw new IOException("Cannot find the second custom^" + + getType() + " element"); + } + + String type = IOUtils.readSmallStream(stream); + if (!getType().equals(type)) { + throw new IOException("Cannot find the second custom^" + + getType() + " element, it is: custom^" + type + + "^"); + } + + if (!stream.nextAll()) { + throw new IOException("Cannot find the third custom^" + + getType() + "^value element"); + } + + return fromStream(stream); + } finally { + stream.close(); + } + } finally { + replace.close(false); + } } public static boolean isCustom(String encodedValue) { diff --git a/src/be/nikiroo/utils/serial/Exporter.java b/src/be/nikiroo/utils/serial/Exporter.java index dc96d97..2470bde 100644 --- a/src/be/nikiroo/utils/serial/Exporter.java +++ b/src/be/nikiroo/utils/serial/Exporter.java @@ -2,11 +2,10 @@ package be.nikiroo.utils.serial; import java.io.IOException; import java.io.NotSerializableException; +import java.io.OutputStream; import java.util.HashMap; import java.util.Map; -import be.nikiroo.utils.StringUtils; - /** * A simple class to serialise objects to {@link String}. *

@@ -17,14 +16,22 @@ import be.nikiroo.utils.StringUtils; */ public class Exporter { private Map map; - private StringBuilder builder; + private OutputStream out; /** * Create a new {@link Exporter}. + * + * @param out + * export the data to this stream */ - public Exporter() { + public Exporter(OutputStream out) { + if (out == null) { + throw new NullPointerException( + "Cannot create an be.nikiroo.utils.serials.Exporter that will export to NULL"); + } + + this.out = out; map = new HashMap(); - builder = new StringBuilder(); } /** @@ -42,98 +49,12 @@ public class Exporter { * if the object cannot be serialised (in this case, the * {@link Exporter} can contain bad, most probably not * importable data) + * @throws IOException + * in case of I/O error */ - public Exporter append(Object o) throws NotSerializableException { - SerialUtils.append(builder, o, map); + public Exporter append(Object o) throws NotSerializableException, + IOException { + SerialUtils.append(out, o, map); return this; } - - /** - * Clear the current content. - */ - public void clear() { - builder.setLength(0); - map.clear(); - } - - /** - * Append the exported items in a serialised form into the given - * {@link StringBuilder}. - * - * @param toBuilder - * the {@link StringBuilder} - * @param b64 - * TRUE to have BASE64-coded content, FALSE to have raw content, - * NULL to let the system decide - * @param zip - * TRUE to zip the BASE64 output if the output is indeed in - * BASE64 format, FALSE not to - */ - public void appendTo(StringBuilder toBuilder, Boolean b64, boolean zip) { - if (b64 == null && builder.length() < 128) { - b64 = false; - } - - if (b64 == null || b64) { - try { - String zipped = StringUtils.base64(builder.toString(), zip); - if (b64 != null || zipped.length() < builder.length() - 4) { - toBuilder.append(zip ? "ZIP:" : "B64:"); - toBuilder.append(zipped); - return; - } - } catch (IOException e) { - throw new RuntimeException( - "Base64 conversion of data failed, maybe not enough memory?", - e); - } - } - - toBuilder.append(builder); - } - - /** - * The exported items in a serialised form. - * - * @deprecated use {@link Exporter#toString(Boolean, boolean)} instead - * - * @param zip - * TRUE to have zipped (and BASE64-coded) content, FALSE to have - * raw content, NULL to let the system decide - * - * @return the items currently in this {@link Exporter} - */ - @Deprecated - public String toString(Boolean zip) { - return toString(zip, zip == null || zip); - } - - /** - * The exported items in a serialised form. - * - * @param b64 - * TRUE to have BASE64-coded content, FALSE to have raw content, - * NULL to let the system decide - * @param zip - * TRUE to zip the BASE64 output if the output is indeed in - * BASE64 format, FALSE not to - * - * @return the items currently in this {@link Exporter} - */ - public String toString(Boolean b64, boolean zip) { - StringBuilder toBuilder = new StringBuilder(); - appendTo(toBuilder, b64, zip); - return toBuilder.toString(); - } - - /** - * The exported items in a serialised form (possibly BASE64-coded, possibly - * zipped). - * - * @return the items currently in this {@link Exporter} - */ - @Override - public String toString() { - return toString(null, true); - } } \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/Importer.java b/src/be/nikiroo/utils/serial/Importer.java index bca157c..f7fece0 100644 --- a/src/be/nikiroo/utils/serial/Importer.java +++ b/src/be/nikiroo/utils/serial/Importer.java @@ -1,12 +1,15 @@ package be.nikiroo.utils.serial; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.io.InputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; +import be.nikiroo.utils.IOUtils; import be.nikiroo.utils.StringUtils; +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 @@ -19,9 +22,6 @@ import be.nikiroo.utils.StringUtils; * @author niki */ public class Importer { - static private Integer SIZE_ID = null; - static private byte[] NEWLINE = null; - private Boolean link; private Object me; private Importer child; @@ -29,15 +29,6 @@ public class Importer { private String currentFieldName; - static { - try { - SIZE_ID = "EXT:".getBytes("UTF-8").length; - NEWLINE = "\n".getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - // UTF-8 is mandated to exist on confirming jre's - } - } - /** * Create a new {@link Importer}. */ @@ -55,7 +46,7 @@ public class Importer { * content, or a number of lines of it (any given line MUST be * complete though) and accumulate it with the already present data. * - * @param data + * @param in * the data to parse * * @return itself so it can be chained @@ -70,77 +61,50 @@ public class Importer { * 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(String data) throws NoSuchFieldException, - NoSuchMethodException, ClassNotFoundException, IOException { - return read(data.getBytes("UTF-8"), 0); - } + public Importer read(InputStream in) throws NoSuchFieldException, + NoSuchMethodException, ClassNotFoundException, IOException, + NullPointerException { - /** - * 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 data - * the data to parse - * @param offset - * the offset at which to start reading the data (we ignore - * anything that goes before that offset) - * - * @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) - */ - private Importer read(byte[] data, int offset) throws NoSuchFieldException, - NoSuchMethodException, ClassNotFoundException, IOException { + NextableInputStream stream = new NextableInputStream(in, + new NextableInputStreamStep('\n')); - int dataStart = offset; - while (dataStart < data.length) { - String id = ""; - if (data.length - dataStart >= SIZE_ID) { - id = new String(data, dataStart, SIZE_ID); + try { + if (in == null) { + throw new NullPointerException("InputStream is null"); } - boolean zip = id.equals("ZIP:"); - boolean b64 = id.equals("B64:"); - if (zip || b64) { - dataStart += SIZE_ID; - } + boolean first = true; + while (stream.next()) { + if (stream.eof()) { + if (first) { + throw new NullPointerException( + "InputStream empty, normal termination"); + } + return this; + } + first = false; - int count = find(data, dataStart, NEWLINE); - count -= dataStart; - if (count < 0) { - count = data.length - dataStart; - } + boolean zip = stream.startsWiths("ZIP:"); + boolean b64 = stream.startsWiths("B64:"); - if (zip || b64) { - boolean unpacked = false; - try { - byte[] line = StringUtils.unbase64(data, dataStart, count, + if (zip || b64) { + stream.skip("XXX:".length()); + InputStream decoded = StringUtils.unbase64(stream.open(), zip); - unpacked = true; - read(line, 0); - } catch (IOException e) { - throw new IOException("Internal error when decoding " - + (unpacked ? "unpacked " : "") - + (zip ? "ZIP" : "B64") - + " content: input may be corrupt"); + try { + read(decoded); + } finally { + decoded.close(); + } + } else { + processLine(stream); } - } else { - String line = new String(data, dataStart, count, "UTF-8"); - processLine(line); } - - dataStart += count + NEWLINE.length; + } finally { + stream.close(false); } return this; @@ -150,7 +114,7 @@ public class Importer { * Read a single (whole) line of serialised data into this {@link Importer} * and accumulate it with the already present data. * - * @param line + * @param in * the line to parse * * @return TRUE if we are just done with one object or sub-object @@ -166,11 +130,12 @@ public class Importer { * @throws IOException * if the content cannot be read (for instance, corrupt data) */ - private boolean processLine(String line) throws NoSuchFieldException, + private boolean processLine(InputStream 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; @@ -181,6 +146,13 @@ public class Importer { return false; } + // TODO use the stream, Luke + String line = IOUtils.readSmallStream(in); + + if (line.isEmpty()) { + return false; + } + if (line.equals("{")) { // START: new child if needed if (link != null) { child = new Importer(map); @@ -255,37 +227,6 @@ public class Importer { } } - /** - * Find the given needle in the data and return its position (or -1 if not - * found). - * - * @param data - * the data to look through - * @param offset - * the offset at wich to start searching - * @param needle - * the needle to find - * - * @return the position of the needle if found, -1 if not found - */ - private int find(byte[] data, int offset, byte[] needle) { - for (int i = offset; i + needle.length - 1 < data.length; i++) { - boolean same = true; - for (int j = 0; j < needle.length; j++) { - if (data[i + j] != needle[j]) { - same = false; - break; - } - } - - if (same) { - return i; - } - } - - return -1; - } - /** * Return the current deserialised value. * diff --git a/src/be/nikiroo/utils/serial/SerialUtils.java b/src/be/nikiroo/utils/serial/SerialUtils.java index 706d579..a6a02a8 100644 --- a/src/be/nikiroo/utils/serial/SerialUtils.java +++ b/src/be/nikiroo/utils/serial/SerialUtils.java @@ -1,7 +1,11 @@ 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; @@ -13,7 +17,11 @@ import java.util.List; import java.util.Map; import java.util.UnknownFormatConversionException; +import be.nikiroo.utils.IOUtils; import be.nikiroo.utils.Image; +import be.nikiroo.utils.StringUtils; +import be.nikiroo.utils.streams.NextableInputStream; +import be.nikiroo.utils.streams.NextableInputStreamStep; /** * Small class to help with serialisation. @@ -53,50 +61,58 @@ public class SerialUtils { // Array types: customTypes.put("[]", new CustomSerializer() { @Override - protected String toString(Object value) { + 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 [] - StringBuilder builder = new StringBuilder(); - builder.append(type).append("\n"); + write(out, type); try { for (int i = 0; true; i++) { Object item = Array.get(value, i); + // encode it normally if direct value - if (!SerialUtils.encode(builder, item)) { + write(out, "\r"); + if (!SerialUtils.encode(out, item)) { try { - // use ZIP: if not - new Exporter().append(item).appendTo(builder, - true, false); + // TODO: bad escaping? + write(out, "B64:"); + OutputStream bout = StringUtils.base64(out, + false, false); + new Exporter(bout).append(item); } 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) throws IOException { - String[] tab = content.split("\n"); + protected Object fromStream(InputStream in) throws IOException { + NextableInputStream stream = new NextableInputStream(in, + new NextableInputStreamStep('\r')); try { + List list = new ArrayList(); + stream.next(); + String type = IOUtils.readSmallStream(stream); + + while (stream.next()) { + Object value = new Importer().read(stream).getValue(); + list.add(value); + } + 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); + SerialUtils.getClass(type), list.size()); + for (int i = 0; i < list.size(); i++) { + Array.set(array, i, list.get(i)); } return array; @@ -107,23 +123,33 @@ public class SerialUtils { throw new IOException(e.getMessage()); } } + + @Override + protected String getType() { + return "[]"; + } }); // URL: customTypes.put("java.net.URL", new CustomSerializer() { @Override - protected String toString(Object value) { + protected void toStream(OutputStream out, Object value) + throws IOException { + String val = ""; if (value != null) { - return ((URL) value).toString(); + val = ((URL) value).toString(); } - return null; + + out.write(val.getBytes("UTF-8")); } @Override - protected Object fromString(String content) throws IOException { - if (content != null) { - return new URL(content); + protected Object fromStream(InputStream in) throws IOException { + String val = IOUtils.readSmallStream(in); + if (!val.isEmpty()) { + return new URL(val); } + return null; } @@ -136,8 +162,21 @@ public class SerialUtils { // Images (this is currently the only supported image type by default) customTypes.put("be.nikiroo.utils.Image", new CustomSerializer() { @Override - protected String toString(Object value) { - return ((Image) value).toBase64(); + protected void toStream(OutputStream out, Object value) + throws IOException { + Image img = (Image) value; + OutputStream encoded = StringUtils.base64(out, false, false); + try { + InputStream in = img.newInputStream(); + try { + IOUtils.write(in, encoded); + } finally { + in.close(); + } + } finally { + encoded.flush(); + // Cannot close! + } } @Override @@ -146,9 +185,11 @@ public class SerialUtils { } @Override - protected Object fromString(String content) { + protected Object fromStream(InputStream in) throws IOException { try { - return new Image(content); + // Cannot close it! + InputStream decoded = StringUtils.unbase64(in, false); + return new Image(decoded); } catch (IOException e) { throw new UnknownFormatConversionException(e.getMessage()); } @@ -212,7 +253,7 @@ public class SerialUtils { ctor = clazz.getDeclaredConstructor(classes .toArray(new Class[] {})); } catch (NoSuchMethodException nsme) { - // TODO: it seems e do not always need a parameter for each + // TODO: it seems we do not always need a parameter for each // level, so we currently try "ALL" levels or "FIRST" level // only -> we should check the actual rule and use it ctor = clazz.getDeclaredConstructor(classes.get(0)); @@ -252,14 +293,14 @@ public class SerialUtils { } /** - * Serialise the given object into this {@link StringBuilder}. + * Serialise the given object into this {@link OutputStream}. *

* 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 out + * the output {@link OutputStream} to serialise to * @param o * the object to serialise * @param map @@ -271,9 +312,11 @@ public class SerialUtils { * if the object cannot be serialised (in this case, the * {@link StringBuilder} can contain bad, most probably not * importable data) + * @throws IOException + * in case of I/O errors */ - static void append(StringBuilder builder, Object o, Map map) - throws NotSerializableException { + static void append(OutputStream out, Object o, Map map) + throws NotSerializableException, IOException { Field[] fields = new Field[] {}; String type = ""; @@ -295,9 +338,13 @@ public class SerialUtils { } } - builder.append("{\nREF ").append(type).append("@").append(id) - .append(":"); - if (!encode(builder, o)) { // check if direct value + write(out, "{\nREF "); + write(out, type); + write(out, "@"); + write(out, id); + write(out, ":"); + + if (!encode(out, o)) { // check if direct value try { for (Field field : fields) { field.setAccessible(true); @@ -311,16 +358,15 @@ public class SerialUtils { continue; } - builder.append("\n"); - builder.append(field.getName()); - builder.append(":"); - Object value; + write(out, "\n"); + write(out, field.getName()); + write(out, ":"); - value = field.get(o); + Object value = field.get(o); - if (!encode(builder, value)) { - builder.append("\n"); - append(builder, value, map); + if (!encode(out, value)) { + write(out, "\n"); + append(out, value, map); } } } catch (IllegalArgumentException e) { @@ -330,12 +376,14 @@ public class SerialUtils { e.printStackTrace(); // should not happen (see // setAccessible) } + + write(out, "\n}"); } - builder.append("\n}"); } /** - * Encode the object into the given builder if possible and if supported. + * Encode the object into the given {@link OutputStream} if possible and if + * supported. *

* 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 @@ -343,47 +391,57 @@ public class SerialUtils { *

* For compound objects, you should use {@link Exporter}. * - * @param builder - * the builder to append to + * @param out + * the {@link OutputStream} to append to * @param value * the object to encode (can be NULL, which will be encoded) * - * @return TRUE if success, FALSE if not (the content of the builder won't - * be changed in case of failure) + * @return TRUE if success, FALSE if not (the content of the + * {@link OutputStream} won't be changed in case of failure) + * + * @throws IOException + * in case of I/O error */ - static boolean encode(StringBuilder builder, Object value) { + static boolean encode(OutputStream out, Object value) throws IOException { if (value == null) { - builder.append("NULL"); + write(out, "NULL"); } else if (value.getClass().getSimpleName().endsWith("[]")) { // Simple name does support [] suffix and do not return NULL for // inner anonymous classes - return customTypes.get("[]").encode(builder, value); + customTypes.get("[]").encode(out, value); } else if (customTypes.containsKey(value.getClass().getCanonicalName())) { - return customTypes.get(value.getClass().getCanonicalName())// - .encode(builder, value); + customTypes.get(value.getClass().getCanonicalName())// + .encode(out, value); } else if (value instanceof String) { - encodeString(builder, (String) value); + encodeString(out, (String) value); } else if (value instanceof Boolean) { - builder.append(value); + write(out, value); } else if (value instanceof Byte) { - builder.append(value).append('b'); + write(out, value); + write(out, "b"); } else if (value instanceof Character) { - encodeString(builder, "" + value); - builder.append('c'); + encodeString(out, "" + value); + write(out, "c"); } else if (value instanceof Short) { - builder.append(value).append('s'); + write(out, value); + write(out, "s"); } else if (value instanceof Integer) { - builder.append(value); + write(out, value); } else if (value instanceof Long) { - builder.append(value).append('L'); + write(out, value); + write(out, "L"); } else if (value instanceof Float) { - builder.append(value).append('F'); + write(out, value); + write(out, "F"); } else if (value instanceof Double) { - builder.append(value).append('d'); + write(out, value); + write(out, "d"); } else if (value instanceof Enum) { String type = value.getClass().getCanonicalName(); - builder.append(type).append(".").append(((Enum) value).name()) - .append(";"); + write(out, type); + write(out, "."); + write(out, ((Enum) value).name()); + write(out, ";"); } else { return false; } @@ -419,7 +477,14 @@ public class SerialUtils { // custom:TYPE_NAME:"content is String-encoded" String type = CustomSerializer.typeOf(encodedValue); if (customTypes.containsKey(type)) { - return customTypes.get(type).decode(encodedValue); + // 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") @@ -452,7 +517,28 @@ public class SerialUtils { if (e instanceof IOException) { throw (IOException) e; } - throw new IOException(e.getMessage()); + throw new IOException(e.getMessage(), e); + } + } + + /** + * Write the given {@link String} into the given {@link OutputStream} in + * UTF-8. + * + * @param out + * the {@link OutputStream} + * @param data + * the data to write, cannot be NULL + * + * @throws IOException + * 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(); } } @@ -507,7 +593,7 @@ public class SerialUtils { } @SuppressWarnings({ "unchecked", "rawtypes" }) - private static Enum decodeEnum(String escaped) { + static private Enum decodeEnum(String escaped) { // escaped: be.xxx.EnumType.VALUE; int pos = escaped.lastIndexOf("."); String type = escaped.substring(0, pos); @@ -522,32 +608,57 @@ public class SerialUtils { } // aa bb -> "aa\tbb" - private static void encodeString(StringBuilder builder, String raw) { - builder.append('\"'); + static void encodeString(OutputStream out, String raw) throws IOException { + // TODO: not. efficient. + out.write('\"'); + // TODO !! utf-8 required for (char car : raw.toCharArray()) { - switch (car) { - case '\\': - builder.append("\\\\"); - break; - case '\r': - builder.append("\\r"); - break; - case '\n': - builder.append("\\n"); - break; - case '"': - builder.append("\\\""); - break; - default: - builder.append(car); - break; + 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]); } } - builder.append('\"'); + out.write('\"'); + } + + // for encode string, NOT to encode a char by itself! + static void encodeString(OutputStream out, char raw) throws IOException { + switch (raw) { + case '\\': + out.write('\\'); + out.write('\\'); + break; + case '\r': + out.write('\\'); + out.write('r'); + break; + case '\n': + out.write('\\'); + out.write('n'); + break; + case '"': + out.write('\\'); + out.write('\"'); + break; + default: + out.write(raw); + break; + } } // "aa\tbb" -> aa bb - private static String decodeString(String escaped) { + static String decodeString(String escaped) { StringBuilder builder = new StringBuilder(); boolean escaping = false; diff --git a/src/be/nikiroo/utils/serial/server/ConnectAction.java b/src/be/nikiroo/utils/serial/server/ConnectAction.java index a377ced..9016326 100644 --- a/src/be/nikiroo/utils/serial/server/ConnectAction.java +++ b/src/be/nikiroo/utils/serial/server/ConnectAction.java @@ -1,18 +1,21 @@ package be.nikiroo.utils.serial.server; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; import javax.net.ssl.SSLException; import be.nikiroo.utils.CryptUtils; -import be.nikiroo.utils.Version; +import be.nikiroo.utils.IOUtils; import be.nikiroo.utils.serial.Exporter; import be.nikiroo.utils.serial.Importer; +import be.nikiroo.utils.streams.BufferedOutputStream; +import be.nikiroo.utils.streams.NextableInputStream; +import be.nikiroo.utils.streams.NextableInputStreamStep; +import be.nikiroo.utils.streams.ReplaceInputStream; +import be.nikiroo.utils.streams.ReplaceOutputStream; /** * Base class used for the client/server basic handling. @@ -26,44 +29,22 @@ import be.nikiroo.utils.serial.Importer; abstract class ConnectAction { private Socket s; private boolean server; - private Version version; - private Version clientVersion; private CryptUtils crypt; private Object lock = new Object(); - private InputStream in; - private OutputStream out; + private NextableInputStream in; + private BufferedOutputStream out; private boolean contentToSend; - private long bytesReceived; - private long bytesSent; - /** * Method that will be called when an action is performed on either the * client or server this {@link ConnectAction} represent. * - * @param version - * the counter part version - * * @throws Exception * in case of I/O error */ - abstract protected void action(Version version) throws Exception; - - /** - * Method called when we negotiate the version with the client. - *

- * Thus, it is only called on the server. - *

- * Will return the actual server version by default. - * - * @param clientVersion - * the client version - * - * @return the version to send to the client - */ - abstract protected Version negotiateVersion(Version clientVersion); + abstract protected void action() throws Exception; /** * Handler called when an unexpected error occurs in the code. @@ -85,33 +66,13 @@ abstract class ConnectAction { * @param key * an optional key to encrypt all the communications (if NULL, * everything will be sent in clear text) - * @param version - * the version of this client-or-server */ - protected ConnectAction(Socket s, boolean server, String key, - Version version) { + protected ConnectAction(Socket s, boolean server, String key) { this.s = s; this.server = server; if (key != null) { crypt = new CryptUtils(key); } - - if (version == null) { - this.version = new Version(); - } else { - this.version = version; - } - - clientVersion = new Version(); - } - - /** - * The version of this client-or-server. - * - * @return the version - */ - public Version getVersion() { - return version; } /** @@ -120,7 +81,7 @@ abstract class ConnectAction { * @return the amount of bytes received */ public long getBytesReceived() { - return bytesReceived; + return in.getBytesRead(); } /** @@ -128,8 +89,8 @@ abstract class ConnectAction { * * @return the amount of bytes sent */ - public long getBytesSent() { - return bytesSent; + public long getBytesWritten() { + return out.getBytesWritten(); } /** @@ -137,48 +98,20 @@ abstract class ConnectAction { */ public void connect() { try { - in = s.getInputStream(); - try { - out = s.getOutputStream(); - try { - if (server) { - String line; - try { - line = readLine(in); - } catch (SSLException e) { - out.write("Unauthorized\n".getBytes()); - throw e; - } + // TODO: assure that \b is never used, make sure \n usage is OK + in = new NextableInputStream(s.getInputStream(), + new NextableInputStreamStep('\b')); - if (line != null && line.startsWith("VERSION ")) { - // "VERSION client-version" (VERSION 1.0.0) - Version clientVersion = new Version( - line.substring("VERSION ".length())); - this.clientVersion = clientVersion; - Version v = negotiateVersion(clientVersion); - if (v == null) { - v = new Version(); - } - - sendString("VERSION " + v.toString()); - } - - action(clientVersion); - } else { - String v = sendString("VERSION " + version.toString()); - if (v != null && v.startsWith("VERSION ")) { - v = v.substring("VERSION ".length()); - } + try { + out = new BufferedOutputStream(s.getOutputStream()); - action(new Version(v)); - } + try { + action(); } finally { out.close(); - out = null; } } finally { in.close(); - in = null; } } catch (Exception e) { onError(e); @@ -199,8 +132,9 @@ abstract class ConnectAction { * @param data * the data to send * - * @return the answer (which can be NULL) if this action is a client, always - * NULL if it is a server + * @return the answer (which can be NULL if no answer, or NULL for an answer + * which is NULL) if this action is a client, always NULL if it is a + * server * * @throws IOException * in case of I/O error @@ -215,15 +149,7 @@ abstract class ConnectAction { */ protected Object sendObject(Object data) throws IOException, NoSuchFieldException, NoSuchMethodException, ClassNotFoundException { - synchronized (lock) { - String rep = sendString(new Exporter().append(data).toString(true, - true)); - if (rep != null) { - return new Importer().read(rep).getValue(); - } - - return null; - } + return send(out, data, false); } /** @@ -253,12 +179,7 @@ abstract class ConnectAction { protected Object recObject() throws IOException, NoSuchFieldException, NoSuchMethodException, ClassNotFoundException, java.lang.NullPointerException { - String str = recString(); - if (str == null) { - throw new NullPointerException("No more data available"); - } - - return new Importer().read(str).getValue(); + return rec(false); } /** @@ -277,17 +198,20 @@ abstract class ConnectAction { * in case of crypt error */ protected String sendString(String line) throws IOException { - synchronized (lock) { - writeLine(out, line); - - if (server) { - out.flush(); - return null; - } - - contentToSend = true; - return recString(); + try { + return (String) send(out, line, true); + } catch (NoSuchFieldException e) { + // Cannot happen + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // Cannot happen + e.printStackTrace(); + } catch (ClassNotFoundException e) { + // Cannot happen + e.printStackTrace(); } + + return null; } /** @@ -299,7 +223,7 @@ abstract class ConnectAction { *

* Will only flush the data if there is contentToSend. * - * @return the answer (which can be NULL) + * @return the answer (which can be NULL if no more content) * * @throws IOException * in case of I/O error @@ -307,73 +231,177 @@ abstract class ConnectAction { * in case of crypt error */ protected String recString() throws IOException { - synchronized (lock) { - if (server || contentToSend) { - if (contentToSend) { - out.flush(); - contentToSend = false; - } - - return readLine(in); - } - - return null; + try { + return (String) rec(true); + } catch (NoSuchFieldException e) { + // Cannot happen + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // Cannot happen + e.printStackTrace(); + } catch (ClassNotFoundException e) { + // Cannot happen + e.printStackTrace(); + } catch (NullPointerException e) { + // Should happen + e.printStackTrace(); } + + return null; } /** - * Read a possibly encrypted line. + * Serialise and send the given object to the counter part (and, only for + * client, return the deserialised answer -- the server will always receive + * NULL). * - * @param in - * the stream to read from - * @return the unencrypted line + * @param out + * the stream to write to + * @param data + * the data to write + * @param asString + * TRUE to write it as a String, FALSE to write it as an Object * + * @return the answer (which can be NULL if no answer, or NULL for an answer + * which is NULL) if this action is a client, always NULL if it is a + * server * * @throws IOException * in case of I/O error * @throws SSLException * in case of crypt error + * @throws IOException + * in case of I/O error + * @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 */ - private String readLine(InputStream in) throws IOException { - if (inReader == null) { - inReader = new BufferedReader(new InputStreamReader(in)); - } - String line = inReader.readLine(); - if (line != null) { - bytesReceived += line.length(); + private Object send(BufferedOutputStream out, Object data, boolean asString) + throws IOException, NoSuchFieldException, NoSuchMethodException, + ClassNotFoundException, java.lang.NullPointerException { + + synchronized (lock) { + OutputStream sub; if (crypt != null) { - line = crypt.decrypt64s(line, false); + sub = crypt.encrypt64(out.open(), false); + } else { + sub = out.open(); } - } - return line; - } + // TODO: could be possible to check for non-crypt and only + // do it for crypt + sub = new ReplaceOutputStream(sub, // + new String[] { "\\", "\b" }, // + new String[] { "\\\\", "\\b" }); + + try { + if (asString) { + sub.write(data.toString().getBytes("UTF-8")); + } else { + new Exporter(sub).append(data); + } + } finally { + sub.close(); + } - private BufferedReader inReader; + out.write('\b'); + + if (server) { + out.flush(); + return null; + } + + contentToSend = true; + try { + return rec(asString); + } catch (NullPointerException e) { + // We accept no data here for Objects + } + + return null; + } + } /** - * Write a line, possible encrypted. + * Reserved for the server: flush the data to the client and retrieve its + * answer. + *

+ * Also used internally for the client (only do something if there is + * contentToSend). + *

+ * Will only flush the data if there is contentToSend. + *

+ * Note that the behaviour is slightly different for String and Object + * reading regarding exceptions: + *

    + *
  • NULL means that the counter part has no more data to send
  • + *
  • All the exceptions except {@link IOException} are there for Object + * conversion
  • + *
+ * + * @param asString + * TRUE for String reading, FALSE for Object reading (which can + * still be a String) + * + * @return the deserialised answer (which can actually be NULL) * - * @param out - * the stream to write to - * @param line - * the line to write * @throws IOException * in case of I/O error - * @throws SSLException - * in case of crypt error + * @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 java.lang.NullPointerException + * for Objects only: if the counter part has no data to send */ - private void writeLine(OutputStream out, String line) throws IOException { - if (crypt == null) { - out.write(line.getBytes()); - bytesSent += line.length(); - } else { - // TODO: how NOT to create so many big Strings? - String b64 = crypt.encrypt64(line, false); - out.write(b64.getBytes()); - bytesSent += b64.length(); + private Object rec(boolean asString) throws IOException, + NoSuchFieldException, NoSuchMethodException, + ClassNotFoundException, java.lang.NullPointerException { + + synchronized (lock) { + if (server || contentToSend) { + if (contentToSend) { + out.flush(); + contentToSend = false; + } + + if (in.next()) { + // TODO: could be possible to check for non-crypt and only + // do it for crypt + InputStream read = new ReplaceInputStream(in.open(), // + new String[] { "\\\\", "\\b" }, // + new String[] { "\\", "\b" }); + + try { + if (crypt != null) { + read = crypt.decrypt64(read, false); + } + + if (asString) { + return IOUtils.readSmallStream(read); + } + + return new Importer().read(read).getValue(); + } finally { + read.close(); + } + } + + if (!asString) { + throw new NullPointerException(); + } + } + + return null; } - out.write("\n".getBytes()); - bytesSent++; } } \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionClient.java b/src/be/nikiroo/utils/serial/server/ConnectActionClient.java index c56dddd..31b71b9 100644 --- a/src/be/nikiroo/utils/serial/server/ConnectActionClient.java +++ b/src/be/nikiroo/utils/serial/server/ConnectActionClient.java @@ -4,8 +4,6 @@ import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; -import be.nikiroo.utils.Version; - /** * Base class used for the client basic handling. *

@@ -23,22 +21,7 @@ abstract class ConnectActionClient { protected ConnectAction action; /** - * Create a new {@link ConnectActionClient} with the current application - * version (see {@link Version#getCurrentVersion()}) as the client version. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionClient(Socket s, String key) { - this(s, key, Version.getCurrentVersion()); - } - - /** - * Create a new {@link ConnectActionClient} with the current application - * version (see {@link Version#getCurrentVersion()}) as the client version. + * Create a new {@link ConnectActionClient}. * * @param host * the host to bind to @@ -58,33 +41,7 @@ abstract class ConnectActionClient { */ public ConnectActionClient(String host, int port, String key) throws IOException { - this(new Socket(host, port), key, Version.getCurrentVersion()); - } - - /** - * Create a new {@link ConnectActionClient}. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param version - * the client version - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the host is not known - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClient(String host, int port, String key, - Version version) throws IOException { - this(new Socket(host, port), key, version); + this(new Socket(host, port), key); } /** @@ -95,27 +52,18 @@ abstract class ConnectActionClient { * @param key * an optional key to encrypt all the communications (if NULL, * everything will be sent in clear text) - * @param version - * the client version */ - public ConnectActionClient(Socket s, String key, Version version) { - action = new ConnectAction(s, false, key, version) { + public ConnectActionClient(Socket s, String key) { + action = new ConnectAction(s, false, key) { @Override - protected void action(Version serverVersion) throws Exception { - ConnectActionClient.this.action(serverVersion); + protected void action() throws Exception { + ConnectActionClient.this.action(); } @Override protected void onError(Exception e) { ConnectActionClient.this.onError(e); } - - @Override - protected Version negotiateVersion(Version clientVersion) { - new Exception("Should never be called on a client") - .printStackTrace(); - return null; - } }; } @@ -141,14 +89,11 @@ abstract class ConnectActionClient { /** * Method that will be called when an action is performed on the client. * - * @param serverVersion - * the server version - * * @throws Exception * in case of I/O error */ @SuppressWarnings("unused") - public void action(Version serverVersion) throws Exception { + public void action() throws Exception { } /** diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionClientObject.java b/src/be/nikiroo/utils/serial/server/ConnectActionClientObject.java index f8eaae1..da13be5 100644 --- a/src/be/nikiroo/utils/serial/server/ConnectActionClientObject.java +++ b/src/be/nikiroo/utils/serial/server/ConnectActionClientObject.java @@ -56,47 +56,6 @@ public class ConnectActionClientObject extends ConnectActionClient { super(host, port, key); } - /** - * Create a new {@link ConnectActionClientObject}. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param version - * the client version - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClientObject(String host, int port, String key, - Version version) throws IOException { - super(host, port, key, version); - } - - /** - * Create a new {@link ConnectActionClientObject}. - * - * @param s - * the socket to bind to - * @param version - * the client version - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionClientObject(Socket s, String key, Version version) { - super(s, key, version); - } - /** * Serialise and send the given object to the server (and return the * deserialised answer). diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionClientString.java b/src/be/nikiroo/utils/serial/server/ConnectActionClientString.java index 35a01d8..366070c 100644 --- a/src/be/nikiroo/utils/serial/server/ConnectActionClientString.java +++ b/src/be/nikiroo/utils/serial/server/ConnectActionClientString.java @@ -56,47 +56,6 @@ public class ConnectActionClientString extends ConnectActionClient { super(host, port, key); } - /** - * Create a new {@link ConnectActionClientString}. - * - * @param host - * the host to bind to - * @param port - * the port to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param version - * the client version - * - * @throws IOException - * in case of I/O error - * @throws UnknownHostException - * if the IP address of the host could not be determined - * @throws IllegalArgumentException - * if the port parameter is outside the specified range of valid - * port values, which is between 0 and 65535, inclusive - */ - public ConnectActionClientString(String host, int port, String key, - Version version) throws IOException { - super(host, port, key, version); - } - - /** - * Create a new {@link ConnectActionClientString}. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param version - * the client version - */ - public ConnectActionClientString(Socket s, String key, Version version) { - super(s, key, version); - } - /** * Send the given object to the server (and return the answer). * diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionServer.java b/src/be/nikiroo/utils/serial/server/ConnectActionServer.java index 699f307..d0ddb92 100644 --- a/src/be/nikiroo/utils/serial/server/ConnectActionServer.java +++ b/src/be/nikiroo/utils/serial/server/ConnectActionServer.java @@ -2,8 +2,6 @@ package be.nikiroo.utils.serial.server; import java.net.Socket; -import be.nikiroo.utils.Version; - /** * Base class used for the server basic handling. *

@@ -22,20 +20,6 @@ abstract class ConnectActionServer { */ protected ConnectAction action; - /** - * Create a new {@link ConnectActionServer} with the current application - * version (see {@link Version#getCurrentVersion()}) as the server version. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - */ - public ConnectActionServer(Socket s, String key) { - this(s, key, Version.getCurrentVersion()); - } - /** * Create a new {@link ConnectActionServer}. * @@ -44,25 +28,18 @@ abstract class ConnectActionServer { * @param key * an optional key to encrypt all the communications (if NULL, * everything will be sent in clear text) - * @param version - * the server version */ - public ConnectActionServer(Socket s, String key, Version version) { - action = new ConnectAction(s, true, key, version) { + public ConnectActionServer(Socket s, String key) { + action = new ConnectAction(s, true, key) { @Override - protected void action(Version clientVersion) throws Exception { - ConnectActionServer.this.action(clientVersion); + protected void action() throws Exception { + ConnectActionServer.this.action(); } @Override protected void onError(Exception e) { ConnectActionServer.this.onError(e); } - - @Override - protected Version negotiateVersion(Version clientVersion) { - return ConnectActionServer.this.negotiateVersion(clientVersion); - } }; } @@ -128,20 +105,17 @@ abstract class ConnectActionServer { * @return the amount of bytes sent */ public long getBytesSent() { - return action.getBytesSent(); + return action.getBytesWritten(); } /** * Method that will be called when an action is performed on the server. * - * @param clientVersion - * the client version - * * @throws Exception * in case of I/O error */ @SuppressWarnings("unused") - public void action(Version clientVersion) throws Exception { + public void action() throws Exception { } /** @@ -154,19 +128,4 @@ abstract class ConnectActionServer { */ protected void onError(@SuppressWarnings("unused") Exception e) { } - - /** - * Method called when we negotiate the version with the client. - *

- * Will return the actual server version by default. - * - * @param clientVersion - * the client version - * - * @return the version to send to the client - */ - protected Version negotiateVersion( - @SuppressWarnings("unused") Version clientVersion) { - return action.getVersion(); - } } \ No newline at end of file diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionServerObject.java b/src/be/nikiroo/utils/serial/server/ConnectActionServerObject.java index 79fa38c..3457a08 100644 --- a/src/be/nikiroo/utils/serial/server/ConnectActionServerObject.java +++ b/src/be/nikiroo/utils/serial/server/ConnectActionServerObject.java @@ -29,21 +29,6 @@ public class ConnectActionServerObject extends ConnectActionServer { super(s, key); } - /** - * Create a new {@link ConnectActionServerObject}. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param version - * the server version - */ - public ConnectActionServerObject(Socket s, String key, Version version) { - super(s, key, version); - } - /** * Serialise and send the given object to the client. * diff --git a/src/be/nikiroo/utils/serial/server/ConnectActionServerString.java b/src/be/nikiroo/utils/serial/server/ConnectActionServerString.java index 545e4b7..2c3ad8f 100644 --- a/src/be/nikiroo/utils/serial/server/ConnectActionServerString.java +++ b/src/be/nikiroo/utils/serial/server/ConnectActionServerString.java @@ -29,21 +29,6 @@ public class ConnectActionServerString extends ConnectActionServer { super(s, key); } - /** - * Create a new {@link ConnectActionServerString}. - * - * @param s - * the socket to bind to - * @param key - * an optional key to encrypt all the communications (if NULL, - * everything will be sent in clear text) - * @param version - * the server version - */ - public ConnectActionServerString(Socket s, String key, Version version) { - super(s, key, version); - } - /** * Serialise and send the given object to the client. * diff --git a/src/be/nikiroo/utils/serial/server/ServerBridge.java b/src/be/nikiroo/utils/serial/server/ServerBridge.java index 6c2ed01..cb06f56 100644 --- a/src/be/nikiroo/utils/serial/server/ServerBridge.java +++ b/src/be/nikiroo/utils/serial/server/ServerBridge.java @@ -1,13 +1,14 @@ package be.nikiroo.utils.serial.server; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Array; import java.net.Socket; import java.net.UnknownHostException; import be.nikiroo.utils.StringUtils; import be.nikiroo.utils.TraceHandler; -import be.nikiroo.utils.Version; import be.nikiroo.utils.serial.Importer; /** @@ -123,25 +124,25 @@ public class ServerBridge extends Server { @Override protected ConnectActionServer createConnectActionServer(Socket s) { + // Bad impl, not up to date (should work, but not efficient) return new ConnectActionServerString(s, key) { @Override - public void action(final Version clientVersion) throws Exception { - onClientContact(clientVersion); + public void action() throws Exception { + onClientContact(); final ConnectActionServerString bridge = this; try { new ConnectActionClientString(forwardToHost, forwardToPort, - forwardToKey, clientVersion) { + forwardToKey) { @Override - public void action(final Version serverVersion) - throws Exception { - onServerContact(serverVersion); + public void action() throws Exception { + onServerContact(); for (String fromClient = bridge.rec(); fromClient != null; fromClient = bridge .rec()) { - onRec(clientVersion, fromClient); + onRec(fromClient); String fromServer = send(fromClient); - onSend(serverVersion, fromServer); + onSend(fromServer); bridge.send(fromServer); } @@ -163,48 +164,38 @@ public class ServerBridge extends Server { /** * This is the method that is called each time a client contact us. - * - * @param clientVersion - * the client version */ - protected void onClientContact(Version clientVersion) { - getTraceHandler().trace(">>> CLIENT " + clientVersion); + protected void onClientContact() { + getTraceHandler().trace(">>> CLIENT "); } /** * This is the method that is called each time a client contact us. - * - * @param serverVersion - * the server version */ - protected void onServerContact(Version serverVersion) { - getTraceHandler().trace("<<< SERVER " + serverVersion); + protected void onServerContact() { + getTraceHandler().trace("<<< SERVER"); getTraceHandler().trace(""); } /** * This is the method that is called each time a client contact us. * - * @param clientVersion - * the client version * @param data * the data sent by the client */ - protected void onRec(Version clientVersion, String data) { - trace(">>> CLIENT (" + clientVersion + ")", data); + protected void onRec(String data) { + trace(">>> CLIENT", data); } /** * This is the method that is called each time the forwarded server contact * us. * - * @param serverVersion - * the client version * @param data * the data sent by the client */ - protected void onSend(Version serverVersion, String data) { - trace("<<< SERVER (" + serverVersion + ")", data); + protected void onSend(String data) { + trace("<<< SERVER", data); } @Override @@ -226,6 +217,7 @@ public class ServerBridge extends Server { * the data to trace */ private void trace(String prefix, String data) { + // TODO: we convert to string and back int size = data == null ? 0 : data.length(); String ssize = StringUtils.formatNumber(size) + "bytes"; @@ -241,22 +233,29 @@ public class ServerBridge extends Server { } } - Object obj = new Importer().read(data).getValue(); - if (obj == null) { - getTraceHandler().trace("NULL", 2); - getTraceHandler().trace("NULL", 3); - getTraceHandler().trace("NULL", 4); - } else { - if (obj.getClass().isArray()) { - getTraceHandler().trace( - "(" + obj.getClass() + ") with " - + Array.getLength(obj) + "element(s)", - 3); + InputStream stream = new ByteArrayInputStream( + data.getBytes("UTF-8")); + try { + Object obj = new Importer().read(stream).getValue(); + if (obj == null) { + getTraceHandler().trace("NULL", 2); + getTraceHandler().trace("NULL", 3); + getTraceHandler().trace("NULL", 4); } else { - getTraceHandler().trace("(" + obj.getClass() + ")", 2); + if (obj.getClass().isArray()) { + getTraceHandler().trace( + "(" + obj.getClass() + ") with " + + Array.getLength(obj) + + "element(s)", 3); + } else { + getTraceHandler().trace("(" + obj.getClass() + ")", + 2); + } + getTraceHandler().trace("" + obj.toString(), 3); + getTraceHandler().trace(data, 4); } - getTraceHandler().trace("" + obj.toString(), 3); - getTraceHandler().trace(data, 4); + } finally { + stream.close(); } } catch (NoSuchMethodException e) { getTraceHandler().trace("(not an object)", 2); diff --git a/src/be/nikiroo/utils/serial/server/ServerObject.java b/src/be/nikiroo/utils/serial/server/ServerObject.java index 4f72013..0315f90 100644 --- a/src/be/nikiroo/utils/serial/server/ServerObject.java +++ b/src/be/nikiroo/utils/serial/server/ServerObject.java @@ -4,8 +4,6 @@ import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; -import be.nikiroo.utils.Version; - /** * This class implements a simple server that can listen for connections and * send/receive objects. @@ -68,18 +66,19 @@ abstract public class ServerObject extends Server { protected ConnectActionServer createConnectActionServer(Socket s) { return new ConnectActionServerObject(s, key) { @Override - public void action(Version clientVersion) throws Exception { + public void action() throws Exception { try { for (Object data = rec(); true; data = rec()) { Object rep = null; try { - rep = onRequest(this, clientVersion, data); + rep = onRequest(this, data); if (isClosing()) { return; } } catch (Exception e) { onError(e); } + send(rep); } } catch (NullPointerException e) { @@ -101,8 +100,6 @@ abstract public class ServerObject extends Server { * * @param action * the client action - * @param clientVersion - * the client version * @param data * the data sent by the client (which can be NULL) * @@ -112,5 +109,5 @@ abstract public class ServerObject extends Server { * in case of an exception, the error will only be logged */ abstract protected Object onRequest(ConnectActionServerObject action, - Version clientVersion, Object data) throws Exception; + Object data) throws Exception; } diff --git a/src/be/nikiroo/utils/serial/server/ServerString.java b/src/be/nikiroo/utils/serial/server/ServerString.java index 9d8d008..a6e7a04 100644 --- a/src/be/nikiroo/utils/serial/server/ServerString.java +++ b/src/be/nikiroo/utils/serial/server/ServerString.java @@ -4,8 +4,6 @@ import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; -import be.nikiroo.utils.Version; - /** * This class implements a simple server that can listen for connections and * send/receive Strings. @@ -68,11 +66,11 @@ abstract public class ServerString extends Server { protected ConnectActionServer createConnectActionServer(Socket s) { return new ConnectActionServerString(s, key) { @Override - public void action(Version clientVersion) throws Exception { + public void action() throws Exception { for (String data = rec(); data != null; data = rec()) { String rep = null; try { - rep = onRequest(this, clientVersion, data); + rep = onRequest(this, data); if (isClosing()) { return; } @@ -103,8 +101,6 @@ abstract public class ServerString extends Server { * * @param action * the client action - * @param clientVersion - * the client version * @param data * the data sent by the client * @@ -114,5 +110,5 @@ abstract public class ServerString extends Server { * in case of an exception, the error will only be logged */ abstract protected String onRequest(ConnectActionServerString action, - Version clientVersion, String data) throws Exception; + String data) throws Exception; } diff --git a/src/be/nikiroo/utils/test_code/CryptUtilsTest.java b/src/be/nikiroo/utils/test_code/CryptUtilsTest.java index 49005ac..11578f5 100644 --- a/src/be/nikiroo/utils/test_code/CryptUtilsTest.java +++ b/src/be/nikiroo/utils/test_code/CryptUtilsTest.java @@ -135,15 +135,17 @@ class CryptUtilsTest extends TestLauncher { addTest(new TestCase("Simple test") { @Override public void test() throws Exception { - InputStream in = new ByteArrayInputStream(new byte[] {42, 127, 12}); + InputStream in = new ByteArrayInputStream(new byte[] { 42, 127, + 12 }); crypt.encrypt(in); ByteArrayOutputStream out = new ByteArrayOutputStream(); IOUtils.write(in, out); byte[] result = out.toByteArray(); - - assertEquals("We wrote 3 bytes, we expected 3 bytes back but got: " - + result.length, result.length, result.length); - + + assertEquals( + "We wrote 3 bytes, we expected 3 bytes back but got: " + + result.length, result.length, result.length); + assertEquals(42, result[0]); assertEquals(127, result[1]); assertEquals(12, result[2]); diff --git a/src/be/nikiroo/utils/test_code/SerialServerTest.java b/src/be/nikiroo/utils/test_code/SerialServerTest.java index 5f62221..9c346fd 100644 --- a/src/be/nikiroo/utils/test_code/SerialServerTest.java +++ b/src/be/nikiroo/utils/test_code/SerialServerTest.java @@ -2,7 +2,6 @@ package be.nikiroo.utils.test_code; import java.net.URL; -import be.nikiroo.utils.Version; import be.nikiroo.utils.serial.server.ConnectActionClientObject; import be.nikiroo.utils.serial.server.ConnectActionClientString; import be.nikiroo.utils.serial.server.ConnectActionServerObject; @@ -17,8 +16,8 @@ class SerialServerTest extends TestLauncher { public SerialServerTest(String[] args) { super("SerialServer test", args); - for (String key : new String[] { null, "", - "some real key with a few bytes in it" }) { + for (String key : new String[] { null, + "some super secret encryption key" }) { for (boolean bridge : new Boolean[] { false, true }) { final String skey = (key != null ? "(encrypted)" : "(plain text)"); @@ -52,8 +51,7 @@ class SerialServerTest extends TestLauncher { ServerString server = new ServerString(this.getName(), 0, key) { @Override protected String onRequest( - ConnectActionServerString action, - Version clientVersion, String data) + ConnectActionServerString action, String data) throws Exception { return null; } @@ -85,8 +83,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientObject(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { rec[0] = "ok"; } }.connect(); @@ -114,8 +111,7 @@ class SerialServerTest extends TestLauncher { ServerString server = new ServerString(this.getName(), 0, key) { @Override protected String onRequest( - ConnectActionServerString action, - Version clientVersion, String data) + ConnectActionServerString action, String data) throws Exception { sent[0] = data; return "pong"; @@ -143,8 +139,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientString(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { recd[0] = send("ping"); } }.connect(); @@ -158,7 +153,8 @@ class SerialServerTest extends TestLauncher { } if (err[0] != null) { - fail("An exception was thrown: " + err[0].getMessage()); + fail("An exception was thrown: " + err[0].getMessage(), + err[0]); } assertEquals("ping", sent[0]); @@ -176,8 +172,7 @@ class SerialServerTest extends TestLauncher { ServerString server = new ServerString(this.getName(), 0, key) { @Override protected String onRequest( - ConnectActionServerString action, - Version clientVersion, String data) + ConnectActionServerString action, String data) throws Exception { sent[0] = data; action.send("pong"); @@ -207,8 +202,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientString(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { recd[0] = send("ping"); recd[1] = send("ping2"); } @@ -223,7 +217,8 @@ class SerialServerTest extends TestLauncher { } if (err[0] != null) { - fail("An exception was thrown: " + err[0].getMessage()); + fail("An exception was thrown: " + err[0].getMessage(), + err[0]); } assertEquals("ping", sent[0]); @@ -243,8 +238,7 @@ class SerialServerTest extends TestLauncher { ServerString server = new ServerString(this.getName(), 0, key) { @Override protected String onRequest( - ConnectActionServerString action, - Version clientVersion, String data) + ConnectActionServerString action, String data) throws Exception { sent[Integer.parseInt(data)] = data; return "" + (Integer.parseInt(data) * 2); @@ -272,8 +266,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientString(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { for (int i = 0; i < 3; i++) { recd[i] = send("" + i); } @@ -289,7 +282,8 @@ class SerialServerTest extends TestLauncher { } if (err[0] != null) { - fail("An exception was thrown: " + err[0].getMessage()); + fail("An exception was thrown: " + err[0].getMessage(), + err[0]); } assertEquals("0", sent[0]); @@ -316,8 +310,7 @@ class SerialServerTest extends TestLauncher { ServerObject server = new ServerObject(this.getName(), 0, key) { @Override protected Object onRequest( - ConnectActionServerObject action, - Version clientVersion, Object data) + ConnectActionServerObject action, Object data) throws Exception { return null; } @@ -344,8 +337,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientObject(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { rec[0] = true; } @@ -377,8 +369,7 @@ class SerialServerTest extends TestLauncher { ServerObject server = new ServerObject(this.getName(), 0, key) { @Override protected Object onRequest( - ConnectActionServerObject action, - Version clientVersion, Object data) + ConnectActionServerObject action, Object data) throws Exception { sent[0] = data; return "pong"; @@ -406,8 +397,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientObject(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { recd[0] = send("ping"); } }.connect(); @@ -421,7 +411,8 @@ class SerialServerTest extends TestLauncher { } if (err[0] != null) { - fail("An exception was thrown: " + err[0].getMessage()); + fail("An exception was thrown: " + err[0].getMessage(), + err[0]); } assertEquals("ping", sent[0]); @@ -439,8 +430,7 @@ class SerialServerTest extends TestLauncher { ServerObject server = new ServerObject(this.getName(), 0, key) { @Override protected Object onRequest( - ConnectActionServerObject action, - Version clientVersion, Object data) + ConnectActionServerObject action, Object data) throws Exception { sent[0] = data; action.send("pong"); @@ -470,8 +460,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientObject(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { recd[0] = send("ping"); recd[1] = send("ping2"); } @@ -486,7 +475,8 @@ class SerialServerTest extends TestLauncher { } if (err[0] != null) { - fail("An exception was thrown: " + err[0].getMessage()); + fail("An exception was thrown: " + err[0].getMessage(), + err[0]); } assertEquals("ping", sent[0]); @@ -506,8 +496,7 @@ class SerialServerTest extends TestLauncher { ServerObject server = new ServerObject(this.getName(), 0, key) { @Override protected Object onRequest( - ConnectActionServerObject action, - Version clientVersion, Object data) + ConnectActionServerObject action, Object data) throws Exception { sent[0] = data; return new Object[] { "ACK" }; @@ -535,8 +524,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientObject(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { recd[0] = send(new Object[] { "key", new URL( @@ -554,7 +542,8 @@ class SerialServerTest extends TestLauncher { } if (err[0] != null) { - fail("An exception was thrown: " + err[0].getMessage()); + fail("An exception was thrown: " + err[0].getMessage(), + err[0]); } Object[] sento = (Object[]) (sent[0]); @@ -578,8 +567,7 @@ class SerialServerTest extends TestLauncher { ServerObject server = new ServerObject(this.getName(), 0, key) { @Override protected Object onRequest( - ConnectActionServerObject action, - Version clientVersion, Object data) + ConnectActionServerObject action, Object data) throws Exception { sent[(Integer) data] = data; return ((Integer) data) * 2; @@ -607,8 +595,7 @@ class SerialServerTest extends TestLauncher { try { new ConnectActionClientObject(null, port, key) { @Override - public void action(Version serverVersion) - throws Exception { + public void action() throws Exception { for (int i = 0; i < 3; i++) { recd[i] = send(i); } @@ -624,7 +611,8 @@ class SerialServerTest extends TestLauncher { } if (err[0] != null) { - fail("An exception was thrown: " + err[0].getMessage()); + fail("An exception was thrown: " + err[0].getMessage(), + err[0]); } assertEquals(0, sent[0]); diff --git a/src/be/nikiroo/utils/test_code/SerialTest.java b/src/be/nikiroo/utils/test_code/SerialTest.java index f3ed346..c008dec 100644 --- a/src/be/nikiroo/utils/test_code/SerialTest.java +++ b/src/be/nikiroo/utils/test_code/SerialTest.java @@ -1,6 +1,12 @@ package be.nikiroo.utils.test_code; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.NotSerializableException; import java.net.URL; +import java.util.Arrays; import be.nikiroo.utils.serial.Exporter; import be.nikiroo.utils.serial.Importer; @@ -15,6 +21,50 @@ class SerialTest extends TestLauncher { this(null); } + private void encodeRecodeTest(TestCase test, Object data) throws Exception { + byte[] encoded = toBytes(data, true); + Object redata = fromBytes(toBytes(data, false)); + byte[] reencoded = toBytes(redata, true); + + // We suppose text mode + if (encoded.length < 256 && reencoded.length < 256) { + test.assertEquals("Different data after encode/decode/encode", + new String(encoded, "UTF-8"), + new String(reencoded, "UTF-8")); + } else { + test.assertEquals("Different data after encode/decode/encode", + true, Arrays.equals(encoded, reencoded)); + } + } + + // try to remove pointer addresses + private byte[] toBytes(Object data, boolean clearRefs) + throws NotSerializableException, IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new Exporter(out).append(data); + out.flush(); + + if (clearRefs) { + String tmp = new String(out.toByteArray(), "UTF-8"); + tmp = tmp.replaceAll("@[0-9]*", "@REF"); + return tmp.getBytes("UTF-8"); + } + + return out.toByteArray(); + } + + private Object fromBytes(byte[] data) throws NoSuchFieldException, + NoSuchMethodException, ClassNotFoundException, + NullPointerException, IOException { + + InputStream in = new ByteArrayInputStream(data); + try { + return new Importer().read(in).getValue(); + } finally { + in.close(); + } + } + public SerialTest(String[] args) { super("Serial test", args); @@ -22,17 +72,9 @@ class SerialTest extends TestLauncher { @Override public void test() throws Exception { Data data = new Data(42); - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); + encodeRecodeTest(this, data); } }); - addTest(new TestCase() { @SuppressWarnings("unused") private TestCase me = setName("Anonymous inner class"); @@ -43,18 +85,9 @@ class SerialTest extends TestLauncher { @SuppressWarnings("unused") int value = 42; }; - - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); + encodeRecodeTest(this, data); } }); - addTest(new TestCase() { @SuppressWarnings("unused") private TestCase me = setName("Array of anonymous inner classes"); @@ -66,211 +99,91 @@ class SerialTest extends TestLauncher { int value = 42; } }; - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - // Comparing the 2 strings won't be useful, because the @REFs - // will be ZIP-encoded; so we parse and re-encode the object - encoded = new Exporter().append(data[0]).toString(false, false); - try { - reencoded = new Exporter().append(((Data[]) redata)[0]) - .toString(false, false); - } catch (Exception e) { - fail("Cannot cast the returned data into its original object", - e); - } - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); + byte[] encoded = toBytes(data, false); + Object redata = fromBytes(encoded); + + // Comparing the 2 arrays won't be useful, because the @REFs + // will be ZIP-encoded; so we parse and re-encode each object + + byte[] encoded1 = toBytes(data[0], true); + byte[] reencoded1 = toBytes(((Object[]) redata)[0], true); + + assertEquals("Different data after encode/decode/encode", true, + Arrays.equals(encoded1, reencoded1)); } }); - addTest(new TestCase("URL Import/Export") { @Override public void test() throws Exception { URL data = new URL("https://fanfan.be/"); - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); + encodeRecodeTest(this, data); } }); - addTest(new TestCase("URL-String Import/Export") { @Override public void test() throws Exception { String data = new URL("https://fanfan.be/").toString(); - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); - assertEquals(data, redata); + encodeRecodeTest(this, data); } }); - addTest(new TestCase("URL/URL-String arrays Import/Export") { @Override public void test() throws Exception { final String url = "https://fanfan.be/"; - Object[] data = new Object[] { new URL(url), url }; - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); - assertEquals(data[0], ((Object[]) redata)[0]); - assertEquals(data[1], ((Object[]) redata)[1]); + + byte[] encoded = toBytes(data, false); + Object redata = fromBytes(encoded); + + // Comparing the 2 arrays won't be useful, because the @REFs + // will be ZIP-encoded; so we parse and re-encode each object + + byte[] encoded1 = toBytes(data[0], true); + byte[] reencoded1 = toBytes(((Object[]) redata)[0], true); + byte[] encoded2 = toBytes(data[1], true); + byte[] reencoded2 = toBytes(((Object[]) redata)[1], true); + + assertEquals("Different data 1 after encode/decode/encode", + true, Arrays.equals(encoded1, reencoded1)); + assertEquals("Different data 2 after encode/decode/encode", + true, Arrays.equals(encoded2, reencoded2)); } }); - addTest(new TestCase("Import/Export with nested objects") { @Override public void test() throws Exception { Data data = new DataObject(new Data(21)); - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); + encodeRecodeTest(this, data); } }); - addTest(new TestCase("Import/Export with nested objects forming a loop") { @Override public void test() throws Exception { DataLoop data = new DataLoop("looping"); data.next = new DataLoop("level 2"); data.next.next = data; - - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); + encodeRecodeTest(this, data); } }); - addTest(new TestCase("Array in Object Import/Export") { @Override public void test() throws Exception { Object data = new DataArray();// new String[] { "un", "deux" }; - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); + encodeRecodeTest(this, data); } }); - addTest(new TestCase("Array Import/Export") { @Override public void test() throws Exception { Object data = new String[] { "un", "deux" }; - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); + encodeRecodeTest(this, data); } }); - addTest(new TestCase("Enum Import/Export") { @Override public void test() throws Exception { Object data = EnumToSend.FANFAN; - String encoded = new Exporter().append(data).toString(false, - false); - Object redata = new Importer().read(encoded).getValue(); - String reencoded = new Exporter().append(redata).toString( - false, false); - - assertEquals(encoded.replaceAll("@[0-9]*", "@REF"), - reencoded.replaceAll("@[0-9]*", "@REF")); - } - }); - - addTest(new TestCase("B64 and ZIP String test") { - @Override - public void test() throws Exception { - Object data = "Fanfan la tulipe"; - String encoded = new Exporter().append(data).toString(true, - false); - String redata = (String) new Importer().read(encoded) - .getValue(); - - assertEquals("Items not identical after B64", data, redata); - - encoded = new Exporter().append(data).toString(true, true); - redata = (String) new Importer().read(encoded).getValue(); - - assertEquals("Items not identical after ZIP", data, redata); - } - }); - - addTest(new TestCase("B64 and ZIP Data test") { - @Override - public void test() throws Exception { - Object data = new Data(55); - String encoded = new Exporter().append(data).toString(true, - false); - Data redata = (Data) new Importer().read(encoded).getValue(); - - assertEquals("Items not identical after B64", data, redata); - - encoded = new Exporter().append(data).toString(true, true); - redata = (Data) new Importer().read(encoded).getValue(); - - assertEquals("Items not identical after ZIP", data, redata); - } - }); - - addTest(new TestCase("B64 and ZIP 70000 chars test") { - @Override - public void test() throws Exception { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < 7000; i++) { - builder.append("0123456789"); - } - - Object data = builder.toString(); - String encoded = new Exporter().append(data).toString(true, - false); - String redata = (String) new Importer().read(encoded) - .getValue(); - - assertEquals("Items not identical after B64", data, redata); - - encoded = new Exporter().append(data).toString(true, true); - redata = (String) new Importer().read(encoded).getValue(); - - assertEquals("Items not identical after ZIP", data, redata); + encodeRecodeTest(this, data); } }); }