initial commit
This commit is contained in:
33
build.sbt
Normal file
33
build.sbt
Normal file
@@ -0,0 +1,33 @@
|
||||
name := "worth"
|
||||
|
||||
organization := "org.oggio88"
|
||||
|
||||
version := "1.0"
|
||||
resolvers += Resolver.mavenLocal
|
||||
scalaVersion := "2.12.6"
|
||||
|
||||
scalacOptions ++= Seq(
|
||||
"-unchecked",
|
||||
"-deprecation",
|
||||
"-language:_",
|
||||
"-opt:l:inline", "-opt-inline-from",
|
||||
"-target:jvm-1.8",
|
||||
"-encoding", "UTF-8"
|
||||
)
|
||||
|
||||
git.useGitDescribe := true
|
||||
fork := true
|
||||
//javaOptions in Test += "-Dorg.oggio88.javason.value.ObjectValue.listBasedImplementation=true"
|
||||
javaOptions in Test += "-Xmx6G"
|
||||
//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 += "org.antlr" % "antlr4" % "4.7.1" % "compile"
|
||||
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
|
||||
}
|
9
project/plugins.sbt
Normal file
9
project/plugins.sbt
Normal file
@@ -0,0 +1,9 @@
|
||||
//addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "4.0.2")
|
||||
addSbtPlugin("com.dwijnand" % "sbt-travisci" % "1.1.1")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
|
||||
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15")
|
||||
//addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.9.3")
|
||||
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "4.1.0")
|
||||
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.3")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.6")
|
38
src/main/java/org/oggio88/worth/buffer/CircularBuffer.java
Normal file
38
src/main/java/org/oggio88/worth/buffer/CircularBuffer.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package org.oggio88.worth.buffer;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.Reader;
|
||||
|
||||
public class CircularBuffer {
|
||||
|
||||
private int[] buffer;
|
||||
private Reader reader;
|
||||
private int delta = 0, cursor = 0;
|
||||
|
||||
public CircularBuffer(Reader reader, int size) {
|
||||
this.reader = reader;
|
||||
buffer = new int[size];
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public int next() {
|
||||
if (delta < 0)
|
||||
return buffer[Math.floorMod(cursor + delta++, buffer.length)];
|
||||
else {
|
||||
int result = reader.read();
|
||||
if (result < 0) return result;
|
||||
buffer[cursor] = result;
|
||||
cursor = (cursor + 1) % buffer.length;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public int prev() {
|
||||
return buffer[cursor + --delta >= 0 ? cursor + delta : cursor + delta + buffer.length];
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return buffer.length;
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package org.oggio88.worth.exception;
|
||||
|
||||
public class IOException extends WorthException {
|
||||
public IOException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package org.oggio88.worth.exception;
|
||||
|
||||
public class NotImplementedException extends WorthException {
|
||||
public NotImplementedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package org.oggio88.worth.exception;
|
||||
|
||||
public class ParseException extends WorthException {
|
||||
public ParseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package org.oggio88.worth.exception;
|
||||
|
||||
public class TypeException extends WorthException {
|
||||
public TypeException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package org.oggio88.worth.exception;
|
||||
|
||||
public class WorthException extends RuntimeException {
|
||||
public WorthException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
105
src/main/java/org/oggio88/worth/serialization/ValueDumper.java
Normal file
105
src/main/java/org/oggio88/worth/serialization/ValueDumper.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package org.oggio88.worth.serialization;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.oggio88.worth.exception.NotImplementedException;
|
||||
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.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
public abstract class ValueDumper implements Dumper {
|
||||
|
||||
@RequiredArgsConstructor
|
||||
protected static class StackLevel {
|
||||
public int index = 0;
|
||||
public final Value value;
|
||||
}
|
||||
|
||||
protected static class ArrayStackLevel extends StackLevel implements Iterator<Value> {
|
||||
private final Iterator<Value> iterator = value.asArray().iterator();
|
||||
|
||||
@Override
|
||||
public Value next() {
|
||||
++index;
|
||||
return iterator.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
public ArrayStackLevel(ArrayValue value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ObjectStackLevel extends StackLevel implements Iterator<Map.Entry<String, Value>> {
|
||||
private final Iterator<Map.Entry<String, Value>> iterator = value.asObject().entrySet().iterator();
|
||||
|
||||
@Override
|
||||
public Map.Entry<String, Value> next() {
|
||||
++index;
|
||||
return iterator.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
public ObjectStackLevel(ObjectValue value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Stack<StackLevel> stack;
|
||||
|
||||
protected ValueDumper() {
|
||||
stack = new Stack<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Value value, OutputStream stream) {
|
||||
throw new NotImplementedException("Method not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Value value, Writer writer) {
|
||||
throw new NotImplementedException("Method not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Value value, OutputStream stream, Charset encoding) {
|
||||
dump(value, new OutputStreamWriter(stream, encoding));
|
||||
}
|
||||
|
||||
protected abstract void beginObject();
|
||||
|
||||
protected abstract void endObject();
|
||||
|
||||
protected abstract void beginArray();
|
||||
|
||||
protected abstract void endArray();
|
||||
|
||||
protected abstract void objectKey(String key);
|
||||
|
||||
protected abstract void stringValue(String value);
|
||||
|
||||
protected abstract void integerValue(long value);
|
||||
|
||||
protected abstract void floatValue(double value);
|
||||
|
||||
protected abstract void booleanValue(boolean value);
|
||||
|
||||
protected abstract void nullValue();
|
||||
}
|
116
src/main/java/org/oggio88/worth/serialization/ValueParser.java
Normal file
116
src/main/java/org/oggio88/worth/serialization/ValueParser.java
Normal file
@@ -0,0 +1,116 @@
|
||||
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.xface.Parser;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Stack;
|
||||
|
||||
public class ValueParser implements Parser {
|
||||
|
||||
@RequiredArgsConstructor
|
||||
protected static class StackLevel {
|
||||
public final Value value;
|
||||
}
|
||||
|
||||
protected static class ArrayStackLevel extends StackLevel {
|
||||
public ArrayStackLevel() {
|
||||
super(new ArrayValue());
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ObjectStackLevel extends StackLevel {
|
||||
public String currentKey;
|
||||
|
||||
public ObjectStackLevel() {
|
||||
super(ObjectValue.newInstance());
|
||||
}
|
||||
}
|
||||
|
||||
protected Stack<StackLevel> stack;
|
||||
|
||||
private void add2Last(Value value) {
|
||||
StackLevel last = stack.lastElement();
|
||||
ArrayStackLevel asl;
|
||||
ObjectStackLevel osl;
|
||||
if ((asl = WorthUtils.dynamicCast(last, ArrayStackLevel.class)) != null)
|
||||
asl.value.add(value);
|
||||
else if ((osl = WorthUtils.dynamicCast(last, ObjectStackLevel.class)) != null) {
|
||||
osl.value.put(osl.currentKey, value);
|
||||
osl.currentKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected ValueParser() {
|
||||
stack = new Stack<>();
|
||||
stack.push(new ArrayStackLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value parse(InputStream is) {
|
||||
throw new NotImplementedException("Method not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value parse(Reader reader) {
|
||||
throw new NotImplementedException("Method not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value parse(InputStream stream, Charset encoding) {
|
||||
return parse(new InputStreamReader(stream, encoding));
|
||||
}
|
||||
|
||||
protected void beginObject() {
|
||||
stack.push(new ObjectStackLevel());
|
||||
}
|
||||
|
||||
protected void endObject() {
|
||||
add2Last(stack.pop().value);
|
||||
}
|
||||
|
||||
protected void beginArray() {
|
||||
stack.push(new ArrayStackLevel());
|
||||
}
|
||||
|
||||
protected void endArray() {
|
||||
add2Last(stack.pop().value);
|
||||
}
|
||||
|
||||
protected void objectKey(String key) {
|
||||
ObjectStackLevel osl = (ObjectStackLevel) stack.lastElement();
|
||||
osl.currentKey = key;
|
||||
}
|
||||
|
||||
protected void stringValue(String value) {
|
||||
add2Last(new StringValue(value));
|
||||
}
|
||||
|
||||
protected void integerValue(long value) {
|
||||
add2Last(new IntegerValue(value));
|
||||
}
|
||||
|
||||
protected void floatValue(double value) {
|
||||
add2Last(new FloatValue(value));
|
||||
}
|
||||
|
||||
protected void booleanValue(boolean value) {
|
||||
add2Last(new BooleanValue(value));
|
||||
}
|
||||
|
||||
protected void nullValue() {
|
||||
add2Last(Value.Null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,183 @@
|
||||
package org.oggio88.worth.serialization.json;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.oggio88.worth.serialization.ValueDumper;
|
||||
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.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.oggio88.worth.utils.WorthUtils.dynamicCast;
|
||||
|
||||
public class JSONDumper extends ValueDumper {
|
||||
|
||||
public static Dumper newInstance() {
|
||||
return new JSONDumper();
|
||||
}
|
||||
|
||||
protected Writer writer;
|
||||
|
||||
@Override
|
||||
public void dump(Value value, OutputStream stream) {
|
||||
dump(value, new OutputStreamWriter(stream));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void dump(Value value, Writer writer) {
|
||||
this.writer = writer;
|
||||
final Consumer<Value> 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:
|
||||
stack.push(new ArrayStackLevel(dynamicCast(v, ArrayValue.class)));
|
||||
beginArray();
|
||||
break;
|
||||
case OBJECT:
|
||||
stack.push(new ObjectStackLevel(dynamicCast(v, ObjectValue.class)));
|
||||
beginObject();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handle_value.accept(value);
|
||||
while (stack.size() > 0) {
|
||||
StackLevel last = stack.lastElement();
|
||||
ArrayStackLevel arrayStackLevel;
|
||||
ObjectStackLevel objectStackLevel;
|
||||
if ((arrayStackLevel = dynamicCast(last, ArrayStackLevel.class)) != null) {
|
||||
if (arrayStackLevel.hasNext()) {
|
||||
if (arrayStackLevel.index > 0) {
|
||||
writer.write(",");
|
||||
}
|
||||
handle_value.accept(arrayStackLevel.next());
|
||||
} else {
|
||||
endArray();
|
||||
stack.pop();
|
||||
}
|
||||
} else if ((objectStackLevel = dynamicCast(last, ObjectStackLevel.class)) != null) {
|
||||
if (objectStackLevel.hasNext()) {
|
||||
if (objectStackLevel.index > 0) {
|
||||
writer.write(",");
|
||||
}
|
||||
Map.Entry<String, Value> entry = objectStackLevel.next();
|
||||
objectKey(entry.getKey());
|
||||
writer.write(":");
|
||||
handle_value.accept(entry.getValue());
|
||||
} else {
|
||||
endObject();
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.writer.flush();
|
||||
this.writer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void beginObject() {
|
||||
this.writer.write("{");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void endObject() {
|
||||
this.writer.write("}");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void beginArray() {
|
||||
this.writer.write("[");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void endArray() {
|
||||
this.writer.write("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void objectKey(String key) {
|
||||
this.writer.write("\"" + key + "\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void stringValue(String value) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (char c : value.toCharArray()) {
|
||||
switch (c) {
|
||||
case '"':
|
||||
sb.append("\\\"");
|
||||
break;
|
||||
case '\r':
|
||||
sb.append("\\r");
|
||||
break;
|
||||
case '\n':
|
||||
sb.append("\\n");
|
||||
break;
|
||||
case '\t':
|
||||
sb.append("\\t");
|
||||
break;
|
||||
case '\\':
|
||||
sb.append("\\\\");
|
||||
break;
|
||||
default: {
|
||||
if (c < 128)
|
||||
sb.append(c);
|
||||
else {
|
||||
sb.append("\\u").append(String.format("%04X", (int) c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.writer.write("\"" + sb.toString() + "\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void integerValue(long value) {
|
||||
this.writer.write(Long.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void floatValue(double value) {
|
||||
this.writer.write(Double.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void booleanValue(boolean value) {
|
||||
this.writer.write(Boolean.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void nullValue() {
|
||||
this.writer.write("null");
|
||||
}
|
||||
}
|
@@ -0,0 +1,227 @@
|
||||
package org.oggio88.worth.serialization.json;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.oggio88.worth.buffer.CircularBuffer;
|
||||
import org.oggio88.worth.exception.IOException;
|
||||
import org.oggio88.worth.exception.NotImplementedException;
|
||||
import org.oggio88.worth.exception.ParseException;
|
||||
import org.oggio88.worth.serialization.ValueParser;
|
||||
import org.oggio88.worth.utils.WorthUtils;
|
||||
import org.oggio88.worth.xface.Parser;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class JSONParser extends ValueParser {
|
||||
|
||||
private int currentLine = 1, currentColumn = 1;
|
||||
|
||||
private static boolean isBlank(int c) {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
||||
}
|
||||
|
||||
private static boolean isDecimal(int c) {
|
||||
return c >= '0' && c <= '9' || c == '+' || c == '-' || c == '.' || c == 'e';
|
||||
}
|
||||
|
||||
private static int parseHex(CircularBuffer circularBuffer) {
|
||||
int result = 0;
|
||||
while (true) {
|
||||
int c = circularBuffer.next();
|
||||
if (c >= '0' && c <= '9') {
|
||||
result = result << 4;
|
||||
result += (c - '0');
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
result = result << 4;
|
||||
result += 10 + (c - 'a');
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
result = result << 4;
|
||||
result += 10 + (c - 'A');
|
||||
} else {
|
||||
circularBuffer.prev();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private final void parseNumber(CircularBuffer circularBuffer) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (true) {
|
||||
int b = circularBuffer.next();
|
||||
if (isDecimal(b)) {
|
||||
sb.appendCodePoint(b);
|
||||
} else {
|
||||
circularBuffer.prev();
|
||||
break;
|
||||
}
|
||||
}
|
||||
String text = sb.toString();
|
||||
if (text.indexOf('.') > 0) {
|
||||
floatValue(Double.valueOf(text));
|
||||
} else {
|
||||
integerValue(Long.valueOf(text));
|
||||
}
|
||||
}
|
||||
|
||||
private final String readString(CircularBuffer circularBuffer) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean escape = false;
|
||||
while (true) {
|
||||
int c = circularBuffer.next();
|
||||
if (c < 0) {
|
||||
circularBuffer.prev();
|
||||
break;
|
||||
} else if (escape) {
|
||||
switch (c) {
|
||||
case '"':
|
||||
sb.append('\"');
|
||||
break;
|
||||
case 'r':
|
||||
sb.append('\r');
|
||||
break;
|
||||
case 'n':
|
||||
sb.append('\n');
|
||||
break;
|
||||
case 't':
|
||||
sb.append('\t');
|
||||
break;
|
||||
case '\\':
|
||||
sb.append('\\');
|
||||
break;
|
||||
case 'u':
|
||||
int codePoint = parseHex(circularBuffer);
|
||||
sb.appendCodePoint(codePoint);
|
||||
break;
|
||||
default:
|
||||
throw error(ParseException::new, "Unrecognized escape sequence '\\%c'", c);
|
||||
}
|
||||
escape = false;
|
||||
} else if (c == '\\') {
|
||||
escape = true;
|
||||
} else if (c == '\"') {
|
||||
break;
|
||||
} else {
|
||||
sb.appendCodePoint(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private final void consumeExpected(CircularBuffer circularBuffer, String expected, String errorMessage) {
|
||||
for (int i = 0; i < expected.length(); i++) {
|
||||
int c = circularBuffer.next();
|
||||
if (c < 0) {
|
||||
throw error(IOException::new, "Unexpected end of stream");
|
||||
}
|
||||
if (c != expected.codePointAt(i)) throw error(ParseException::new, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends RuntimeException> T error(Function<String, T> constructor, String fmt, Object... args) {
|
||||
return constructor.apply(
|
||||
String.format("Error at line %d column %d: %s",
|
||||
currentLine, currentColumn, String.format(fmt, args)));
|
||||
}
|
||||
|
||||
public static Parser newInstance() {
|
||||
return new JSONParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value parse(InputStream stream) {
|
||||
return parse(new InputStreamReader(stream));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Value parse(Reader reader) {
|
||||
final CircularBuffer circularBuffer = new CircularBuffer(reader, 8) {
|
||||
|
||||
@Override
|
||||
public int next() {
|
||||
int result = super.next();
|
||||
if (result == '\n') {
|
||||
++currentLine;
|
||||
currentColumn = 1;
|
||||
} else {
|
||||
++currentColumn;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int prev() {
|
||||
int result = super.prev();
|
||||
if (result == '\n') {
|
||||
--currentLine;
|
||||
} else {
|
||||
--currentColumn;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
int c = circularBuffer.next();
|
||||
if (c == -1) {
|
||||
break;
|
||||
} else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
||||
continue;
|
||||
} else if (c == '{') {
|
||||
beginObject();
|
||||
} else if (c == '}') {
|
||||
endObject();
|
||||
} else if (c == '[') {
|
||||
beginArray();
|
||||
} else if (c == ']') {
|
||||
endArray();
|
||||
} else if (isDecimal(c)) {
|
||||
circularBuffer.prev();
|
||||
try {
|
||||
parseNumber(circularBuffer);
|
||||
} catch (NumberFormatException nfe) {
|
||||
|
||||
}
|
||||
} else if (c == '\"') {
|
||||
String text = readString(circularBuffer);
|
||||
ObjectStackLevel osl;
|
||||
if ((osl = WorthUtils.dynamicCast(stack.lastElement(), ObjectStackLevel.class)) != null && osl.currentKey == null) {
|
||||
objectKey(text);
|
||||
} else {
|
||||
stringValue(text);
|
||||
}
|
||||
} else if (c == 't') {
|
||||
consumeExpected(circularBuffer, "rue", "Unrecognized boolean value");
|
||||
booleanValue(true);
|
||||
} else if (c == 'f') {
|
||||
consumeExpected(circularBuffer, "alse", "Unrecognized boolean value");
|
||||
booleanValue(false);
|
||||
} else if (c == 'n') {
|
||||
consumeExpected(circularBuffer, "ull", "Unrecognized null value");
|
||||
nullValue();
|
||||
}
|
||||
}
|
||||
if (stack.size() > 1) {
|
||||
char c;
|
||||
if (stack.lastElement() instanceof ArrayStackLevel) {
|
||||
c = ']';
|
||||
} else if (stack.lastElement() 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);
|
||||
} catch (NumberFormatException e) {
|
||||
throw error(ParseException::new, e.getMessage());
|
||||
} finally {
|
||||
stack.clear();
|
||||
}
|
||||
}
|
||||
}
|
26
src/main/java/org/oggio88/worth/utils/WorthUtils.java
Normal file
26
src/main/java/org/oggio88/worth/utils/WorthUtils.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.oggio88.worth.utils;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class WorthUtils {
|
||||
|
||||
@SneakyThrows
|
||||
public static <T> T uncheckCall(final Callable<T> callable) {
|
||||
return callable.call();
|
||||
}
|
||||
|
||||
public static <T> T dynamicCast(final Object o, final Class<T> cls) {
|
||||
if (cls.isInstance(o)) {
|
||||
return (T) o;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean equalsNullSafe(Object o1, Object o2) {
|
||||
if (o1 == null) return o2 == null;
|
||||
else return o1.equals(o2);
|
||||
}
|
||||
}
|
69
src/main/java/org/oggio88/worth/value/ArrayValue.java
Normal file
69
src/main/java/org/oggio88/worth/value/ArrayValue.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package org.oggio88.worth.value;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class ArrayValue implements Value, Iterable<Value> {
|
||||
|
||||
private final List<Value> value;
|
||||
|
||||
public ArrayValue() {
|
||||
this.value = new ArrayList();
|
||||
}
|
||||
|
||||
public ArrayValue(List<Value> value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type type() {
|
||||
return Type.ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Value value) {
|
||||
this.value.add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value get(int index) {
|
||||
return value.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value pop() {
|
||||
Value last = tail();
|
||||
value.remove(value.size() - 1);
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value head() {
|
||||
return value.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value tail() {
|
||||
return value.get(value.size() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Value> asArray() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<Value> iterator() {
|
||||
return value.iterator();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return value.size();
|
||||
}
|
||||
}
|
24
src/main/java/org/oggio88/worth/value/BooleanValue.java
Normal file
24
src/main/java/org/oggio88/worth/value/BooleanValue.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package org.oggio88.worth.value;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class BooleanValue implements Value {
|
||||
|
||||
private final boolean value;
|
||||
|
||||
public BooleanValue(boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type type() {
|
||||
return Type.BOOLEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean asBoolean() {
|
||||
return value;
|
||||
}
|
||||
}
|
24
src/main/java/org/oggio88/worth/value/FloatValue.java
Normal file
24
src/main/java/org/oggio88/worth/value/FloatValue.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package org.oggio88.worth.value;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class FloatValue implements Value {
|
||||
|
||||
private final double value;
|
||||
|
||||
public FloatValue(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type type() {
|
||||
return Type.DOUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double asFloat() {
|
||||
return value;
|
||||
}
|
||||
}
|
24
src/main/java/org/oggio88/worth/value/IntegerValue.java
Normal file
24
src/main/java/org/oggio88/worth/value/IntegerValue.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package org.oggio88.worth.value;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class IntegerValue implements Value {
|
||||
|
||||
private final long value;
|
||||
|
||||
public IntegerValue(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type type() {
|
||||
return Type.INTEGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asInteger() {
|
||||
return value;
|
||||
}
|
||||
}
|
13
src/main/java/org/oggio88/worth/value/NullValue.java
Normal file
13
src/main/java/org/oggio88/worth/value/NullValue.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.oggio88.worth.value;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class NullValue implements Value {
|
||||
|
||||
@Override
|
||||
public Type type() {
|
||||
return Type.NULL;
|
||||
}
|
||||
}
|
185
src/main/java/org/oggio88/worth/value/ObjectValue.java
Normal file
185
src/main/java/org/oggio88/worth/value/ObjectValue.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package org.oggio88.worth.value;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.oggio88.worth.utils.WorthUtils.equalsNullSafe;
|
||||
|
||||
|
||||
public interface ObjectValue extends Value, Iterable<Map.Entry<String, Value>> {
|
||||
|
||||
boolean listBasedImplementation = Boolean.valueOf(
|
||||
System.getProperty("org.oggio88.javason.value.ObjectValue.listBasedImplementation", "false"));
|
||||
boolean preserveKeyOrder = Boolean.valueOf(
|
||||
System.getProperty("org.oggio88.javason.value.MapObjectValue.preserveKeyOrder", "false"));
|
||||
|
||||
static ObjectValue newInstance() {
|
||||
if (listBasedImplementation) {
|
||||
return new MapObjectValue();
|
||||
} else {
|
||||
return new MapObjectValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default Type type() {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
}
|
||||
|
||||
final class ObjectEntry<K, V> implements Map.Entry<K, V> {
|
||||
private final K key;
|
||||
private V value;
|
||||
|
||||
public ObjectEntry(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
V old = this.value;
|
||||
this.value = value;
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
class MapObjectValue implements ObjectValue {
|
||||
|
||||
private final Map<String, Value> value;
|
||||
|
||||
public MapObjectValue() {
|
||||
this.value = ObjectValue.preserveKeyOrder ? new LinkedHashMap() : new HashMap();
|
||||
}
|
||||
|
||||
public MapObjectValue(Map<String, Value> value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Value> asObject() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value get(String key) {
|
||||
Value result = value.get(key);
|
||||
if (result == null) {
|
||||
result = Value.Null;
|
||||
value.put(key, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getOrDefault(String key, Value defaultValue) {
|
||||
if (value.containsKey(key))
|
||||
return value.get(key);
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getOrPut(String key, Value value2Put) {
|
||||
if (value.containsKey(key))
|
||||
return value.get(key);
|
||||
else {
|
||||
put(key, value2Put);
|
||||
return value2Put;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, Value value2Put) {
|
||||
this.value.put(key, value2Put);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean has(String key) {
|
||||
return value.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<String, Value>> iterator() {
|
||||
return value.entrySet().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
class ListObjectValue implements ObjectValue {
|
||||
|
||||
private final List<Map.Entry<String, Value>> value = new ArrayList();
|
||||
|
||||
public ListObjectValue(Map<String, Value> map) {
|
||||
this.value.addAll(map.entrySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Value> asObject() {
|
||||
Map<String, Value> result = preserveKeyOrder ? new LinkedHashMap() : new HashMap();
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value get(String key) {
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
if(equalsNullSafe(entry.getKey(), key)) return entry.getValue();
|
||||
}
|
||||
return Value.Null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getOrDefault(String key, Value defaultValue) {
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
if(equalsNullSafe(entry.getKey(), key)) return entry.getValue();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getOrPut(String key, Value value2Put) {
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
if(equalsNullSafe(entry.getKey(), key)) return entry.getValue();
|
||||
}
|
||||
put(key, value2Put);
|
||||
return value2Put;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, Value value2Put) {
|
||||
value.add(new ObjectEntry(key, value2Put));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean has(String key) {
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
if(equalsNullSafe(entry.getKey(), key)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<String, Value>> iterator() {
|
||||
return value.iterator();
|
||||
}
|
||||
}
|
25
src/main/java/org/oggio88/worth/value/StringValue.java
Normal file
25
src/main/java/org/oggio88/worth/value/StringValue.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package org.oggio88.worth.value;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class StringValue implements Value {
|
||||
|
||||
private final String value;
|
||||
|
||||
public StringValue(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type type() {
|
||||
return Type.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return value;
|
||||
}
|
||||
}
|
13
src/main/java/org/oggio88/worth/xface/Dumper.java
Normal file
13
src/main/java/org/oggio88/worth/xface/Dumper.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.oggio88.worth.xface;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public interface Dumper {
|
||||
void dump(Value value, OutputStream is);
|
||||
|
||||
void dump(Value value, Writer reader);
|
||||
|
||||
void dump(Value value, OutputStream stream, Charset encoding);
|
||||
}
|
14
src/main/java/org/oggio88/worth/xface/Parser.java
Normal file
14
src/main/java/org/oggio88/worth/xface/Parser.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.oggio88.worth.xface;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public interface Parser {
|
||||
|
||||
Value parse(InputStream is);
|
||||
|
||||
Value parse(Reader reader);
|
||||
|
||||
Value parse(InputStream stream, Charset encoding);
|
||||
}
|
82
src/main/java/org/oggio88/worth/xface/Value.java
Normal file
82
src/main/java/org/oggio88/worth/xface/Value.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package org.oggio88.worth.xface;
|
||||
|
||||
import org.oggio88.worth.exception.TypeException;
|
||||
import org.oggio88.worth.value.NullValue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface Value {
|
||||
|
||||
Value Null = new NullValue();
|
||||
|
||||
enum Type {
|
||||
OBJECT, ARRAY, STRING, DOUBLE, INTEGER, BOOLEAN, NULL
|
||||
}
|
||||
|
||||
Type type();
|
||||
|
||||
default boolean asBoolean() {
|
||||
throw new TypeException("Not a boolean");
|
||||
}
|
||||
|
||||
default long asInteger() {
|
||||
throw new TypeException("Not an integer");
|
||||
}
|
||||
|
||||
default double asFloat() {
|
||||
throw new TypeException("Not a float");
|
||||
}
|
||||
|
||||
default String asString() {
|
||||
throw new TypeException("Not a String");
|
||||
}
|
||||
|
||||
default List<Value> asArray() {
|
||||
throw new TypeException("Not an array");
|
||||
}
|
||||
|
||||
default Map<String, Value> asObject() {
|
||||
throw new TypeException("Not an object");
|
||||
}
|
||||
|
||||
default void add(Value value) {
|
||||
throw new TypeException("Not an array");
|
||||
}
|
||||
|
||||
default Value pop() {
|
||||
throw new TypeException("Not an array");
|
||||
}
|
||||
|
||||
default Value head() {
|
||||
throw new TypeException("Not an array");
|
||||
}
|
||||
|
||||
default Value tail() {
|
||||
throw new TypeException("Not an array");
|
||||
}
|
||||
|
||||
default Value get(int index) {
|
||||
throw new TypeException("Not an array");
|
||||
}
|
||||
|
||||
default void put(String key, Value value) {
|
||||
throw new TypeException("Not an object");
|
||||
}
|
||||
|
||||
default Value get(String key) {
|
||||
throw new TypeException("Not an object");
|
||||
}
|
||||
|
||||
default Value getOrDefault(String key, Value defaultValue) {
|
||||
throw new TypeException("Not an object");
|
||||
}
|
||||
|
||||
default Value getOrPut(String key, Value value2Put) {
|
||||
throw new TypeException("Not an object");
|
||||
}
|
||||
|
||||
default boolean has(String key) {
|
||||
throw new TypeException("Not an object");
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package org.oggio88.worth.buffer;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Random;
|
||||
|
||||
public class CircularBufferTest {
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void test() {
|
||||
MessageDigest streamDigest = MessageDigest.getInstance("MD5"), outputDigest = MessageDigest.getInstance("MD5");
|
||||
InputStream is = new DigestInputStream(getClass().getResourceAsStream("/logging.properties"), streamDigest);
|
||||
CircularBuffer cb = new CircularBuffer(new InputStreamReader(is), 32);
|
||||
Random rand = new Random();
|
||||
while (true) {
|
||||
int b = cb.next();
|
||||
if (b < 0) break;
|
||||
if (rand.nextInt() % 2 == 0) {
|
||||
cb.prev();
|
||||
} else {
|
||||
char c = (char) b;
|
||||
outputDigest.update((byte) b);
|
||||
System.out.print(c);
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
Assert.assertArrayEquals(streamDigest.digest(), outputDigest.digest());
|
||||
}
|
||||
}
|
134
src/test/java/org/oggio88/worth/serialization/json/JSONTest.java
Normal file
134
src/test/java/org/oggio88/worth/serialization/json/JSONTest.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package org.oggio88.worth.serialization.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.exception.NotImplementedException;
|
||||
import org.oggio88.worth.utils.WorthUtils;
|
||||
import org.oggio88.worth.value.ArrayValue;
|
||||
import org.oggio88.worth.value.ObjectValue;
|
||||
import org.oggio88.worth.xface.Parser;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
public class JSONTest {
|
||||
|
||||
private String[] testFiles = new String[]{"/test.json", "/wordpress.json"};
|
||||
|
||||
private InputStream getTestSource(String filename) {
|
||||
return getClass().getResourceAsStream(filename);
|
||||
}
|
||||
|
||||
private boolean compareValueAndJsonNode(Value value, JsonNode jsonNode) {
|
||||
switch (value.type()) {
|
||||
case NULL:
|
||||
return jsonNode.getNodeType() == JsonNodeType.NULL;
|
||||
case INTEGER:
|
||||
if (jsonNode.getNodeType() == JsonNodeType.NUMBER) {
|
||||
return value.asInteger() == jsonNode.asLong();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case DOUBLE:
|
||||
if (jsonNode.getNodeType() == JsonNodeType.NUMBER) {
|
||||
return value.asFloat() == jsonNode.asDouble();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case BOOLEAN:
|
||||
if (jsonNode.getNodeType() == JsonNodeType.BOOLEAN) {
|
||||
return value.asBoolean() == jsonNode.asBoolean();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case STRING:
|
||||
if (jsonNode.getNodeType() == JsonNodeType.STRING) {
|
||||
return value.asString().equals(jsonNode.asText());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case ARRAY:
|
||||
ArrayValue array = WorthUtils.dynamicCast(value, ArrayValue.class);
|
||||
if (jsonNode.getNodeType() == JsonNodeType.ARRAY && array.size() == jsonNode.size()) {
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
if (!compareValueAndJsonNode(array.get(i), jsonNode.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case OBJECT:
|
||||
ObjectValue object = WorthUtils.dynamicCast(value, ObjectValue.class);
|
||||
if (jsonNode.getNodeType() == JsonNodeType.OBJECT) {
|
||||
for (Map.Entry<String, Value> entry : object) {
|
||||
if (!jsonNode.has(entry.getKey())) {
|
||||
return false;
|
||||
} else if (!compareValueAndJsonNode(entry.getValue(), jsonNode.get(entry.getKey())))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException("This should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void consistencyTest() {
|
||||
System.setProperty("org.oggio88.javason.value.ObjectValue.preserveKeyOrder", "true");
|
||||
for (String testFile : testFiles) {
|
||||
Parser parser = new JSONParser();
|
||||
Value parsedValue = parser.parse(getTestSource(testFile));
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
JSONDumper.newInstance().dump(parsedValue, baos);
|
||||
String dumpedJSON = new String(baos.toByteArray());
|
||||
byte[] barray = baos.toByteArray();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(barray);
|
||||
parser = new JSONParser();
|
||||
Value reParsedValue = parser.parse(bais);
|
||||
Assert.assertEquals(parsedValue, reParsedValue);
|
||||
baos = new ByteArrayOutputStream();
|
||||
JSONDumper.newInstance().dump(reParsedValue, baos);
|
||||
String reDumpedJSON = new String(baos.toByteArray());
|
||||
Assert.assertEquals(dumpedJSON, reDumpedJSON);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void comparativeTest() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void hexTest() {
|
||||
String hex = "1F608";
|
||||
byte[] buffer = new String(hex).getBytes();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
|
||||
Method method = JSONParser.class.getDeclaredMethod("parseHex", CircularBuffer.class);
|
||||
method.setAccessible(true);
|
||||
int result = (int) method.invoke(null, new CircularBuffer(new InputStreamReader(bais), 5));
|
||||
Assert.assertEquals((int) Integer.valueOf(hex, 16), result);
|
||||
}
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
package org.oggio88.worth.serialization.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
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.nio.file.Paths;
|
||||
|
||||
class Chronometer {
|
||||
|
||||
public enum TimeUnit {
|
||||
NANOSECOND(1e-9), MICROSECOND(1e-6), MILLISECOND(1e-3), SECOND(1);
|
||||
|
||||
private double factor;
|
||||
|
||||
TimeUnit(double factor) {
|
||||
this.factor = factor;
|
||||
}
|
||||
}
|
||||
|
||||
private long start = System.nanoTime();
|
||||
|
||||
public void start() {
|
||||
start = System.nanoTime();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
start();
|
||||
}
|
||||
|
||||
public double stop(TimeUnit unit) {
|
||||
return (System.nanoTime() - start) / (1e9 * unit.factor);
|
||||
}
|
||||
|
||||
public double stop() {
|
||||
return stop(TimeUnit.MILLISECOND);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void loopTest() {
|
||||
double jacksonTime, worthTime;
|
||||
final int loops = 100;
|
||||
Chronometer chr = new Chronometer();
|
||||
{
|
||||
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);
|
||||
System.out.printf("Jackson time: %8s msec\n", String.format("%.3f", jacksonTime));
|
||||
}
|
||||
{
|
||||
chr.reset();
|
||||
for (int i = 0; i < loops; i++) {
|
||||
Value value = new JSONParser().parse(getClass().getResourceAsStream("/wordpress.json"));
|
||||
}
|
||||
worthTime = chr.stop(Chronometer.TimeUnit.MILLISECOND);
|
||||
System.out.printf("Worth time: %8s msec\n", String.format("%.3f", worthTime));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
@SneakyThrows
|
||||
public void hugeJSONTest() {
|
||||
byte[] testData = extractTestData();
|
||||
double jacksonTime, worthTime;
|
||||
Chronometer chr = new Chronometer();
|
||||
{
|
||||
chr.reset();
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
JsonNode jsonNode = om.readTree(new ByteArrayInputStream(testData));
|
||||
jacksonTime = chr.stop(Chronometer.TimeUnit.SECOND);
|
||||
System.out.printf("Jackson time: %8s sec\n", String.format("%.3f", jacksonTime));
|
||||
}
|
||||
{
|
||||
chr.reset();
|
||||
Value value = new JSONParser().parse(new ByteArrayInputStream(testData));
|
||||
worthTime = chr.stop(Chronometer.TimeUnit.SECOND);
|
||||
System.out.printf("Worth time: %8s sec\n", String.format("%.3f", worthTime));
|
||||
}
|
||||
}
|
||||
}
|
80
src/test/resources/JSON.g4
Normal file
80
src/test/resources/JSON.g4
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
/** Taken from "The Definitive ANTLR 4 Reference" by Terence Parr */
|
||||
|
||||
// Derived from http://json.org
|
||||
grammar JSON;
|
||||
|
||||
json
|
||||
: value
|
||||
;
|
||||
|
||||
obj
|
||||
: '{' pair (',' pair)* '}'
|
||||
| '{' '}'
|
||||
;
|
||||
|
||||
pair
|
||||
: STRING ':' value
|
||||
;
|
||||
|
||||
array
|
||||
: '[' value (',' value)* ']'
|
||||
| '[' ']'
|
||||
;
|
||||
|
||||
value
|
||||
: STRING
|
||||
| NUMBER
|
||||
| obj
|
||||
| array
|
||||
| 'true'
|
||||
| 'false'
|
||||
| 'null'
|
||||
;
|
||||
|
||||
|
||||
STRING
|
||||
: '"' (ESC | SAFECODEPOINT)* '"'
|
||||
;
|
||||
|
||||
|
||||
fragment ESC
|
||||
: '\\' (["\\/bfnrt] | UNICODE)
|
||||
;
|
||||
|
||||
|
||||
fragment UNICODE
|
||||
: 'u' HEX HEX HEX HEX
|
||||
;
|
||||
|
||||
|
||||
fragment HEX
|
||||
: [0-9a-fA-F]
|
||||
;
|
||||
|
||||
|
||||
fragment SAFECODEPOINT
|
||||
: ~ ["\\\u0000-\u001F]
|
||||
;
|
||||
|
||||
|
||||
NUMBER
|
||||
: '-'? INT ('.' [0-9] +)? EXP?
|
||||
;
|
||||
|
||||
|
||||
fragment INT
|
||||
: '0' | [1-9] [0-9]*
|
||||
;
|
||||
|
||||
// no leading zeros
|
||||
|
||||
fragment EXP
|
||||
: [Ee] [+\-]? INT
|
||||
;
|
||||
|
||||
// \- since - means "range" inside [...]
|
||||
|
||||
WS
|
||||
: [ \t\n\r] + -> skip
|
||||
;
|
BIN
src/test/resources/citylots.json.xz
Normal file
BIN
src/test/resources/citylots.json.xz
Normal file
Binary file not shown.
36
src/test/resources/logging.properties
Normal file
36
src/test/resources/logging.properties
Normal file
@@ -0,0 +1,36 @@
|
||||
handlers=java.util.logging.ConsoleHandler
|
||||
config=
|
||||
sun.net.www.protocol.http.HttpURLConnection.handlers=java.util.logging.ConsoleHandler
|
||||
#sun.net.www.protocol.http.HttpURLConnection.level=ALL
|
||||
|
||||
java.util.logging.FileHandler.level = WARNING
|
||||
java.util.logging.FileHandler.filter =
|
||||
java.util.logging.FileHandler.formatter =
|
||||
java.util.logging.FileHandler.encoding =
|
||||
java.util.logging.FileHandler.limit =
|
||||
java.util.logging.FileHandler.count =
|
||||
java.util.logging.FileHandler.append = false
|
||||
java.util.logging.FileHandler.pattern = log.%u.%g.txt
|
||||
|
||||
#java.util.logging.ConsoleHandler.level = ALL
|
||||
java.util.logging.ConsoleHandler.filter =
|
||||
java.util.logging.ConsoleHandler.formatter =
|
||||
java.util.logging.ConsoleHandler.encoding =
|
||||
|
||||
java.util.logging.StreamHandler.level = WARNING
|
||||
java.util.logging.StreamHandler.filter =
|
||||
java.util.logging.StreamHandler.formatter =
|
||||
java.util.logging.StreamHandler.encoding =
|
||||
|
||||
java.util.logging.SocketHandler.level = WARNING
|
||||
java.util.logging.SocketHandler.filter =
|
||||
java.util.logging.SocketHandler.formatter =
|
||||
java.util.logging.SocketHandler.encoding =
|
||||
java.util.logging.SocketHandler.host =
|
||||
java.util.logging.SocketHandler.port =
|
||||
|
||||
java.util.logging.MemoryHandler.level = WARNING
|
||||
java.util.logging.MemoryHandler.filter =
|
||||
java.util.logging.MemoryHandler.size =
|
||||
java.util.logging.MemoryHandler.push =
|
||||
java.util.logging.MemoryHandler.target =
|
31
src/test/resources/test.json
Normal file
31
src/test/resources/test.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"widget": {
|
||||
"debug": "on",
|
||||
"window": {
|
||||
"parent" : null,
|
||||
"title": "Sample Konfabulator Widget",
|
||||
"name": "main_window",
|
||||
"width": 500,
|
||||
"height": 500
|
||||
},
|
||||
"image": {
|
||||
"src": "Images/Sun.png",
|
||||
"name": "sun1",
|
||||
"hOffset": 250,
|
||||
"vOffset": 250,
|
||||
"alignment": "center",
|
||||
"tags" : ["Ireland", "Amazon", "development"],
|
||||
"monochromatic" : false
|
||||
},
|
||||
"text": {
|
||||
"data": "Click Here",
|
||||
"size": 36,
|
||||
"style": "bold",
|
||||
"name": "text1",
|
||||
"hOffset": 250,
|
||||
"vOffset": 100,
|
||||
"alignment": "center",
|
||||
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
|
||||
}
|
||||
}
|
||||
}
|
318
src/test/resources/wordpress.json
Normal file
318
src/test/resources/wordpress.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user