- 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 (small change, Version not used anymore)
-- change: Break compat with base64 methods in StringUtils (now it works correctly, too)
+- change: markableFileInputStream moved to nikiroo.utils.streams
+- change: break compat with package utils.server
+- change: break compat with base64 methods in StringUtils (now it works correctly, too)
+- change: TestLauncher is now "silent" by default (no exception details, see setDetails(true))
## Version 4.7.2
import java.io.InputStream;
import java.io.OutputStream;
-import be.nikiroo.utils.IOUtils;
import be.nikiroo.utils.streams.BufferedInputStream;
-import be.nikiroo.utils.streams.NextableInputStream;
-import be.nikiroo.utils.streams.NextableInputStreamStep;
import be.nikiroo.utils.streams.ReplaceInputStream;
import be.nikiroo.utils.streams.ReplaceOutputStream;
protected abstract String getType();
/**
- * Encode the object into the given {@link OutputStream}.
+ * Encode the object into the given {@link OutputStream}, i.e., generate the
+ * <tt><i>ENCODED_VALUE</i></tt> part.
+ * <p>
+ * Use whatever scheme you wish, the system shall ensure that the content is
+ * correctly encoded and that you will receive the same content at decode
+ * time.
*
* @param out
* the builder to append to
/**
* Decode the value back into the supported object type.
+ * <p>
+ * We do <b>not</b> expect the full content here but only:
+ * <ul>
+ * <li>ENCODED_VALUE
+ * <li>
+ * </ul>
+ * That is, we do not expect the "<tt>custom</tt>^<tt><i>TYPE</i></tt>^"
+ * part.
*
* @param in
* the encoded value
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();
- }
+ return fromStream(replace);
} finally {
replace.close(false);
}
}
- /** Use {@link CustomSerializer#isCustom(BufferedInputStream)}. */
- @Deprecated
- public static boolean isCustom(String encodedValue) {
- int pos1 = encodedValue.indexOf('^');
- int pos2 = encodedValue.indexOf('^', pos1 + 1);
-
- return pos1 >= 0 && pos2 >= 0 && encodedValue.startsWith("custom^");
- }
-
public static boolean isCustom(BufferedInputStream in) throws IOException {
return in.startsWith("custom^");
}
// Custom objects
if (CustomSerializer.isCustom(in)) {
// not a field value but a direct value
- String line = IOUtils.readSmallStream(in);
- me = SerialUtils.decode(line);
+ me = SerialUtils.decode(in);
return false;
}
- // TODO use the stream, Luke
- // .. at least for REF
- String line = IOUtils.readSmallStream(in);
-
- if (line.startsWith("REF ")) { // REF: create/link self
- // TODO: here, line is REF type@999:xxx
+ // REF: (object)
+ if (in.startsWith("REF ")) { // REF: create/link self
+ // here, line is REF type@999:xxx
// xxx is optional
- // note: use .end() when containsKey(ref)
- String[] tab = line.substring("REF ".length()).split("@");
- String type = tab[0];
- tab = tab[1].split(":");
- String ref = tab[0];
-
- link = map.containsKey(ref);
- if (link) {
- me = map.get(ref);
- } else {
- if (line.endsWith(":")) {
- // construct
- me = SerialUtils.createObject(type);
+
+ NextableInputStream stream = new NextableInputStream(in,
+ new NextableInputStreamStep(':'));
+ try {
+ stream.next();
+
+ stream.skip("REF ".length());
+ String header = IOUtils.readSmallStream(stream);
+
+ String[] tab = header.split("@");
+ if (tab.length != 2) {
+ throw new IOException("Bad import header line: " + header);
+ }
+ String type = tab[0];
+ String ref = tab[1];
+
+ stream.nextAll();
+
+ link = map.containsKey(ref);
+ if (link) {
+ me = map.get(ref);
+ stream.end();
} else {
- // direct value
- int pos = line.indexOf(":");
- String encodedValue = line.substring(pos + 1);
- me = SerialUtils.decode(encodedValue);
+ if (stream.eof()) {
+ // construct
+ me = SerialUtils.createObject(type);
+ } else {
+ // direct value
+ me = SerialUtils.decode(stream);
+ }
+ map.put(ref, me);
}
- map.put(ref, me);
+ } finally {
+ stream.close(false);
}
- } else { // FIELD: new field *or* direct simple value
- if (line.endsWith(":")) {
- // field value is compound
- currentFieldName = line.substring(0, line.length() - 1);
- } else if (line.startsWith(":") || !line.contains(":")
- || line.startsWith("\"")) {
- // not a field value but a direct value
- me = SerialUtils.decode(line);
- } else {
- // field value is direct
- int pos = line.indexOf(":");
- String fieldName = line.substring(0, pos);
- String encodedValue = line.substring(pos + 1);
- Object value = null;
- value = SerialUtils.decode(encodedValue);
-
- // To support simple types directly:
- if (me == null) {
- me = value;
+
+ return false;
+ }
+
+ if (SerialUtils.isDirectValue(in)) {
+ // not a field value but a direct value
+ me = SerialUtils.decode(in);
+ return false;
+ }
+
+ if (in.startsWith("^")) {
+ in.skip(1);
+
+ NextableInputStream nameThenContent = new NextableInputStream(in,
+ new NextableInputStreamStep(':'));
+
+ try {
+ nameThenContent.next();
+ String fieldName = IOUtils.readSmallStream(nameThenContent);
+
+ if (nameThenContent.next() && !nameThenContent.eof()) {
+ // field value is direct or custom
+ Object value = null;
+ value = SerialUtils.decode(nameThenContent);
+
+ // To support simple types directly:
+ if (me == null) {
+ me = value;
+ } else {
+ setField(fieldName, value);
+ }
} else {
- setField(fieldName, value);
+ // field value is compound
+ currentFieldName = fieldName;
}
+ } finally {
+ nameThenContent.close(false);
}
+
+ return false;
}
- return false;
+ String line = IOUtils.readSmallStream(in);
+ throw new IOException("Line cannot be processed: <" + line + ">");
}
private void setField(String name, Object value)
package be.nikiroo.utils.serial;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.NotSerializableException;
import be.nikiroo.utils.StringUtils;
import be.nikiroo.utils.streams.Base64InputStream;
import be.nikiroo.utils.streams.Base64OutputStream;
+import be.nikiroo.utils.streams.BufferedInputStream;
import be.nikiroo.utils.streams.NextableInputStream;
import be.nikiroo.utils.streams.NextableInputStreamStep;
continue;
}
- write(out, "\n");
+ write(out, "\n^");
write(out, field.getName());
write(out, ":");
} else if (value instanceof Boolean) {
write(out, value);
} else if (value instanceof Byte) {
- write(out, value);
write(out, "b");
+ write(out, value);
} else if (value instanceof Character) {
- encodeString(out, "" + value);
write(out, "c");
+ encodeString(out, "" + value);
} else if (value instanceof Short) {
- write(out, value);
write(out, "s");
+ write(out, value);
} else if (value instanceof Integer) {
+ write(out, "i");
write(out, value);
} else if (value instanceof Long) {
+ write(out, "l");
write(out, value);
- write(out, "L");
} else if (value instanceof Float) {
+ write(out, "f");
write(out, value);
- write(out, "F");
} else if (value instanceof Double) {
- write(out, value);
write(out, "d");
+ write(out, value);
} else if (value instanceof Enum) {
+ write(out, "E:");
String type = value.getClass().getCanonicalName();
write(out, type);
write(out, ".");
return true;
}
+ static boolean isDirectValue(BufferedInputStream encodedValue)
+ throws IOException {
+ if (CustomSerializer.isCustom(encodedValue)) {
+ return false;
+ }
+
+ for (String fullValue : new String[] { "NULL", "null", "true", "false" }) {
+ if (encodedValue.is(fullValue)) {
+ return true;
+ }
+ }
+
+ // TODO: Not efficient
+ for (String prefix : new String[] { "c\"", "\"", "b", "s", "i", "l",
+ "f", "d", "E:" }) {
+ if (encodedValue.startsWith(prefix)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Decode the data into an equivalent supported source object.
* <p>
* A supported object in this context means an object we can directly
- * encode, like an Integer or a String. Custom objects and arrays are also
- * considered supported, but <b>compound objects are not supported here</b>.
+ * encode, like an Integer or a String (see
+ * {@link SerialUtils#decode(String)}.
+ * <p>
+ * Custom objects and arrays are also considered supported here, but
+ * <b>compound objects are not</b>.
+ * <p>
+ * For compound objects, you should use {@link Importer}.
+ *
+ * @param encodedValue
+ * the encoded data, cannot be NULL
+ *
+ * @return the object (can be NULL for NULL encoded values)
+ *
+ * @throws IOException
+ * if the content cannot be converted
+ */
+ static Object decode(BufferedInputStream encodedValue) throws IOException {
+ if (CustomSerializer.isCustom(encodedValue)) {
+ // custom^TYPE^ENCODED_VALUE
+ NextableInputStream content = new NextableInputStream(encodedValue,
+ new NextableInputStreamStep('^'));
+ try {
+ content.next();
+ @SuppressWarnings("unused")
+ String custom = IOUtils.readSmallStream(content);
+ content.next();
+ String type = IOUtils.readSmallStream(content);
+ content.nextAll();
+ if (customTypes.containsKey(type)) {
+ return customTypes.get(type).decode(content);
+ }
+ content.end();
+ throw new IOException("Unknown custom type: " + type);
+ } finally {
+ content.close(false);
+ // TODO: check what happens with thrown Exception in finally
+ encodedValue.end();
+ }
+ }
+
+ String encodedString = IOUtils.readSmallStream(encodedValue);
+ return decode(encodedString);
+ }
+
+ /**
+ * Decode the data into an equivalent supported source object.
+ * <p>
+ * A supported object in this context means an object we can directly
+ * encode, like an Integer or a String.
+ * <p>
+ * For custom objects and arrays, you should use
+ * {@link SerialUtils#decode(InputStream)} or directly {@link Importer}.
* <p>
* For compound objects, you should use {@link Importer}.
*
try {
String cut = "";
if (encodedValue.length() > 1) {
- cut = encodedValue.substring(0, encodedValue.length() - 1);
+ cut = encodedValue.substring(1);
}
- if (CustomSerializer.isCustom(encodedValue)) {
- // custom:TYPE_NAME:"content is String-encoded"
- String type = CustomSerializer.typeOf(encodedValue);
- if (customTypes.containsKey(type)) {
- // TODO: we should start with a stream
- InputStream streamEncodedValue = new ByteArrayInputStream(
- StringUtils.getBytes(encodedValue));
- try {
- return customTypes.get(type).decode(streamEncodedValue);
- } finally {
- streamEncodedValue.close();
- }
- }
- throw new IOException("Unknown custom type: " + type);
- } else if (encodedValue.equals("NULL")
- || encodedValue.equals("null")) {
+ if (encodedValue.equals("NULL") || encodedValue.equals("null")) {
return null;
- } else if (encodedValue.endsWith("\"")) {
+ } else if (encodedValue.startsWith("\"")) {
return decodeString(encodedValue);
} else if (encodedValue.equals("true")) {
return true;
} else if (encodedValue.equals("false")) {
return false;
- } else if (encodedValue.endsWith("b")) {
+ } else if (encodedValue.startsWith("b")) {
return Byte.parseByte(cut);
- } else if (encodedValue.endsWith("c")) {
+ } else if (encodedValue.startsWith("c")) {
return decodeString(cut).charAt(0);
- } else if (encodedValue.endsWith("s")) {
+ } else if (encodedValue.startsWith("s")) {
return Short.parseShort(cut);
- } else if (encodedValue.endsWith("L")) {
+ } else if (encodedValue.startsWith("l")) {
return Long.parseLong(cut);
- } else if (encodedValue.endsWith("F")) {
+ } else if (encodedValue.startsWith("f")) {
return Float.parseFloat(cut);
- } else if (encodedValue.endsWith("d")) {
+ } else if (encodedValue.startsWith("d")) {
return Double.parseDouble(cut);
- } else if (encodedValue.endsWith(";")) {
- return decodeEnum(encodedValue);
+ } else if (encodedValue.startsWith("i")) {
+ return Integer.parseInt(cut);
+ } else if (encodedValue.startsWith("E:")) {
+ cut = cut.substring(1);
+ return decodeEnum(cut);
} else {
- return Integer.parseInt(encodedValue);
+ throw new IOException("Unrecognized value: " + encodedValue);
}
} catch (Exception e) {
if (e instanceof IOException) {
contentToSend = false;
}
- if (in.next()) {
+ if (in.next() && !in.eof()) {
// TODO: could be possible to check for non-crypt and only
// do it for crypt
InputStream read = new ReplaceInputStream(in.open(), //
}
/**
- * Check if this stream is totally spent (no more data to read or to
+ * Check if this stream is spent (no more data to read or to
* process).
*
* @return TRUE if it is
* It can only be called when the "current" stream is spent (i.e., you must
* first process the stream until it is spent).
* <p>
- * We consider that when the under-laying {@link InputStream} is also spent,
- * we cannot have a next sub-stream (it will thus return FALSE).
- * <p>
* {@link IOException}s can happen when we have no data available in the
* buffer; in that case, we fetch more data to know if we can have a next
* sub-stream or not.
* <p>
* This is can be a blocking call when data need to be fetched.
*
- * @return TRUE if we unblocked the next sub-stream, FALSE if not
+ * @return TRUE if we unblocked the next sub-stream, FALSE if not (i.e.,
+ * FALSE when there are no more sub-streams to fetch)
*
* @throws IOException
* in case of I/O error or if the stream is closed
* process).
* <p>
* Note: when the stream is divided into sub-streams, each sub-stream will
- * report it is eof when emptied.
+ * report its own eof when spent.
*
* @return TRUE if it is
*
return false;
}
- /**
- * We have more data available in the buffer or we can fetch more.
- *
- * @return TRUE if it is the case, FALSE if not
- */
@Override
protected boolean hasMoreData() {
return started && super.hasMoreData();
* TRUE for {@link NextableInputStream#nextAll()}, FALSE for
* {@link NextableInputStream#next()}
*
- * @return TRUE if we unblocked the next sub-stream, FALSE if not
+ * @return TRUE if we unblocked the next sub-stream, FALSE if not (i.e.,
+ * FALSE when there are no more sub-streams to fetch)
*
* @throws IOException
* in case of I/O error or if the stream is closed
if (all) {
step = null;
}
+
+ return true;
}
- if (step != null && !hasMoreData() && stopped) {
+ // If started, must be stopped an no more data to continue
+ // i.e., sub-stream must be spent
+ if (!stopped || hasMoreData()) {
+ return false;
+ }
+
+ if (step != null) {
stop = step.getResumeLen();
start += step.getResumeSkip();
eof = step.getResumeEof();
}
checkBuffer(false);
- }
- // consider that if EOF, there is no next
- if (start >= stop) {
- // Make sure, block if necessary
- preRead();
-
- return hasMoreData();
+ return true;
}
- return true;
+ return false;
+
+ // // consider that if EOF, there is no next
+ // if (start >= stop) {
+ // // Make sure, block if necessary
+ // preRead();
+ //
+ // return hasMoreData();
+ // }
+ //
+ // return true;
}
/**
private List<TestLauncher> series;
private List<TestCase> tests;
+ private TestLauncher parent;
+
private int columns;
private String okString;
private String koString;
protected int total;
private int currentSeries = 0;
+ private boolean details = false;
/**
* Create a new {@link TestLauncher} with default parameters.
cont = true;
}
+ /**
+ * Display the details of the errors
+ *
+ * @return TRUE to display them, false to simply mark the test as failed
+ */
+ public boolean isDetails() {
+ if (parent != null) {
+ return parent.isDetails();
+ }
+
+ return details;
+ }
+
+ /**
+ * Display the details of the errors
+ *
+ * @param details
+ * TRUE to display them, false to simply mark the test as failed
+ */
+ public void setDetails(boolean details) {
+ if (parent != null) {
+ parent.setDetails(details);
+ }
+
+ this.details = details;
+ }
+
/**
* Called before actually starting the tests themselves.
*
protected void addSeries(TestLauncher series) {
this.series.add(series);
+ series.parent = this;
}
/**
private void print(int depth, Throwable error) {
if (error != null) {
System.out.println(" " + koString);
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- error.printStackTrace(pw);
- String lines = sw.toString();
- for (String line : lines.split("\n")) {
- System.out.println(prefix(depth, false) + "\t\t" + line);
+ if (isDetails()) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ error.printStackTrace(pw);
+ String lines = sw.toString();
+ for (String line : lines.split("\n")) {
+ System.out.println(prefix(depth, false) + "\t\t" + line);
+ }
}
} else {
System.out.println(" " + okString);
byte[] expected = new byte[] { 42, 12, 0, 127 };
NextableInputStream in = new NextableInputStream(
new ByteArrayInputStream(expected), null);
- in.next();
- byte[] actual = IOUtils.toByteArray(in);
-
- assertEquals(
- "The resulting array has not the same number of items",
- expected.length, actual.length);
- for (int i = 0; i < expected.length; i++) {
- assertEquals("Item " + i + " (0-based) is not the same",
- expected[i], actual[i]);
- }
+ checkNext(this, "READ", in, expected);
}
});
super(title + " " + skey + sbridge, args);
- addTest(new TestCase("Simple connection " + skey) {
- @Override
- public void test() throws Exception {
- final String[] rec = new String[1];
-
- ServerString server = new ServerString(this.getName(), 0, key) {
- @Override
- protected String onRequest(
- ConnectActionServerString action, String data)
- throws Exception {
- return null;
- }
-
- @Override
- protected void onError(Exception e) {
- }
- };
-
- int port = server.getPort();
- assertEquals("A port should have been assigned", true, port > 0);
-
- server.start();
-
- ServerBridge br = null;
- if (bridge) {
- br = new ServerBridge(0, key, "", port, key);
- br.setTraceHandler(null);
-
- port = br.getPort();
- assertEquals(
- "A port should have been assigned to the bridge",
- true, port > 0);
-
- br.start();
- }
-
- try {
- try {
- new ConnectActionClientObject(null, port, key) {
- @Override
- public void action() throws Exception {
- rec[0] = "ok";
- }
- }.connect();
- } finally {
- server.stop();
- }
- } finally {
- if (br != null) {
- br.stop();
- }
- }
-
- assertNotNull("The client action was not run", rec[0]);
- assertEquals("ok", rec[0]);
- }
- });
+ // addTest(new TestCase("Simple connection " + skey) {
+ // @Override
+ // public void test() throws Exception {
+ // final String[] rec = new String[1];
+ //
+ // ServerString server = new ServerString(this.getName(), 0, key) {
+ // @Override
+ // protected String onRequest(
+ // ConnectActionServerString action, String data)
+ // throws Exception {
+ // return null;
+ // }
+ //
+ // @Override
+ // protected void onError(Exception e) {
+ // }
+ // };
+ //
+ // int port = server.getPort();
+ // assertEquals("A port should have been assigned", true, port > 0);
+ //
+ // server.start();
+ //
+ // ServerBridge br = null;
+ // if (bridge) {
+ // br = new ServerBridge(0, key, "", port, key);
+ // br.setTraceHandler(null);
+ //
+ // port = br.getPort();
+ // assertEquals(
+ // "A port should have been assigned to the bridge",
+ // true, port > 0);
+ //
+ // br.start();
+ // }
+ //
+ // try {
+ // try {
+ // new ConnectActionClientObject(null, port, key) {
+ // @Override
+ // public void action() throws Exception {
+ // rec[0] = "ok";
+ // }
+ // }.connect();
+ // } finally {
+ // server.stop();
+ // }
+ // } finally {
+ // if (br != null) {
+ // br.stop();
+ // }
+ // }
+ //
+ // assertNotNull("The client action was not run", rec[0]);
+ // assertEquals("ok", rec[0]);
+ // }
+ // });
addTest(new TestCase("Simple exchange " + skey) {
final String[] sent = new String[1];
encodeRecodeTest(this, data);
}
});
+
addTest(new TestCase() {
@SuppressWarnings("unused")
private TestCase me = setName("Anonymous inner class");
public Test(String[] args) {
super("Nikiroo-utils", args);
+ // setDetails(true);
+
addSeries(new ProgressTest(args));
addSeries(new BundleTest(args));
addSeries(new IOUtilsTest(args));