added reference serialization and deserialization in both json (text) and jbon (binary) format

This commit is contained in:
2019-07-26 12:42:35 +01:00
parent 89fb8ac1b7
commit ff9b26ed93
14 changed files with 426 additions and 100 deletions

View File

@@ -26,10 +26,6 @@ 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
}
enablePlugins(Antlr4Plugin)
antlr4Version in Antlr4 := "4.7.1"
antlr4PackageName in Antlr4 := Some("net.woggioni.worth.antlr")

View File

@@ -2,8 +2,11 @@ package net.woggioni.worth.serialization;
import lombok.RequiredArgsConstructor;
import net.woggioni.worth.exception.NotImplementedException;
import net.woggioni.worth.value.ArrayValue;
import net.woggioni.worth.value.ObjectValue;
import net.woggioni.worth.traversal.TraversalContext;
import net.woggioni.worth.traversal.ValueIdentity;
import net.woggioni.worth.traversal.ValueVisitor;
import net.woggioni.worth.traversal.ValueWalker;
import net.woggioni.worth.value.*;
import net.woggioni.worth.xface.Dumper;
import net.woggioni.worth.xface.Value;
@@ -11,9 +14,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.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class ValueDumper implements Dumper {
@@ -25,6 +28,47 @@ public abstract class ValueDumper implements Dumper {
public final Value value;
}
protected Map<ValueIdentity, Integer> getIdMap(Value root) {
Map<ValueIdentity, Integer> occurrencies = new HashMap<>();
ValueVisitor visitor = new ValueVisitor(){
private void visit(Value v) {
ValueIdentity identity = new ValueIdentity(v);
Integer i = occurrencies.getOrDefault(identity, 0);
occurrencies.put(identity, ++i);
}
@Override
public boolean filter(Value value, TraversalContext ctx) {
if(value.type() == Value.Type.ARRAY || value.type() == Value.Type.OBJECT) {
ValueIdentity identity = new ValueIdentity(value);
Integer i = occurrencies.getOrDefault(identity, 0);
occurrencies.put(identity, ++i);
return i == 1;
} else {
return true;
}
}
@Override
public void visit(ObjectValue value, TraversalContext ctx) {
visit(value);
}
@Override
public void visit(ArrayValue value, TraversalContext ctx) {
visit(value);
}
};
ValueWalker.walk(root, visitor);
Map<ValueIdentity, Integer> result = new HashMap<>();
int i = 0;
for(ValueIdentity identity : occurrencies.keySet()) {
result.put(identity, i++);
}
return result;
}
protected static class ArrayStackLevel extends StackLevel implements Iterator<Value> {
private final Iterator<Value> iterator = ((ArrayValue) value).iterator();
@@ -110,4 +154,8 @@ public abstract class ValueDumper implements Dumper {
protected abstract void booleanValue(boolean value);
protected abstract void nullValue();
protected abstract void valueId(int id);
protected abstract void valueReference(int id);
}

View File

@@ -3,6 +3,7 @@ package net.woggioni.worth.serialization;
import lombok.RequiredArgsConstructor;
import net.woggioni.worth.exception.MaxDepthExceededException;
import net.woggioni.worth.exception.NotImplementedException;
import net.woggioni.worth.exception.ParseException;
import net.woggioni.worth.utils.WorthUtils;
import net.woggioni.worth.value.*;
import net.woggioni.worth.xface.Parser;
@@ -11,8 +12,12 @@ import net.woggioni.worth.xface.Value;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Array;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import static net.woggioni.worth.utils.WorthUtils.newThrowable;
@@ -27,9 +32,6 @@ public class ValueParser implements Parser {
}
protected static class ArrayStackLevel extends StackLevel {
public ArrayStackLevel() {
super(new ArrayValue(), -1);
}
public ArrayStackLevel(long expectedSize) {
super(new ArrayValue(), expectedSize);
}
@@ -38,16 +40,13 @@ public class ValueParser implements Parser {
protected static class ObjectStackLevel extends StackLevel {
public String currentKey;
public ObjectStackLevel(Value.Configuration cfg) {
super(ObjectValue.newInstance(cfg), -1);
}
public ObjectStackLevel(Value.Configuration cfg, long expectedSize) {
super(ObjectValue.newInstance(cfg), expectedSize);
}
}
protected ArrayDeque<StackLevel> stack;
protected Map<Integer, Value> idMap;
private void add2Last(Value value) {
StackLevel last = stack.getFirst();
@@ -67,17 +66,20 @@ public class ValueParser implements Parser {
protected ValueParser(Value.Configuration cfg) {
this.cfg = cfg;
if (cfg.serializeReferences) {
idMap = new HashMap<>();
}
stack = new ArrayDeque<>() {
@Override
public void push(StackLevel stackLevel) {
if(size() == cfg.maxDepth) {
if (size() == cfg.maxDepth) {
throw newThrowable(MaxDepthExceededException.class,
"Objects is too deep, max allowed depth is %d", cfg.maxDepth);
"Objects is too deep, max allowed depth is %d", cfg.maxDepth);
}
super.push(stackLevel);
}
};
stack.push(new ArrayStackLevel());
stack.push(new ArrayStackLevel(-1));
}
@Override
@@ -95,29 +97,40 @@ public class ValueParser implements Parser {
return parse(new InputStreamReader(stream, encoding));
}
protected void beginObject() {
stack.push(new ObjectStackLevel(cfg));
protected Value beginObject() {
return beginObject(-1);
}
protected void beginObject(long size) {
stack.push(new ObjectStackLevel(cfg, size));
protected Value beginObject(long size) {
ObjectStackLevel osl = new ObjectStackLevel(cfg, size);
stack.push(osl);
return osl.value;
}
protected void endObject() {
add2Last(stack.pop().value);
ValueParser.StackLevel sl = stack.pop();
if(!(sl instanceof ValueParser.ObjectStackLevel)) {
error(ParseException::new, "Unexpected object terminator");
}
add2Last(sl.value);
}
protected void beginArray() {
stack.push(new ArrayStackLevel());
protected Value beginArray() {
return beginArray(-1);
}
protected void beginArray(long size) {
stack.push(new ArrayStackLevel(size));
protected Value beginArray(long size) {
ArrayStackLevel ale = new ArrayStackLevel(size);
stack.push(ale);
return ale.value;
}
protected void endArray() {
add2Last(stack.pop().value);
ValueParser.StackLevel sl = stack.pop();
if(!(sl instanceof ValueParser.ArrayStackLevel)) {
error(ParseException::new, "Unexpected array terminator");
}
add2Last(sl.value);
}
protected void objectKey(String key) {
@@ -144,4 +157,20 @@ public class ValueParser implements Parser {
protected void nullValue() {
add2Last(Value.Null);
}
protected void valueId(int id, Value value) {
idMap.put(id, value);
}
protected void valueReference(int id) {
Value referencedValue = idMap.get(id);
if (referencedValue == null) {
error(ParseException::new, "got invalid id '%d'", id);
}
add2Last(referencedValue);
}
protected <T extends RuntimeException> T error(Function<String, T> constructor, String fmt, Object... args) {
throw new NotImplementedException("Method not implemented");
}
}

View File

@@ -4,6 +4,7 @@ import lombok.SneakyThrows;
import net.woggioni.worth.exception.NotImplementedException;
import net.woggioni.worth.serialization.ValueDumper;
import net.woggioni.worth.serialization.json.JSONDumper;
import net.woggioni.worth.traversal.ValueIdentity;
import net.woggioni.worth.utils.Leb128;
import net.woggioni.worth.utils.WorthUtils;
import net.woggioni.worth.value.ArrayValue;
@@ -13,7 +14,9 @@ import net.woggioni.worth.xface.Value;
import java.io.OutputStream;
import java.io.Writer;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
public class JBONDumper extends ValueDumper {
@@ -44,8 +47,18 @@ public class JBONDumper extends ValueDumper {
@Override
@SneakyThrows
public void dump(Value value, OutputStream outputStream) {
Map<ValueIdentity, Integer> ids;
Set<Integer> dumpedId;
if(cfg.serializeReferences) {
ids = getIdMap(value);
dumpedId = new HashSet<>();
} else {
ids = null;
dumpedId = null;
}
this.os = outputStream;
final Consumer<Value> handle_value = (v) -> {
Integer id;
switch (v.type()) {
case NULL:
nullValue();
@@ -64,13 +77,33 @@ public class JBONDumper extends ValueDumper {
break;
case ARRAY:
ArrayValue arrayValue = WorthUtils.dynamicCast(v, ArrayValue.class);
stack.push(new ArrayStackLevel(arrayValue));
beginArray(arrayValue.size());
if(ids != null && (id = ids.get(new ValueIdentity(arrayValue))) != null) {
if(dumpedId.add(id)) {
stack.push(new ArrayStackLevel(arrayValue));
valueId(id);
beginArray(arrayValue.size());
} else {
valueReference(id);
}
} else {
stack.push(new ArrayStackLevel(arrayValue));
beginArray(arrayValue.size());
}
break;
case OBJECT:
ObjectValue objectValue = WorthUtils.dynamicCast(v, ObjectValue.class);
stack.push(new ObjectStackLevel(objectValue));
beginObject(objectValue.size());
if(ids != null && (id = ids.get(new ValueIdentity(objectValue))) != null) {
if(dumpedId.add(id)) {
stack.push(new ObjectStackLevel(WorthUtils.dynamicCast(v, ObjectValue.class)));
valueId(id);
beginObject(objectValue.size());
} else {
valueReference(id);
}
} else {
stack.push(new ObjectStackLevel(WorthUtils.dynamicCast(v, ObjectValue.class)));
beginObject(objectValue.size());
}
break;
}
};
@@ -191,4 +224,18 @@ public class JBONDumper extends ValueDumper {
protected void nullValue() {
os.write(BinaryMarker.Null.value);
}
@Override
@SneakyThrows
protected void valueReference(int id) {
os.write(BinaryMarker.Reference.value);
Leb128.encode(os, id);
}
@Override
@SneakyThrows
protected void valueId(int id) {
os.write(BinaryMarker.Id.value);
Leb128.encode(os, id);
}
}

View File

@@ -18,7 +18,8 @@ public class JBONParser extends ValueParser {
private int cursor = 0;
private <T extends RuntimeException> T error(Function<String, T> constructor, String fmt, Object... args) {
@Override
protected <T extends RuntimeException> T error(Function<String, T> constructor, String fmt, Object... args) {
return constructor.apply(
String.format("Error at position %d: %s",
cursor, String.format(fmt, args)));
@@ -38,6 +39,7 @@ public class JBONParser extends ValueParser {
stream.read();
try {
Integer currentId = null;
Leb128.Leb128Decoder decoder = new Leb128.Leb128Decoder(stream);
ObjectStackLevel osl;
while (true) {
@@ -52,7 +54,11 @@ public class JBONParser extends ValueParser {
if(c == -1) {
break;
}
if(c == BinaryMarker.Null.value) {
if(idMap != null && c == BinaryMarker.Id.value) {
currentId = (int) decoder.decode();
} else if(idMap != null && c == BinaryMarker.Reference.value) {
valueReference((int) decoder.decode());
} else if(c == BinaryMarker.Null.value) {
nullValue();
} else if(c == BinaryMarker.True.value) {
booleanValue(true);
@@ -75,17 +81,26 @@ public class JBONParser extends ValueParser {
stream.read(buffer);
String text = new String(buffer);
stringValue(text);
} else if(c == BinaryMarker.EmptyArray.value) {
beginArray(0);
} else if(c > BinaryMarker.EmptyArray.value && c < BinaryMarker.LargeArray.value) {
beginArray(c - BinaryMarker.EmptyArray.value);
} else if(c == BinaryMarker.LargeArray.value) {
long size = decoder.decode();
beginArray(size);
} else if(c == BinaryMarker.EmptyObject.value) {
beginObject(0);
} else if(c > BinaryMarker.EmptyObject.value && c < BinaryMarker.LargeObject.value) {
beginObject(c - BinaryMarker.EmptyObject.value);
} else if(c >= BinaryMarker.EmptyArray.value && c <= BinaryMarker.LargeArray.value) {
long size;
if(c == BinaryMarker.LargeArray.value) {
size = decoder.decode();
} else {
size = c - BinaryMarker.EmptyArray.value;
}
Value newArray = beginArray(size);
if(currentId != null) valueId(currentId, newArray);
currentId = null;
} else if(c >= BinaryMarker.EmptyObject.value && c <= BinaryMarker.LargeObject.value) {
long size;
if(c == BinaryMarker.LargeObject.value) {
size = decoder.decode();
} else {
size = c - BinaryMarker.EmptyObject.value;
}
Value newObject = beginObject(size);
if(currentId != null) valueId(currentId, newObject);
currentId = null;
} else if(c == BinaryMarker.LargeObject.value) {
long size = decoder.decode();
beginObject(size);

View File

@@ -2,17 +2,21 @@ package net.woggioni.worth.serialization.json;
import lombok.SneakyThrows;
import net.woggioni.worth.serialization.ValueDumper;
import net.woggioni.worth.traversal.ValueIdentity;
import net.woggioni.worth.utils.WorthUtils;
import net.woggioni.worth.value.ArrayValue;
import net.woggioni.worth.value.ObjectValue;
import net.woggioni.worth.value.*;
import net.woggioni.worth.xface.Dumper;
import net.woggioni.worth.xface.Value;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class JSONDumper extends ValueDumper {
@@ -73,8 +77,18 @@ public class JSONDumper extends ValueDumper {
@Override
@SneakyThrows
public void dump(Value value, Writer writer) {
Map<ValueIdentity, Integer> ids;
Set<Integer> dumpedId;
if(cfg.serializeReferences) {
ids = getIdMap(value);
dumpedId = new HashSet<>();
} else {
ids = null;
dumpedId = null;
}
this.writer = writer;
final Consumer<Value> handle_value = (v) -> {
Integer id;
switch (v.type()) {
case NULL:
nullValue();
@@ -93,13 +107,33 @@ public class JSONDumper extends ValueDumper {
break;
case ARRAY:
ArrayValue arrayValue = WorthUtils.dynamicCast(v, ArrayValue.class);
stack.push(new ArrayStackLevel(arrayValue));
beginArray(arrayValue.size());
if(ids != null && (id = ids.get(new ValueIdentity(arrayValue))) != null) {
if(dumpedId.add(id)) {
stack.push(new ArrayStackLevel(arrayValue));
valueId(id);
beginArray(arrayValue.size());
} else {
valueReference(id);
}
} else {
stack.push(new ArrayStackLevel(arrayValue));
beginArray(arrayValue.size());
}
break;
case OBJECT:
ObjectValue objectValue = WorthUtils.dynamicCast(v, ObjectValue.class);
stack.push(new ObjectStackLevel(WorthUtils.dynamicCast(v, ObjectValue.class)));
beginObject(objectValue.size());
if(ids != null && (id = ids.get(new ValueIdentity(objectValue))) != null) {
if(dumpedId.add(id)) {
stack.push(new ObjectStackLevel(WorthUtils.dynamicCast(v, ObjectValue.class)));
valueId(id);
beginObject(objectValue.size());
} else {
valueReference(id);
}
} else {
stack.push(new ObjectStackLevel(WorthUtils.dynamicCast(v, ObjectValue.class)));
beginObject(objectValue.size());
}
break;
}
};
@@ -197,4 +231,16 @@ public class JSONDumper extends ValueDumper {
protected void nullValue() {
this.writer.write("null");
}
@Override
@SneakyThrows
protected void valueId(int id) {
this.writer.write("(" + id + ")");
}
@Override
@SneakyThrows
protected void valueReference(int id) {
this.writer.write("$" + id);
}
}

View File

@@ -48,7 +48,7 @@ public class JSONParser extends ValueParser {
return result;
}
private final void parseNumber(LookAheadTextInputStream stream) {
private final String parseNumber(LookAheadTextInputStream stream) {
StringBuilder sb = new StringBuilder();
while (true) {
int b = stream.getCurrentByte();
@@ -61,12 +61,31 @@ public class JSONParser extends ValueParser {
}
stream.read();
}
String text = sb.toString();
if (text.indexOf('.') > 0) {
floatValue(Double.valueOf(text));
} else {
integerValue(Long.valueOf(text));
return sb.toString();
}
private final int parseId(LookAheadTextInputStream stream) {
StringBuilder sb = new StringBuilder();
boolean digitsStarted = false;
boolean digitsEnded = false;
while (true) {
int b = stream.getCurrentByte();
if(b == '(' ) {
} else if(Character.isWhitespace(b)) {
if(digitsStarted) digitsEnded = true;
} else if (b < 0 || b == ')') {
break;
} else if (isDecimal(b)) {
if(digitsEnded) {
error(ParseException::new, "error parsing id");
} else {
digitsStarted = true;
sb.appendCodePoint(b);
}
}
stream.read();
}
return Integer.valueOf(sb.toString());
}
private String readString(LookAheadTextInputStream stream) {
@@ -131,10 +150,10 @@ public class JSONParser extends ValueParser {
}
}
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)));
@Override
protected <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() {
@@ -175,22 +194,34 @@ public class JSONParser extends ValueParser {
};
try {
Integer currentId = null;
while (true) {
int c = stream.getCurrentByte();
if (c == -1) {
break;
} else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
} else if (c == '(') {
currentId = parseId(stream);
} else if (c == '{') {
beginObject();
Value newObject = beginObject();
if(currentId != null) valueId(currentId, newObject);
currentId = null;
} else if (c == '}') {
endObject();
} else if (c == '[') {
beginArray();
Value newArray = beginArray();
if(currentId != null) valueId(currentId, newArray);
currentId = null;
} else if (c == ']') {
endArray();
} else if (isDecimal(c)) {
try {
parseNumber(stream);
String text = parseNumber(stream);
if (text.indexOf('.') > 0) {
floatValue(Double.valueOf(text));
} else {
integerValue(Long.valueOf(text));
}
continue;
} catch (NumberFormatException nfe) {
throw error(ParseException::new, nfe.getMessage());
@@ -212,6 +243,11 @@ public class JSONParser extends ValueParser {
} else if (c == 'n') {
consumeExpected(stream, "null", "Unrecognized null value");
nullValue();
} else if (idMap != null && c == '$') {
stream.read();
String text = parseNumber(stream);
valueReference(Integer.valueOf(text));
continue;
}
stream.read();
}

View File

@@ -0,0 +1,26 @@
package net.woggioni.worth.traversal;
import lombok.RequiredArgsConstructor;
import net.woggioni.worth.xface.Value;
@RequiredArgsConstructor
public class ValueIdentity {
private final Value value;
@Override
public int hashCode() {
return System.identityHashCode(value);
}
@Override
public boolean equals(Object other) {
if(other == null) {
return false;
} else if(!(other instanceof ValueIdentity)) {
return false;
} else {
return value == ((ValueIdentity) other).value;
}
}
}

View File

@@ -1,14 +1,16 @@
package net.woggioni.worth.traversal;
import net.woggioni.worth.value.*;
import net.woggioni.worth.xface.Value;
public interface ValueVisitor {
void visit(ObjectValue value, TraversalContext ctx);
void visit(ArrayValue value, TraversalContext ctx);
void visit(BooleanValue value, TraversalContext ctx);
void visit(StringValue value, TraversalContext ctx);
void visit(IntegerValue value, TraversalContext ctx);
void visit(FloatValue value, TraversalContext ctx);
void visit(NullValue value, TraversalContext ctx);
default void visit(ObjectValue value, TraversalContext ctx) {}
default void visit(ArrayValue value, TraversalContext ctx) {}
default void visit(BooleanValue value, TraversalContext ctx) {}
default void visit(StringValue value, TraversalContext ctx) {}
default void visit(IntegerValue value, TraversalContext ctx) {}
default void visit(FloatValue value, TraversalContext ctx) {}
default void visit(NullValue value, TraversalContext ctx) {}
default boolean filter(Value value, TraversalContext ctx) { return true; }
}

View File

@@ -108,28 +108,30 @@ public class ValueWalker {
stack.add(ase);
while(true) {
Value currentValue = stack.get(stack.size() - 1).next();
if((av = dynamicCast(currentValue, ArrayValue.class)) != null) {
ase = new ArrayStackElement(av);
stack.add(ase);
} else if((ov = dynamicCast(currentValue, ObjectValue.class)) != null) {
ObjectStackElement ose = new ObjectStackElement(ov);
stack.add(ose);
} else {
IntegerValue iv;
BooleanValue bv;
NullValue nv;
FloatValue fv;
StringValue sv;
if((iv = dynamicCast(currentValue, IntegerValue.class)) != null) {
visitor.visit(iv, ctx);
} else if((fv = dynamicCast(currentValue, FloatValue.class)) != null) {
visitor.visit(fv, ctx);
} else if((bv = dynamicCast(currentValue, BooleanValue.class)) != null) {
visitor.visit(bv, ctx);
} else if ((sv = dynamicCast(currentValue, StringValue.class)) != null) {
visitor.visit(sv, ctx);
} else if ((nv = dynamicCast(currentValue, NullValue.class)) != null) {
visitor.visit(nv, ctx);
if(visitor.filter(currentValue, ctx)) {
if ((av = dynamicCast(currentValue, ArrayValue.class)) != null) {
ase = new ArrayStackElement(av);
stack.add(ase);
} else if ((ov = dynamicCast(currentValue, ObjectValue.class)) != null) {
ObjectStackElement ose = new ObjectStackElement(ov);
stack.add(ose);
} else {
IntegerValue iv;
BooleanValue bv;
NullValue nv;
FloatValue fv;
StringValue sv;
if ((iv = dynamicCast(currentValue, IntegerValue.class)) != null) {
visitor.visit(iv, ctx);
} else if ((fv = dynamicCast(currentValue, FloatValue.class)) != null) {
visitor.visit(fv, ctx);
} else if ((bv = dynamicCast(currentValue, BooleanValue.class)) != null) {
visitor.visit(bv, ctx);
} else if ((sv = dynamicCast(currentValue, StringValue.class)) != null) {
visitor.visit(sv, ctx);
} else if ((nv = dynamicCast(currentValue, NullValue.class)) != null) {
visitor.visit(nv, ctx);
}
}
}
while(true) {

View File

@@ -5,6 +5,8 @@ import net.woggioni.worth.xface.Value;
import java.io.*;
import java.lang.reflect.Constructor;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -27,20 +29,27 @@ public class WorthUtils {
}
public static void writeObject2File(String fileName, Object o) {
writeObject2File(new File(fileName), o);
writeObject2File(Paths.get(fileName), o);
}
@SneakyThrows
public static void writeObject2File(File file, Object o) {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file.getPath()))) {
public static void writeObject2File(Path file, Object o) {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file.toString()))) {
writer.write(o.toString());
}
}
@SneakyThrows
public static String readFile2String(File file) {
public static void writeBytes2File(Path file, byte[] bytes) {
try (OutputStream os = new FileOutputStream(file.toString())) {
os.write(bytes);
}
}
@SneakyThrows
public static String readFile2String(Path file) {
StringBuilder builder = new StringBuilder();
try (Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file.getPath())))) {
try (Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file.toString())))) {
char[] buffer = new char[1024];
while (true) {
int read = reader.read(buffer);

View File

@@ -100,6 +100,11 @@ public interface Value {
@Builder.Default
public final int maxDepth =
Integer.parseInt(System.getProperty(Value.class.getName() + ".maxDepth", "1048576"));
@Builder.Default
public final boolean serializeReferences =
Boolean.parseBoolean(System.getProperty(
Value.class.getName() + ".serializeReferences", "false"));
}
Configuration configuration = Configuration.builder().build();

View File

@@ -0,0 +1,62 @@
package net.woggioni.worth.serialization;
import lombok.SneakyThrows;
import net.woggioni.worth.serialization.binary.JBONDumper;
import net.woggioni.worth.serialization.binary.JBONParser;
import net.woggioni.worth.serialization.json.JSONDumper;
import net.woggioni.worth.serialization.json.JSONParser;
import net.woggioni.worth.utils.WorthUtils;
import net.woggioni.worth.value.IntegerValue;
import net.woggioni.worth.value.ObjectValue;
import net.woggioni.worth.xface.Dumper;
import net.woggioni.worth.xface.Parser;
import net.woggioni.worth.xface.Value;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.file.Paths;
import java.util.function.Function;
public class ReferenceTest {
@SneakyThrows
private void common(Function<Value.Configuration, Dumper> dumperConstructor,
Function<Value.Configuration, Parser> parserConstructor) {
Value.Configuration cfg = Value.Configuration.builder()
.serializeReferences(true)
.objectValueImplementation(ObjectValue.Implementation.HashMap)
.build();
Value value = ObjectValue.newInstance(cfg);
value.put("child", value);
value.put("id", new IntegerValue(25));
byte[] bytes;
try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
Dumper dumper = dumperConstructor.apply(cfg);
dumper.dump(value, baos);
bytes = baos.toByteArray();
}
WorthUtils.writeBytes2File(Paths.get("/tmp/ciao.jbon"), bytes);
Value reparsedValue;
try(ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
Parser parser = parserConstructor.apply(cfg);
reparsedValue = parser.parse(bais);
}
Assert.assertEquals(reparsedValue, reparsedValue.get("child"));
Assert.assertEquals(value.get("id"), reparsedValue.get("id"));
}
@Test
@SneakyThrows
public void json() {
common(JSONDumper::new, JSONParser::new);
}
@Test
@SneakyThrows
public void jbon() {
common(JBONDumper::new, JBONParser::new);
}
}

View File

@@ -8,7 +8,9 @@ import net.woggioni.worth.buffer.LookAheadTextInputStream;
import net.woggioni.worth.exception.NotImplementedException;
import net.woggioni.worth.utils.WorthUtils;
import net.woggioni.worth.value.ArrayValue;
import net.woggioni.worth.value.IntegerValue;
import net.woggioni.worth.value.ObjectValue;
import net.woggioni.worth.xface.Dumper;
import net.woggioni.worth.xface.Parser;
import net.woggioni.worth.xface.Value;
import org.junit.Assert;
@@ -20,6 +22,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
public class JSONTest {