From 2adb6143a559cd7221b49f421aab69c82b554cf2 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Tue, 16 Jul 2019 14:11:23 +0200 Subject: [PATCH] added binary serialization format --- build.sbt | 15 +- .../worth/buffer/LookAheadInputStream.java | 4 +- .../worth/serialization/ValueDumper.java | 10 +- .../worth/serialization/ValueParser.java | 38 ++-- .../serialization/binary/BinaryMarker.java | 22 +++ .../serialization/binary/JBONDumper.java | 175 ++++++++++++++++++ .../serialization/binary/JBONParser.java | 124 +++++++++++++ .../worth/serialization/json/JSONDumper.java | 16 +- .../worth/serialization/json/JSONParser.java | 14 +- .../java/org/oggio88/worth/utils/Leb128.java | 77 ++++++++ .../org/oggio88/worth/utils/ValueWalker.java | 61 ++++++ .../org/oggio88/worth/utils/WorthUtils.java | 129 +++++++++++-- .../org/oggio88/worth/value/ArrayValue.java | 9 +- .../org/oggio88/worth/value/ObjectValue.java | 49 +++-- .../java/org/oggio88/worth/xface/Value.java | 5 + .../oggio88/worth/antlr/JSONListenerImpl.java | 2 +- .../org/oggio88/worth/antlr/ParseTest.java | 7 +- .../worth/serialization/binary/JBONTest.java | 92 +++++++++ .../worth/serialization/json/JSONTest.java | 7 +- .../serialization/json/PerformanceTest.java | 42 ++--- .../org/oggio88/worth/utils/Leb128Test.java | 60 ++++++ .../oggio88/worth/utils/ValueWalkerTest.java | 29 +++ 22 files changed, 875 insertions(+), 112 deletions(-) create mode 100644 src/main/java/org/oggio88/worth/serialization/binary/BinaryMarker.java create mode 100644 src/main/java/org/oggio88/worth/serialization/binary/JBONDumper.java create mode 100644 src/main/java/org/oggio88/worth/serialization/binary/JBONParser.java create mode 100644 src/main/java/org/oggio88/worth/utils/Leb128.java create mode 100644 src/main/java/org/oggio88/worth/utils/ValueWalker.java create mode 100644 src/test/java/org/oggio88/worth/serialization/binary/JBONTest.java create mode 100644 src/test/java/org/oggio88/worth/utils/Leb128Test.java create mode 100644 src/test/java/org/oggio88/worth/utils/ValueWalkerTest.java diff --git a/build.sbt b/build.sbt index 8fc8d00..b7945f9 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,6 @@ organization := "org.oggio88" version := "1.0" resolvers += Resolver.mavenLocal -scalaVersion := "2.12.6" scalacOptions ++= Seq( "-unchecked", @@ -17,16 +16,16 @@ scalacOptions ++= Seq( git.useGitDescribe := true fork := true -//javaOptions in Test += "-Dorg.oggio88.javason.value.ObjectValue.listBasedImplementation=true" -javaOptions in Test += "-Xmx6G" +//javaOptions in Test += "-Dorg.oggio88.worth.value.ObjectValue.listBasedImplementation=true" +//javaOptions in Test += "-Xmx14G" //scalafmtOnCompile := true libraryDependencies += "org.projectlombok" % "lombok" % "1.18.2" -libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" -libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.6" % "test" +libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test +libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.6" % Test -libraryDependencies += "org.antlr" % "antlr4" % "4.7.1" % "test" -libraryDependencies += "org.antlr" % "antlr4-runtime" % "4.7.1" % "test" -libraryDependencies += "org.tukaani" % "xz" % "1.8" % "test" +libraryDependencies += "org.antlr" % "antlr4" % "4.7.1" % Test +libraryDependencies += "org.antlr" % "antlr4-runtime" % "4.7.1" % Test +libraryDependencies += "org.tukaani" % "xz" % "1.8" % Test artifactName := { (sv: ScalaVersion, module: ModuleID, artifact: Artifact) => artifact.name + "-" + module.revision + "." + artifact.extension diff --git a/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java b/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java index 57372b4..241d0a3 100644 --- a/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java +++ b/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java @@ -9,7 +9,7 @@ public class LookAheadInputStream extends InputStream { private final InputStream stream; private int currentByte; - LookAheadInputStream(InputStream stream) { + public LookAheadInputStream(InputStream stream) { this.stream = stream; } @@ -21,7 +21,7 @@ public class LookAheadInputStream extends InputStream { return result; } - public int getCurrentByte(){ + public int getCurrentByte() { return currentByte; } } diff --git a/src/main/java/org/oggio88/worth/serialization/ValueDumper.java b/src/main/java/org/oggio88/worth/serialization/ValueDumper.java index a2f7f63..e5519a8 100644 --- a/src/main/java/org/oggio88/worth/serialization/ValueDumper.java +++ b/src/main/java/org/oggio88/worth/serialization/ValueDumper.java @@ -11,9 +11,9 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; +import java.util.ArrayDeque; import java.util.Iterator; import java.util.Map; -import java.util.Stack; public abstract class ValueDumper implements Dumper { @@ -62,10 +62,10 @@ public abstract class ValueDumper implements Dumper { } - protected Stack stack; + protected ArrayDeque stack; protected ValueDumper() { - stack = new Stack<>(); + stack = new ArrayDeque<>(); } @Override @@ -83,11 +83,11 @@ public abstract class ValueDumper implements Dumper { dump(value, new OutputStreamWriter(stream, encoding)); } - protected abstract void beginObject(); + protected abstract void beginObject(int size); protected abstract void endObject(); - protected abstract void beginArray(); + protected abstract void beginArray(int size); protected abstract void endArray(); diff --git a/src/main/java/org/oggio88/worth/serialization/ValueParser.java b/src/main/java/org/oggio88/worth/serialization/ValueParser.java index 15e198c..aa0e7ad 100644 --- a/src/main/java/org/oggio88/worth/serialization/ValueParser.java +++ b/src/main/java/org/oggio88/worth/serialization/ValueParser.java @@ -3,12 +3,7 @@ package org.oggio88.worth.serialization; import lombok.RequiredArgsConstructor; import org.oggio88.worth.exception.NotImplementedException; import org.oggio88.worth.utils.WorthUtils; -import org.oggio88.worth.value.ArrayValue; -import org.oggio88.worth.value.BooleanValue; -import org.oggio88.worth.value.FloatValue; -import org.oggio88.worth.value.IntegerValue; -import org.oggio88.worth.value.ObjectValue; -import org.oggio88.worth.value.StringValue; +import org.oggio88.worth.value.*; import org.oggio88.worth.xface.Parser; import org.oggio88.worth.xface.Value; @@ -16,18 +11,22 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; -import java.util.Stack; +import java.util.ArrayDeque; public class ValueParser implements Parser { @RequiredArgsConstructor protected static class StackLevel { public final Value value; + public final long expectedSize; } protected static class ArrayStackLevel extends StackLevel { public ArrayStackLevel() { - super(new ArrayValue()); + super(new ArrayValue(), -1); + } + public ArrayStackLevel(long expectedSize) { + super(new ArrayValue(), expectedSize); } } @@ -35,14 +34,18 @@ public class ValueParser implements Parser { public String currentKey; public ObjectStackLevel() { - super(ObjectValue.newInstance()); + super(ObjectValue.newInstance(), -1); + } + + public ObjectStackLevel(long expectedSize) { + super(ObjectValue.newInstance(), expectedSize); } } - protected Stack stack; + protected ArrayDeque stack; private void add2Last(Value value) { - StackLevel last = stack.lastElement(); + StackLevel last = stack.getFirst(); ArrayStackLevel asl; ObjectStackLevel osl; if ((asl = WorthUtils.dynamicCast(last, ArrayStackLevel.class)) != null) @@ -54,7 +57,7 @@ public class ValueParser implements Parser { } protected ValueParser() { - stack = new Stack<>(); + stack = new ArrayDeque<>(); stack.push(new ArrayStackLevel()); } @@ -77,6 +80,11 @@ public class ValueParser implements Parser { stack.push(new ObjectStackLevel()); } + protected void beginObject(long size) { + stack.push(new ObjectStackLevel(size)); + } + + protected void endObject() { add2Last(stack.pop().value); } @@ -85,12 +93,16 @@ public class ValueParser implements Parser { stack.push(new ArrayStackLevel()); } + protected void beginArray(long size) { + stack.push(new ArrayStackLevel(size)); + } + protected void endArray() { add2Last(stack.pop().value); } protected void objectKey(String key) { - ObjectStackLevel osl = (ObjectStackLevel) stack.lastElement(); + ObjectStackLevel osl = (ObjectStackLevel) stack.getFirst(); osl.currentKey = key; } diff --git a/src/main/java/org/oggio88/worth/serialization/binary/BinaryMarker.java b/src/main/java/org/oggio88/worth/serialization/binary/BinaryMarker.java new file mode 100644 index 0000000..95ae56d --- /dev/null +++ b/src/main/java/org/oggio88/worth/serialization/binary/BinaryMarker.java @@ -0,0 +1,22 @@ +package org.oggio88.worth.serialization.binary; + +public enum BinaryMarker { + Float(0x0), + Int(0x1), + Null(0x2), + True(0x3), + False(0x4), + Reference(0x5), + Id(0x6), + EmptyString(0x0d), + LargeString(0x5d), + EmptyObject(0x5e), + LargeObject(0xae), + EmptyArray(0xaf), + LargeArray(0xff); + + public byte value; + BinaryMarker(int b) { + value = (byte) b; + } +} diff --git a/src/main/java/org/oggio88/worth/serialization/binary/JBONDumper.java b/src/main/java/org/oggio88/worth/serialization/binary/JBONDumper.java new file mode 100644 index 0000000..88dbcb6 --- /dev/null +++ b/src/main/java/org/oggio88/worth/serialization/binary/JBONDumper.java @@ -0,0 +1,175 @@ +package org.oggio88.worth.serialization.binary; + +import lombok.SneakyThrows; +import org.oggio88.worth.exception.NotImplementedException; +import org.oggio88.worth.serialization.ValueDumper; +import org.oggio88.worth.utils.Leb128; +import org.oggio88.worth.value.ArrayValue; +import org.oggio88.worth.value.ObjectValue; +import org.oggio88.worth.xface.Dumper; +import org.oggio88.worth.xface.Value; + +import java.io.OutputStream; +import java.io.Writer; +import java.util.Map; +import java.util.function.Consumer; + +import static org.oggio88.worth.utils.WorthUtils.dynamicCast; + +public class JBONDumper extends ValueDumper { + + public static Dumper newInstance() { + return new JBONDumper(); + } + + protected OutputStream os; + + @Override + public void dump(Value value, Writer writer) { + throw new NotImplementedException(null); + } + + @Override + @SneakyThrows + public void dump(Value value, OutputStream outputStream) { + this.os = outputStream; + final Consumer handle_value = (v) -> { + switch (v.type()) { + case NULL: + nullValue(); + break; + case BOOLEAN: + booleanValue(v.asBoolean()); + break; + case INTEGER: + integerValue(v.asInteger()); + break; + case DOUBLE: + floatValue(v.asFloat()); + break; + case STRING: + stringValue(v.asString()); + break; + case ARRAY: + ArrayValue arrayValue = dynamicCast(v, ArrayValue.class); + stack.push(new ArrayStackLevel(arrayValue)); + beginArray(arrayValue.size()); + break; + case OBJECT: + ObjectValue objectValue = dynamicCast(v, ObjectValue.class); + stack.push(new ObjectStackLevel(objectValue)); + beginObject(objectValue.size()); + break; + } + }; + + handle_value.accept(value); + while (stack.size() > 0) { + StackLevel last = stack.getFirst(); + ArrayStackLevel arrayStackLevel; + ObjectStackLevel objectStackLevel; + if ((arrayStackLevel = dynamicCast(last, ArrayStackLevel.class)) != null) { + if (arrayStackLevel.hasNext()) { + handle_value.accept(arrayStackLevel.next()); + } else { + endArray(); + stack.pop(); + } + } else if ((objectStackLevel = dynamicCast(last, ObjectStackLevel.class)) != null) { + if (objectStackLevel.hasNext()) { + Map.Entry entry = objectStackLevel.next(); + objectKey(entry.getKey()); + handle_value.accept(entry.getValue()); + } else { + endObject(); + stack.pop(); + } + } + } + this.os.flush(); + this.os = null; + } + + @Override + @SneakyThrows + protected void beginObject(int size) { + if(size == 0) { + os.write(BinaryMarker.EmptyObject.value); + } else { + os.write(BinaryMarker.LargeObject.value); + Leb128.encode(os, size); + } + } + + @Override + @SneakyThrows + protected void endObject() { + } + + @Override + @SneakyThrows + protected void beginArray(int size) { + if(size == 0) { + os.write(BinaryMarker.EmptyArray.value); + } else { + os.write(BinaryMarker.LargeArray.value); + Leb128.encode(os, size); + } + } + + @Override + @SneakyThrows + protected void endArray() { + } + + @Override + @SneakyThrows + protected void objectKey(String key) { + byte[] bytes = key.getBytes(); + Leb128.encode(os, bytes.length); + os.write(key.getBytes()); + } + + @Override + @SneakyThrows + protected void stringValue(String value) { + if(value.isEmpty()) { + os.write(BinaryMarker.EmptyString.value); + } else { + os.write(BinaryMarker.LargeString.value); + byte[] bytes = value.getBytes(); + Leb128.encode(os, bytes.length); + os.write(value.getBytes()); + } + } + + @Override + @SneakyThrows + protected void integerValue(long value) { + os.write(BinaryMarker.Int.value); + Leb128.encode(os, value); + } + + @Override + @SneakyThrows + protected void floatValue(double value) { + os.write(BinaryMarker.Float.value); + Leb128.encode(os, value); + } + + @Override + @SneakyThrows + protected void booleanValue(boolean value) { + if(value) { + os.write(BinaryMarker.True.value); + } else { + os.write(BinaryMarker.False.value); + } + } + + @Override + @SneakyThrows + protected void nullValue() { + os.write(BinaryMarker.Null.value); + } +} diff --git a/src/main/java/org/oggio88/worth/serialization/binary/JBONParser.java b/src/main/java/org/oggio88/worth/serialization/binary/JBONParser.java new file mode 100644 index 0000000..4147f46 --- /dev/null +++ b/src/main/java/org/oggio88/worth/serialization/binary/JBONParser.java @@ -0,0 +1,124 @@ +package org.oggio88.worth.serialization.binary; + +import lombok.SneakyThrows; +import org.oggio88.worth.buffer.LookAheadInputStream; +import org.oggio88.worth.exception.ParseException; +import org.oggio88.worth.serialization.ValueParser; +import org.oggio88.worth.utils.Leb128; +import org.oggio88.worth.utils.WorthUtils; +import org.oggio88.worth.xface.Parser; +import org.oggio88.worth.xface.Value; + +import java.io.InputStream; +import java.util.function.Function; + +public class JBONParser extends ValueParser { + + private int cursor = 0; + + + private T error(Function constructor, String fmt, Object... args) { + return constructor.apply( + String.format("Error at position %d: %s", + cursor, String.format(fmt, args))); + } + + @Override + @SneakyThrows + public Value parse(InputStream is) { + final LookAheadInputStream stream = new LookAheadInputStream(is) { + @Override + public int read() { + int result = super.read(); + ++cursor; + return result; + } + }; + stream.read(); + + try { + Leb128.Leb128Decoder decoder = new Leb128.Leb128Decoder(stream); + ObjectStackLevel osl; + while (true) { + if ((osl = WorthUtils.dynamicCast(stack.getFirst(), ObjectStackLevel.class)) != null && osl.currentKey == null) { + int size = (int) decoder.decode(); + byte[] buffer = new byte[size]; + stream.read(buffer); + String text = new String(buffer); + objectKey(text); + } + byte b; + { + int c = stream.read(); + if(c == -1) { + break; + } + b = (byte) c; + } + if(b == BinaryMarker.Null.value) { + nullValue(); + } else if(b == BinaryMarker.True.value) { + booleanValue(true); + } else if(b == BinaryMarker.False.value) { + booleanValue(false); + } else if(b == BinaryMarker.Int.value) { + integerValue(decoder.decode()); + } else if(b == BinaryMarker.Float.value) { + floatValue(decoder.decodeDouble()); + } else if(b == BinaryMarker.EmptyString.value) { + stringValue(""); + } else if(b == BinaryMarker.LargeString.value) { + byte[] buffer = new byte[(int) decoder.decode()]; + stream.read(buffer); + String text = new String(buffer); + stringValue(text); + } else if(b == BinaryMarker.EmptyArray.value) { + beginArray(0); + } else if(b == BinaryMarker.LargeArray.value) { + long size = decoder.decode(); + beginArray(size); + } else if(b == BinaryMarker.EmptyObject.value) { + beginObject(0); + } else if(b == BinaryMarker.LargeObject.value) { + long size = decoder.decode(); + beginObject(size); + } else { + throw new ParseException(String.format("Illegal byte at position %d: 0x%02x", cursor, b)); + } + while(stack.size() > 0) { + if ((osl = WorthUtils.dynamicCast(stack.getFirst(), ObjectStackLevel.class)) != null + && osl.value.size() == osl.expectedSize) { + endObject(); + continue; + } + ArrayStackLevel asl; + if((asl = WorthUtils.dynamicCast(stack.getFirst(), ArrayStackLevel.class)) != null + && asl.value.size() == asl.expectedSize) { + endArray(); + continue; + } + break; + } + } + if (stack.size() > 1) { + ValueParser.StackLevel last = stack.getFirst(); + String type; + if(last instanceof ArrayStackLevel) { + type = "array"; + } else { + type = "object"; + } + throw error(ParseException::new, "Unfinished %s", type); + } + return WorthUtils.dynamicCast(stack.getFirst(), ArrayStackLevel.class).value.get(0); + } catch (NumberFormatException | NegativeArraySizeException e) { + throw error(ParseException::new, e.getMessage()); + } finally { + stack.clear(); + } + } + + public static Parser newInstance() { + return new JBONParser(); + } +} \ No newline at end of file diff --git a/src/main/java/org/oggio88/worth/serialization/json/JSONDumper.java b/src/main/java/org/oggio88/worth/serialization/json/JSONDumper.java index 9cf83be..5c3d9b9 100644 --- a/src/main/java/org/oggio88/worth/serialization/json/JSONDumper.java +++ b/src/main/java/org/oggio88/worth/serialization/json/JSONDumper.java @@ -21,7 +21,7 @@ public class JSONDumper extends ValueDumper { return new JSONDumper(); } - protected Writer writer; + private Writer writer; private String escapeString(String value){ StringBuilder sb = new StringBuilder(); @@ -81,19 +81,21 @@ public class JSONDumper extends ValueDumper { stringValue(v.asString()); break; case ARRAY: - stack.push(new ArrayStackLevel(dynamicCast(v, ArrayValue.class))); - beginArray(); + ArrayValue arrayValue = dynamicCast(v, ArrayValue.class); + stack.push(new ArrayStackLevel(arrayValue)); + beginArray(arrayValue.size()); break; case OBJECT: + ObjectValue objectValue = dynamicCast(v, ObjectValue.class); stack.push(new ObjectStackLevel(dynamicCast(v, ObjectValue.class))); - beginObject(); + beginObject(objectValue.size()); break; } }; handle_value.accept(value); while (stack.size() > 0) { - StackLevel last = stack.lastElement(); + StackLevel last = stack.getFirst(); ArrayStackLevel arrayStackLevel; ObjectStackLevel objectStackLevel; if ((arrayStackLevel = dynamicCast(last, ArrayStackLevel.class)) != null) { @@ -127,7 +129,7 @@ public class JSONDumper extends ValueDumper { @Override @SneakyThrows - protected void beginObject() { + protected void beginObject(int size) { this.writer.write("{"); } @@ -139,7 +141,7 @@ public class JSONDumper extends ValueDumper { @Override @SneakyThrows - protected void beginArray() { + protected void beginArray(int size) { this.writer.write("["); } diff --git a/src/main/java/org/oggio88/worth/serialization/json/JSONParser.java b/src/main/java/org/oggio88/worth/serialization/json/JSONParser.java index d62f9e6..eef3a7a 100644 --- a/src/main/java/org/oggio88/worth/serialization/json/JSONParser.java +++ b/src/main/java/org/oggio88/worth/serialization/json/JSONParser.java @@ -69,7 +69,7 @@ public class JSONParser extends ValueParser { } } - private final String readString(LookAheadTextInputStream stream) { + private String readString(LookAheadTextInputStream stream) { StringBuilder sb = new StringBuilder(); boolean escape = false; boolean start = false; @@ -116,7 +116,7 @@ public class JSONParser extends ValueParser { return sb.toString(); } - private final void consumeExpected(LookAheadTextInputStream stream, String expected, String errorMessage) { + private void consumeExpected(LookAheadTextInputStream stream, String expected, String errorMessage) { int i = 0; while (true) { int c = stream.getCurrentByte(); @@ -185,11 +185,9 @@ public class JSONParser extends ValueParser { throw error(ParseException::new, nfe.getMessage()); } } else if (c == '\"') { - if (currentLine == 125) - System.out.print(""); String text = readString(stream); ObjectStackLevel osl; - if ((osl = WorthUtils.dynamicCast(stack.lastElement(), ObjectStackLevel.class)) != null && osl.currentKey == null) { + if ((osl = WorthUtils.dynamicCast(stack.getFirst(), ObjectStackLevel.class)) != null && osl.currentKey == null) { objectKey(text); } else { stringValue(text); @@ -208,16 +206,16 @@ public class JSONParser extends ValueParser { } if (stack.size() > 1) { char c; - if (stack.lastElement() instanceof ArrayStackLevel) { + if (stack.getFirst() instanceof ArrayStackLevel) { c = ']'; - } else if (stack.lastElement() instanceof ObjectStackLevel) { + } else if (stack.getFirst() instanceof ObjectStackLevel) { c = '}'; } else { throw new NotImplementedException("This should never happen"); } throw error(ParseException::new, "Missing '%c' token", c); } - return WorthUtils.dynamicCast(stack.lastElement(), ArrayStackLevel.class).value.get(0); + return WorthUtils.dynamicCast(stack.getFirst(), ArrayStackLevel.class).value.get(0); } catch (NumberFormatException e) { throw error(ParseException::new, e.getMessage()); } finally { diff --git a/src/main/java/org/oggio88/worth/utils/Leb128.java b/src/main/java/org/oggio88/worth/utils/Leb128.java new file mode 100644 index 0000000..c6378b2 --- /dev/null +++ b/src/main/java/org/oggio88/worth/utils/Leb128.java @@ -0,0 +1,77 @@ +package org.oggio88.worth.utils; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; + +import java.io.InputStream; +import java.io.OutputStream; + +public class Leb128 { + + public static long reverse(long n) { + long res = 0; + for(int i = 0; i < 8; i++) { + long b = (n & (0xFFL << (i * 8))) >>> (i * 8); + res |= b << ((7 - i) * 8); + } + return res; + } + + public static int encode(OutputStream os, double input) { + return encode(os, reverse(Double.doubleToLongBits(input))); + } + + @SneakyThrows + public static int encode(OutputStream os, long input) { + int bytes_written = 0; + long number = input >= 0 ? (input << 1) : (-(input + 1)) << 1 | 1; + while(number > 127) { + os.write((int) (number & 127) | 128); + bytes_written++; + number >>= 7; + } + os.write((int) number); + return ++bytes_written; + } + + @RequiredArgsConstructor + public static class Leb128Decoder { + @Getter + private int bytesRead = 0; + + private final InputStream is; + + public byte decodeByte() { + return (byte) decode(); + } + + public short decodeShort() { + return (short) decode(); + } + + public int decodeInt() { + return (int) decode(); + } + + public double decodeDouble() { + return Double.longBitsToDouble(reverse(decode())); + } + + @SneakyThrows + public long decode() { + long res = 0; + for(int i = 0; i < (8 * 8 + 6) / 7; i++) { + int c = is.read(); + bytesRead++; + if(c < 0) { + throw new IllegalArgumentException("Unexpected end of file"); + } + byte b = (byte) c; + res |= ((long)(b & 127)) << (i * 7); + if(b >= 0) break; + } + return (res & 1) != 0 ? - (res >> 1) - 1 : (res >> 1); + } + } +} diff --git a/src/main/java/org/oggio88/worth/utils/ValueWalker.java b/src/main/java/org/oggio88/worth/utils/ValueWalker.java new file mode 100644 index 0000000..ef9843b --- /dev/null +++ b/src/main/java/org/oggio88/worth/utils/ValueWalker.java @@ -0,0 +1,61 @@ +package org.oggio88.worth.utils; + +import org.oggio88.worth.xface.Value; + +import java.util.Optional; +import java.util.function.Function; + +public class ValueWalker { + + private Value parent; + + public ValueWalker(Value root) { + parent = root; + } + + public ValueWalker get(String key) { + if(parent.type() == Value.Type.OBJECT) { + parent = parent.get(key); + } else { + parent = Value.Null; + } + return this; + } + + public ValueWalker get(int index) { + if(parent.type() == Value.Type.ARRAY) { + parent = parent.get(index); + } else { + parent = Value.Null; + } + return this; + } + + public Value get() { + return parent; + } + + public Optional map(Function callback) { + if(isPresent()) { + return Optional.of(callback.apply(parent)); + } else { + return Optional.empty(); + } + } + + public Optional flatMap(Function> callback) { + if(isPresent()) { + return callback.apply(parent); + } else { + return Optional.empty(); + } + } + + public boolean isPresent() { + return !isEmpty(); + } + + public boolean isEmpty() { + return parent.type() == Value.Type.NULL; + } +} diff --git a/src/main/java/org/oggio88/worth/utils/WorthUtils.java b/src/main/java/org/oggio88/worth/utils/WorthUtils.java index 555a12a..fead2ce 100644 --- a/src/main/java/org/oggio88/worth/utils/WorthUtils.java +++ b/src/main/java/org/oggio88/worth/utils/WorthUtils.java @@ -1,19 +1,20 @@ package org.oggio88.worth.utils; import lombok.SneakyThrows; -import org.oggio88.worth.buffer.CircularBuffer; -import org.oggio88.worth.exception.ParseException; +import org.oggio88.worth.xface.Value; -import java.io.InputStream; +import java.io.*; +import java.lang.reflect.Constructor; +import java.util.Optional; import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; public class WorthUtils { - @SneakyThrows - public static T uncheckCall(final Callable callable) { - return callable.call(); - } - public static T dynamicCast(final Object o, final Class cls) { if (cls.isInstance(o)) { return (T) o; @@ -22,8 +23,114 @@ public class WorthUtils { } } - public static boolean equalsNullSafe(Object o1, Object o2) { - if (o1 == null) return o2 == null; - else return o1.equals(o2); + public static Stream iterable2stream(Iterable iterable) { + return StreamSupport.stream(iterable.spliterator(), false); + } + + public static void writeObject2File(String fileName, Object o) { + writeObject2File(new File(fileName), o); + } + + @SneakyThrows + public static void writeObject2File(File file, Object o) { + try (Writer writer = new OutputStreamWriter(new FileOutputStream(file.getPath()))) { + writer.write(o.toString()); + } + } + + @SneakyThrows + public static String readFile2String(File file) { + StringBuilder builder = new StringBuilder(); + try (Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file.getPath())))) { + char[] buffer = new char[1024]; + while (true) { + int read = reader.read(buffer); + builder.append(buffer, 0, read); + if (read < buffer.length) break; + } + } + return builder.toString(); + } + + @SneakyThrows + public static String readResource2String(String classpath) { + StringBuilder sb = new StringBuilder(); + try (Reader reader = new InputStreamReader(WorthUtils.class.getResourceAsStream(classpath))) { + char[] buffer = new char[1024]; + while (true) { + int read = reader.read(buffer); + sb.append(buffer, 0, read); + if (read < buffer.length) break; + } + } + return sb.toString(); + } + + @SneakyThrows + public static T newThrowable(Class cls, String format, Object... args) { + Constructor constructor = cls.getConstructor(String.class); + return constructor.newInstance(String.format(format, args)); + } + + @SneakyThrows + public static T newThrowable(Class cls, Throwable throwable, String format, Object... args) { + Constructor constructor = cls.getConstructor(String.class, Throwable.class); + return constructor.newInstance(String.format(format, args), throwable); + } + + @SneakyThrows + public static void raise(Class cls, Throwable throwable, String format, Object... args) { + throw newThrowable(cls, throwable, format, args); + } + + @SneakyThrows + public static void raise(Class cls, String format, Object... args) { + throw newThrowable(cls, format, args); + } + + public static Value getOrNull(Value root, String... keys) { + Value result = root; + for (String key : keys) { + result = result.get(key); + if (result == null) { + result = Value.Null; + break; + } + } + return result; + } + + public static T getOrNull(Value root, Function callback, String... keys) { + Value result = getOrNull(root, keys); + return result.type() == Value.Type.NULL ? null : callback.apply(result); + } + + @SneakyThrows + public static T getOrThrow(Value root, Function success, Supplier error, String... keys) { + Value result = getOrNull(root, keys); + if (result.type() == Value.Type.NULL) { + throw error.get(); + } else { + return success.apply(result); + } + } + + public static Predicate not(Predicate p) { + return p.negate(); + } + + public static Stream flatMap(Stream stream, + Function> mappingFunction) { + return stream.map(mappingFunction).filter(Optional::isPresent).map(Optional::get); + } + + public static Stream optional2Stream(Optional optional) { + return optional.map(Stream::of).orElse(Stream.empty()); + } + + public static void setSystemPropertyIfNotDefined(String key, String value) { + if(System.getProperty(key) == null) { + System.setProperty(key, value); + } } } diff --git a/src/main/java/org/oggio88/worth/value/ArrayValue.java b/src/main/java/org/oggio88/worth/value/ArrayValue.java index 424fc2d..3528ffc 100644 --- a/src/main/java/org/oggio88/worth/value/ArrayValue.java +++ b/src/main/java/org/oggio88/worth/value/ArrayValue.java @@ -6,6 +6,7 @@ import org.oggio88.worth.xface.Value; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Optional; @EqualsAndHashCode public class ArrayValue implements Value, Iterable { @@ -32,7 +33,12 @@ public class ArrayValue implements Value, Iterable { @Override public Value get(int index) { - return value.get(index); + int sz = size(); + if(index < sz) { + return value.get(Math.floorMod(index, sz)); + } else { + return Value.Null; + } } @Override @@ -63,6 +69,7 @@ public class ArrayValue implements Value, Iterable { return value.iterator(); } + @Override public int size() { return value.size(); } diff --git a/src/main/java/org/oggio88/worth/value/ObjectValue.java b/src/main/java/org/oggio88/worth/value/ObjectValue.java index 9aecc9e..1e293d7 100644 --- a/src/main/java/org/oggio88/worth/value/ObjectValue.java +++ b/src/main/java/org/oggio88/worth/value/ObjectValue.java @@ -6,19 +6,16 @@ import org.oggio88.worth.xface.Value; import java.util.*; -import static org.oggio88.worth.utils.WorthUtils.equalsNullSafe; - - public interface ObjectValue extends Value, Iterable> { boolean listBasedImplementation = Boolean.valueOf( - System.getProperty("org.oggio88.javason.value.ObjectValue.listBasedImplementation", "false")); + System.getProperty(ObjectValue.class.getName() + ".listBasedImplementation", "false")); boolean preserveKeyOrder = Boolean.valueOf( - System.getProperty("org.oggio88.javason.value.MapObjectValue.preserveKeyOrder", "false")); + System.getProperty(ObjectValue.class.getName() + ".preserveKeyOrder", "false")); static ObjectValue newInstance() { if (listBasedImplementation) { - return new MapObjectValue(); + return new ListObjectValue(); } else { return new MapObjectValue(); } @@ -63,7 +60,7 @@ class MapObjectValue implements ObjectValue { private final Map value; public MapObjectValue() { - this.value = ObjectValue.preserveKeyOrder ? new LinkedHashMap() : new HashMap(); + this.value = ObjectValue.preserveKeyOrder ? new LinkedHashMap<>() : new HashMap<>(); } public MapObjectValue(Map value) { @@ -72,25 +69,17 @@ class MapObjectValue implements ObjectValue { @Override public Map asObject() { - return value; + return Collections.unmodifiableMap(value); } @Override public Value get(String key) { - Value result = value.get(key); - if (result == null) { - result = Value.Null; - value.put(key, result); - } - return result; + return value.getOrDefault(key, Value.Null); } @Override public Value getOrDefault(String key, Value defaultValue) { - if (value.containsKey(key)) - return value.get(key); - else - return defaultValue; + return value.getOrDefault(key, defaultValue); } @Override @@ -118,13 +107,18 @@ class MapObjectValue implements ObjectValue { public Iterator> iterator() { return value.entrySet().iterator(); } + + @Override + public int size() { + return value.size(); + } } @NoArgsConstructor @EqualsAndHashCode class ListObjectValue implements ObjectValue { - private final List> value = new ArrayList(); + private final List> value = new ArrayList<>(); public ListObjectValue(Map map) { this.value.addAll(map.entrySet()); @@ -132,17 +126,17 @@ class ListObjectValue implements ObjectValue { @Override public Map asObject() { - Map result = preserveKeyOrder ? new LinkedHashMap() : new HashMap(); + Map result = preserveKeyOrder ? new LinkedHashMap<>() : new HashMap<>(); for (Map.Entry entry : value) { result.put(entry.getKey(), entry.getValue()); } - return result; + return Collections.unmodifiableMap(result); } @Override public Value get(String key) { for (Map.Entry entry : value) { - if(equalsNullSafe(entry.getKey(), key)) return entry.getValue(); + if(Objects.equals(entry.getKey(), key)) return entry.getValue(); } return Value.Null; } @@ -150,7 +144,7 @@ class ListObjectValue implements ObjectValue { @Override public Value getOrDefault(String key, Value defaultValue) { for (Map.Entry entry : value) { - if(equalsNullSafe(entry.getKey(), key)) return entry.getValue(); + if(Objects.equals(entry.getKey(), key)) return entry.getValue(); } return defaultValue; } @@ -158,7 +152,7 @@ class ListObjectValue implements ObjectValue { @Override public Value getOrPut(String key, Value value2Put) { for (Map.Entry entry : value) { - if(equalsNullSafe(entry.getKey(), key)) return entry.getValue(); + if(Objects.equals(entry.getKey(), key)) return entry.getValue(); } put(key, value2Put); return value2Put; @@ -173,7 +167,7 @@ class ListObjectValue implements ObjectValue { @Override public boolean has(String key) { for (Map.Entry entry : value) { - if(equalsNullSafe(entry.getKey(), key)) return true; + if(Objects.equals(entry.getKey(), key)) return true; } return false; } @@ -182,4 +176,9 @@ class ListObjectValue implements ObjectValue { public Iterator> iterator() { return value.iterator(); } + + @Override + public int size() { + return value.size(); + } } diff --git a/src/main/java/org/oggio88/worth/xface/Value.java b/src/main/java/org/oggio88/worth/xface/Value.java index fd4cdbc..ba19bfd 100644 --- a/src/main/java/org/oggio88/worth/xface/Value.java +++ b/src/main/java/org/oggio88/worth/xface/Value.java @@ -5,6 +5,7 @@ import org.oggio88.worth.value.NullValue; import java.util.List; import java.util.Map; +import java.util.Optional; public interface Value { @@ -40,6 +41,10 @@ public interface Value { throw new TypeException("Not an object"); } + default int size() { + throw new TypeException("Neither an array nor an object"); + } + default void add(Value value) { throw new TypeException("Not an array"); } diff --git a/src/test/java/org/oggio88/worth/antlr/JSONListenerImpl.java b/src/test/java/org/oggio88/worth/antlr/JSONListenerImpl.java index 792f14f..c068dde 100644 --- a/src/test/java/org/oggio88/worth/antlr/JSONListenerImpl.java +++ b/src/test/java/org/oggio88/worth/antlr/JSONListenerImpl.java @@ -20,7 +20,7 @@ public class JSONListenerImpl extends ValueParser implements JSONListener { @Override public void exitJson(JSONParser.JsonContext ctx) { - result = stack.get(0).value; + result = stack.getFirst().value; stack.clear(); } diff --git a/src/test/java/org/oggio88/worth/antlr/ParseTest.java b/src/test/java/org/oggio88/worth/antlr/ParseTest.java index 9a6aed1..4cad6eb 100644 --- a/src/test/java/org/oggio88/worth/antlr/ParseTest.java +++ b/src/test/java/org/oggio88/worth/antlr/ParseTest.java @@ -1,20 +1,23 @@ package org.oggio88.worth.antlr; import lombok.SneakyThrows; -import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.junit.Test; import org.oggio88.worth.serialization.json.JSONDumper; import org.oggio88.worth.xface.Value; +import java.io.InputStreamReader; + public class ParseTest { @Test @SneakyThrows public void test(){ - ANTLRInputStream inputStream = new ANTLRInputStream(getClass().getResourceAsStream("/test.json")); + CodePointCharStream inputStream = CharStreams.fromReader(new InputStreamReader(getClass().getResourceAsStream("/test.json"))); JSONLexer lexer = new JSONLexer(inputStream); CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); JSONParser parser = new JSONParser(commonTokenStream); diff --git a/src/test/java/org/oggio88/worth/serialization/binary/JBONTest.java b/src/test/java/org/oggio88/worth/serialization/binary/JBONTest.java new file mode 100644 index 0000000..a5d1685 --- /dev/null +++ b/src/test/java/org/oggio88/worth/serialization/binary/JBONTest.java @@ -0,0 +1,92 @@ +package org.oggio88.worth.serialization.binary; + +import lombok.SneakyThrows; +import org.junit.Assert; +import org.junit.Test; +import org.oggio88.worth.buffer.LookAheadTextInputStream; +import org.oggio88.worth.serialization.binary.JBONDumper; +import org.oggio88.worth.serialization.binary.JBONParser; +import org.oggio88.worth.serialization.json.JSONParser; +import org.oggio88.worth.value.NullValue; +import org.oggio88.worth.value.ObjectValue; +import org.oggio88.worth.xface.Parser; +import org.oggio88.worth.xface.Value; + +import java.io.*; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class JBONTest { + + private String[] testFiles = new String[]{"/test.json", "/wordpress.json"}; + + private InputStream getTestSource(String filename) { + return getClass().getResourceAsStream(filename); + } + + @Test + @SneakyThrows + public void consistencyTest() { + System.setProperty(ObjectValue.class.getName() + ".preserveKeyOrder", "true"); + for (String testFile : testFiles) { + Value parsedValue; + try(InputStream is = getTestSource(testFile)) { + Parser parser = new JSONParser(); + parsedValue = parser.parse(is); + } + byte[] dumpedJBON; + try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + JBONDumper.newInstance().dump(parsedValue, baos); + dumpedJBON = baos.toByteArray(); + } + Value reParsedValue; + try(InputStream is = new ByteArrayInputStream(dumpedJBON)) { + Parser parser = new JBONParser(); + reParsedValue = parser.parse(is); + } + Assert.assertEquals(parsedValue, reParsedValue); + byte[] reDumpedJBON; + try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + JBONDumper.newInstance().dump(reParsedValue, baos); + reDumpedJBON = baos.toByteArray(); + } + Assert.assertArrayEquals(dumpedJBON, reDumpedJBON); + } + } + + @Test + @SneakyThrows + public void comparativeTest() { + for (String testFile : testFiles) { + Value originalValue = new JSONParser().parse(getTestSource(testFile)); + + Path outputFile = Files.createTempFile(Paths.get("/tmp"),"worh", null); + try (OutputStream os = new FileOutputStream(outputFile.toFile())) { + JBONDumper jbonDumper = new JBONDumper(); + jbonDumper.dump(originalValue, os); + } + Value binarySerializedValue; + try(InputStream is = new FileInputStream(outputFile.toFile())) { + JBONParser jbonParser = new JBONParser(); + binarySerializedValue = jbonParser.parse(is); + } + Assert.assertEquals(originalValue, binarySerializedValue); + } + } + + @Test + @SneakyThrows + public void hexTest() { + String hex = "1F608"; + byte[] buffer = hex.getBytes(); + ByteArrayInputStream bais = new ByteArrayInputStream(buffer); + Method method = JSONParser.class.getDeclaredMethod("parseHex", LookAheadTextInputStream.class); + method.setAccessible(true); + LookAheadTextInputStream ltis = new LookAheadTextInputStream(new InputStreamReader(bais)); + ltis.read(); + int result = (int) method.invoke(null, ltis); + Assert.assertEquals((int) Integer.valueOf(hex, 16), result); + } +} diff --git a/src/test/java/org/oggio88/worth/serialization/json/JSONTest.java b/src/test/java/org/oggio88/worth/serialization/json/JSONTest.java index 6d435b0..1104844 100644 --- a/src/test/java/org/oggio88/worth/serialization/json/JSONTest.java +++ b/src/test/java/org/oggio88/worth/serialization/json/JSONTest.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.node.JsonNodeType; import lombok.SneakyThrows; import org.junit.Assert; import org.junit.Test; -import org.oggio88.worth.buffer.CircularBuffer; import org.oggio88.worth.buffer.LookAheadTextInputStream; import org.oggio88.worth.exception.NotImplementedException; import org.oggio88.worth.utils.WorthUtils; @@ -187,7 +186,7 @@ public class JSONTest { @Test @SneakyThrows public void consistencyTest() { - System.setProperty("org.oggio88.javason.value.ObjectValue.preserveKeyOrder", "true"); + System.setProperty(ObjectValue.class.getName() + ".preserveKeyOrder", "true"); for (String testFile : testFiles) { Parser parser = new JSONParser(); Value parsedValue = parser.parse(getTestSource(testFile)); @@ -209,8 +208,8 @@ public class JSONTest { @Test @SneakyThrows public void comparativeTest() { + ObjectMapper om = new ObjectMapper(); for (String testFile : testFiles) { - ObjectMapper om = new ObjectMapper(); JsonNode jsonNode = om.readTree(getTestSource(testFile)); Value value = new JSONParser().parse(getTestSource(testFile)); Assert.assertTrue(compareValueAndJsonNode(value, jsonNode, (v, j) -> { @@ -223,7 +222,7 @@ public class JSONTest { @SneakyThrows public void hexTest() { String hex = "1F608"; - byte[] buffer = new String(hex).getBytes(); + byte[] buffer = hex.getBytes(); ByteArrayInputStream bais = new ByteArrayInputStream(buffer); Method method = JSONParser.class.getDeclaredMethod("parseHex", LookAheadTextInputStream.class); method.setAccessible(true); diff --git a/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java b/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java index 81d5606..3bde86d 100644 --- a/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java +++ b/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java @@ -4,19 +4,21 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.junit.Ignore; import org.junit.Test; import org.oggio88.worth.antlr.JSONLexer; import org.oggio88.worth.antlr.JSONListenerImpl; +import org.oggio88.worth.serialization.binary.JBONDumper; +import org.oggio88.worth.value.ObjectValue; +import org.oggio88.worth.xface.Dumper; import org.oggio88.worth.xface.Value; import org.tukaani.xz.XZInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.InputStream; +import java.io.*; import java.nio.file.Paths; class Chronometer { @@ -54,17 +56,8 @@ class Chronometer { public class PerformanceTest { @SneakyThrows - private static byte[] extractTestData() { - ByteArrayOutputStream baous = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024 * 1024]; - try (InputStream is = new XZInputStream(PerformanceTest.class.getResourceAsStream("/citylots.json.xz"))) { - while (true) { - int read = is.read(buffer); - if (read < 0) break; - baous.write(buffer, 0, read); - } - } - return baous.toByteArray(); + private static InputStream extractTestData() { + return new XZInputStream(PerformanceTest.class.getResourceAsStream("/citylots.json.xz")); } @Test @@ -83,9 +76,9 @@ public class PerformanceTest { final int loops = 100; Chronometer chr = new Chronometer(); { + ObjectMapper om = new ObjectMapper(); chr.reset(); for (int i = 0; i < loops; i++) { - ObjectMapper om = new ObjectMapper(); JsonNode jsonNode = om.readTree(getClass().getResourceAsStream("/wordpress.json")); } jacksonTime = chr.stop(Chronometer.TimeUnit.MILLISECOND); @@ -102,8 +95,8 @@ public class PerformanceTest { { chr.reset(); for (int i = 0; i < loops; i++) { - ANTLRInputStream inputStream = new ANTLRInputStream( - getClass().getResourceAsStream("/wordpress.json")); + CharStream inputStream = CharStreams.fromReader( + new InputStreamReader(getClass().getResourceAsStream("/wordpress.json"))); JSONLexer lexer = new JSONLexer(inputStream); CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); org.oggio88.worth.antlr.JSONParser parser = new org.oggio88.worth.antlr.JSONParser(commonTokenStream); @@ -120,25 +113,24 @@ public class PerformanceTest { @Ignore @SneakyThrows public void hugeJSONTest() { - byte[] testData = extractTestData(); double jacksonTime, worthTime, antlrTime; Chronometer chr = new Chronometer(); - { + try(InputStream is = extractTestData()) { chr.reset(); ObjectMapper om = new ObjectMapper(); - JsonNode jsonNode = om.readTree(new ByteArrayInputStream(testData)); + om.readTree(is); jacksonTime = chr.stop(Chronometer.TimeUnit.SECOND); System.out.printf("Jackson time: %8s sec\n", String.format("%.3f", jacksonTime)); } - { + try(InputStream is = extractTestData()) { chr.reset(); - Value value = new JSONParser().parse(new ByteArrayInputStream(testData)); + new JSONParser().parse(is); worthTime = chr.stop(Chronometer.TimeUnit.SECOND); System.out.printf("Worth time: %8s sec\n", String.format("%.3f", worthTime)); } - { + try(InputStream is = extractTestData()) { chr.reset(); - ANTLRInputStream inputStream = new ANTLRInputStream(new ByteArrayInputStream(testData)); + CharStream inputStream = CharStreams.fromReader(new InputStreamReader(is)); JSONLexer lexer = new JSONLexer(inputStream); CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); org.oggio88.worth.antlr.JSONParser parser = new org.oggio88.worth.antlr.JSONParser(commonTokenStream); diff --git a/src/test/java/org/oggio88/worth/utils/Leb128Test.java b/src/test/java/org/oggio88/worth/utils/Leb128Test.java new file mode 100644 index 0000000..00ea938 --- /dev/null +++ b/src/test/java/org/oggio88/worth/utils/Leb128Test.java @@ -0,0 +1,60 @@ +package org.oggio88.worth.utils; + +import lombok.SneakyThrows; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; + +public class Leb128Test { + + @Test + public void testLong() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + List numbers = Arrays.asList(0L, 1L, -3L, 5L, 7L, 8L, 125L, 255L, 10325L, -2000L, 1024L * 1024L * 1024L * 12L); + + numbers.forEach(n -> Leb128.encode(baos, n)); + + byte[] bytes = baos.toByteArray(); + + Leb128.Leb128Decoder decoder = new Leb128.Leb128Decoder(new ByteArrayInputStream(bytes)); + numbers.forEach(n -> Assert.assertEquals((long) n, decoder.decode())); + } + + @Test + public void testDouble() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + List numbers = Arrays.asList(0.0, 1.5, -3.0, 0.5, 2.5, 8.25, -125.0, 255.0, 10325.0, -2000.0, 1024.0 * 1024 * 1024 * 12); + + numbers.forEach(n -> Leb128.encode(baos, n)); + + byte[] bytes = baos.toByteArray(); + + Leb128.Leb128Decoder decoder = new Leb128.Leb128Decoder(new ByteArrayInputStream(bytes)); + numbers.forEach(n -> Assert.assertEquals(n, decoder.decodeDouble(), 0.0)); + } + + + @Test + public void reverseTest() { + long n = 101325; + Assert.assertEquals(n, Leb128.reverse(Leb128.reverse(n))); + } + + @Test + @SneakyThrows + public void reverseTestDouble() { + double n = 0.25; + long doubleLong = Double.doubleToLongBits(n); + long reverse = Leb128.reverse(doubleLong); + try(ByteArrayOutputStream os = new ByteArrayOutputStream()) { + Leb128.encode(os, reverse); + byte[] bytes = os.toByteArray(); + Assert.assertEquals(3, bytes.length); + } + } +} diff --git a/src/test/java/org/oggio88/worth/utils/ValueWalkerTest.java b/src/test/java/org/oggio88/worth/utils/ValueWalkerTest.java new file mode 100644 index 0000000..6ea7c19 --- /dev/null +++ b/src/test/java/org/oggio88/worth/utils/ValueWalkerTest.java @@ -0,0 +1,29 @@ +package org.oggio88.worth.utils; + +import lombok.SneakyThrows; +import org.junit.Assert; +import org.junit.Test; +import org.oggio88.worth.serialization.json.JSONParser; +import org.oggio88.worth.xface.Parser; +import org.oggio88.worth.xface.Value; + +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.util.Optional; + +public class ValueWalkerTest { + + @Test + @SneakyThrows + public void test() { + Value value; + try(InputStream is = new BufferedInputStream(getClass().getResourceAsStream("/test.json"))) { + Parser parser = JSONParser.newInstance(); + value = parser.parse(is); + } + ValueWalker valueWalker = new ValueWalker(value); + Optional text = valueWalker.get("widget").get("image").get("tags").get(1).map(Value::asString); + Assert.assertTrue(text.isPresent()); + Assert.assertEquals("Amazon", text.get()); + } +}