From: Niki Roo Date: Thu, 25 Apr 2019 15:41:06 +0000 (+0200) Subject: Merge branch 'master' into streamify X-Git-Url: http://git.nikiroo.be/?p=nikiroo-utils.git;a=commitdiff_plain;h=1d20e650389b5aaaaa5de4213cefefade582da5d;hp=473e5f319a4cacc584b5daaf8f0c5d1f18bbf5d0 Merge branch 'master' into streamify --- diff --git a/src/be/nikiroo/utils/serial/CustomSerializer.java b/src/be/nikiroo/utils/serial/CustomSerializer.java index be89316..9c3ef8c 100644 --- a/src/be/nikiroo/utils/serial/CustomSerializer.java +++ b/src/be/nikiroo/utils/serial/CustomSerializer.java @@ -1,39 +1,86 @@ 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.NextableInputStream; +import be.nikiroo.utils.NextableInputStreamStep; 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) + * + * @return FALSE if the value is not supported, TRUE if the operation was + * successful (if the value is supported by the operation was not + * successful, you will get an {@link IOException}) + * + * @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 boolean encode(OutputStream out, Object value) throws IOException { + SerialUtils.write(out, "custom^"); + SerialUtils.write(out, getType()); + SerialUtils.write(out, "^"); + // TODO: manage ENTER + toStream(out, value); return true; } - public Object decode(String encodedValue) throws IOException { - return fromString((String) SerialUtils.decode(contentOf(encodedValue))); + public Object decode(InputStream in) throws IOException { + // TODO: manage ENTER + // TODO read and skip "custom^......^": next(), next(), nextAll() ? + NextableInputStream stream = new NextableInputStream(in, + 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"); + } + + // TODO: manage ENTER + return fromStream(stream); + } finally { + stream.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..709c590 100644 --- a/src/be/nikiroo/utils/serial/Exporter.java +++ b/src/be/nikiroo/utils/serial/Exporter.java @@ -2,6 +2,7 @@ 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; @@ -17,14 +18,19 @@ import be.nikiroo.utils.StringUtils; */ public class Exporter { private Map map; - private StringBuilder builder; + private OutputStream out; /** * Create a new {@link Exporter}. */ - 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,26 +48,21 @@ 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}. + * {@link OutputStream}. * - * @param toBuilder - * the {@link StringBuilder} + * @param out + * the {@link OutputStream} * @param b64 * TRUE to have BASE64-coded content, FALSE to have raw content, * NULL to let the system decide @@ -69,17 +70,17 @@ public class Exporter { * 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) { + public void appendTo(OutputStream out, Boolean b64, boolean zip) { + if (b64 == null && out.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); + String zipped = StringUtils.base64(out.toString(), zip); + if (b64 != null || zipped.length() < out.length() - 4) { + SerialUtils.write(out, zip ? "ZIP:" : "B64:"); + SerialUtils.write(out, zipped); return; } } catch (IOException e) { @@ -89,51 +90,6 @@ public class Exporter { } } - 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); + out.append(out); } } \ 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..5d7d8d0 100644 --- a/src/be/nikiroo/utils/serial/Importer.java +++ b/src/be/nikiroo/utils/serial/Importer.java @@ -1,11 +1,14 @@ 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.NextableInputStream; +import be.nikiroo.utils.NextableInputStreamStep; import be.nikiroo.utils.StringUtils; /** @@ -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}. */ @@ -70,77 +61,44 @@ 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 { + // TODO: fix NexInStream: next() MUST be called first time, too + // TODO: NexInStream: add getBytes() (size downloaded) + // TODO: public InputStrem open() (open/close do nothing) + // TODO: public boolean eof() + // TODO: public nextAll(): next, but disable separation of sub-streams + // TODO: close(alsoCloseIncludedField) - int dataStart = offset; - while (dataStart < data.length) { - String id = ""; - if (data.length - dataStart >= SIZE_ID) { - id = new String(data, dataStart, SIZE_ID); - } + NextableInputStream stream = new NextableInputStream(in, + new NextableInputStreamStep('\n')); - boolean zip = id.equals("ZIP:"); - boolean b64 = id.equals("B64:"); - if (zip || b64) { - dataStart += SIZE_ID; + if (in == null || stream.eof()) { + if (in == null) { + throw new NullPointerException("InputStream is null"); } + throw new NullPointerException("InputStream is empty"); + } - int count = find(data, dataStart, NEWLINE); - count -= dataStart; - if (count < 0) { - count = data.length - dataStart; - } + while (stream.next()) { + boolean zip = stream.startsWiths("ZIP:"); + boolean b64 = stream.startsWiths("B64:"); if (zip || b64) { - boolean unpacked = false; + InputStream decoded = StringUtils.unbase64(stream.open(), zip); try { - byte[] line = StringUtils.unbase64(data, dataStart, count, - 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"); + read(decoded); + } finally { + decoded.close(); } } else { - String line = new String(data, dataStart, count, "UTF-8"); - processLine(line); + processLine(stream); } - - dataStart += count + NEWLINE.length; } return this; @@ -166,11 +124,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 +140,9 @@ public class Importer { return false; } + // TODO use the stream, Luke + String line = IOUtils.readSmallStream(in); + if (line.equals("{")) { // START: new child if needed if (link != null) { child = new Importer(map); diff --git a/src/be/nikiroo/utils/serial/SerialUtils.java b/src/be/nikiroo/utils/serial/SerialUtils.java index 706d579..0dc4a35 100644 --- a/src/be/nikiroo/utils/serial/SerialUtils.java +++ b/src/be/nikiroo/utils/serial/SerialUtils.java @@ -1,7 +1,10 @@ package be.nikiroo.utils.serial; 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 +16,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.NextableInputStream; +import be.nikiroo.utils.NextableInputStreamStep; +import be.nikiroo.utils.StringUtils; /** * Small class to help with serialisation. @@ -53,50 +60,54 @@ 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); + write(out, "\n"); try { for (int i = 0; true; i++) { Object item = Array.get(value, i); // encode it normally if direct value - if (!SerialUtils.encode(builder, item)) { + if (!SerialUtils.encode(out, item)) { try { - // use ZIP: if not - new Exporter().append(item).appendTo(builder, - true, false); + // TODO: use ZIP: if not? + new Exporter(out).append(item); } catch (NotSerializableException e) { throw new UnknownFormatConversionException(e .getMessage()); } } - builder.append("\n"); + write(out, "\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('\n')); 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 +118,34 @@ 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 +158,20 @@ 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.close(); + } } @Override @@ -146,9 +180,9 @@ public class SerialUtils { } @Override - protected Object fromString(String content) { + protected Object fromStream(InputStream in) throws IOException { try { - return new Image(content); + return new Image(in); } catch (IOException e) { throw new UnknownFormatConversionException(e.getMessage()); } @@ -252,14 +286,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 +305,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 +331,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 +351,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) { @@ -331,11 +370,12 @@ public class SerialUtils { // setAccessible) } } - builder.append("\n}"); + write(out, "\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 +383,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); + return customTypes.get("[]").encode(out, value); } else if (customTypes.containsKey(value.getClass().getCanonicalName())) { return customTypes.get(value.getClass().getCanonicalName())// - .encode(builder, value); + .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; } @@ -456,6 +506,27 @@ public class SerialUtils { } } + /** + * 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(); + } + } + /** * Return the corresponding class or throw an {@link Exception} if it * cannot. @@ -507,7 +578,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 +593,56 @@ 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 { + 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[4069]; + 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..8dbc7aa 100644 --- a/src/be/nikiroo/utils/serial/server/ConnectAction.java +++ b/src/be/nikiroo/utils/serial/server/ConnectAction.java @@ -199,8 +199,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 @@ -216,10 +217,18 @@ 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(); + new Exporter(out).append(data); + + if (server) { + out.flush(); + return null; + } + + contentToSend = true; + try { + return recObject(); + } catch (NullPointerException e) { + // We accept no data here } return null; @@ -253,12 +262,18 @@ 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"); - } + synchronized (lock) { + if (server || contentToSend) { + if (contentToSend) { + out.flush(); + contentToSend = false; + } - return new Importer().read(str).getValue(); + return new Importer().read(in).getValue(); + } + + return null; + } } /** @@ -365,15 +380,15 @@ abstract class ConnectAction { */ private void writeLine(OutputStream out, String line) throws IOException { if (crypt == null) { - out.write(line.getBytes()); + out.write(line.getBytes("UTF-8")); bytesSent += line.length(); } else { // TODO: how NOT to create so many big Strings? String b64 = crypt.encrypt64(line, false); - out.write(b64.getBytes()); + out.write(b64.getBytes("UTF-8")); bytesSent += b64.length(); } - out.write("\n".getBytes()); + out.write("\n".getBytes("UTF-8")); bytesSent++; } } \ No newline at end of file