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 void toStream(OutputStream out, Object value)
* in case of I/O error
*/
public boolean encode(OutputStream out, Object value) throws IOException {
- if (!isSupported(value)) {
- return false;
- }
-
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) {
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;
/**
* @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;
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}.
*/
* 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 <b>MUST</b> 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;
* @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;
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);
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.
@Override
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 []
- write(out,type);
- write(out,"\n");
+ write(out, type);
+ write(out, "\n");
try {
for (int i = 0; true; i++) {
Object item = Array.get(value, i);
.getMessage());
}
}
- write(out,"\n");
+ write(out, "\n");
}
} catch (ArrayIndexOutOfBoundsException e) {
// Done.
}
}
- @Override
- protected String getType() {
- return "[]";
- }
-
@Override
protected Object fromStream(InputStream in) throws IOException {
- return null;
- }
-
- @Override
- protected Object fromString(String content) throws IOException {
- String[] tab = content.split("\n");
+ NextableInputStream stream = new NextableInputStream(in,
+ new NextableInputStreamStep('\n'));
try {
+ List<Object> list = new ArrayList<Object>();
+ 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;
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;
}
// 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
}
@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());
}
* @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
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;
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;
+ }
}
/**