added reference serialization and deserialization in both json (text) and jbon (binary) format
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
@@ -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");
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
Reference in New Issue
Block a user