added binary serialization format
This commit is contained in:
@@ -9,7 +9,7 @@ public class LookAheadInputStream extends InputStream {
|
||||
private final InputStream stream;
|
||||
private int currentByte;
|
||||
|
||||
LookAheadInputStream(InputStream stream) {
|
||||
public LookAheadInputStream(InputStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class LookAheadInputStream extends InputStream {
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getCurrentByte(){
|
||||
public int getCurrentByte() {
|
||||
return currentByte;
|
||||
}
|
||||
}
|
||||
|
@@ -11,9 +11,9 @@ import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
public abstract class ValueDumper implements Dumper {
|
||||
|
||||
@@ -62,10 +62,10 @@ public abstract class ValueDumper implements Dumper {
|
||||
}
|
||||
|
||||
|
||||
protected Stack<StackLevel> stack;
|
||||
protected ArrayDeque<StackLevel> stack;
|
||||
|
||||
protected ValueDumper() {
|
||||
stack = new Stack<>();
|
||||
stack = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,11 +83,11 @@ public abstract class ValueDumper implements Dumper {
|
||||
dump(value, new OutputStreamWriter(stream, encoding));
|
||||
}
|
||||
|
||||
protected abstract void beginObject();
|
||||
protected abstract void beginObject(int size);
|
||||
|
||||
protected abstract void endObject();
|
||||
|
||||
protected abstract void beginArray();
|
||||
protected abstract void beginArray(int size);
|
||||
|
||||
protected abstract void endArray();
|
||||
|
||||
|
@@ -3,12 +3,7 @@ package org.oggio88.worth.serialization;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.oggio88.worth.exception.NotImplementedException;
|
||||
import org.oggio88.worth.utils.WorthUtils;
|
||||
import org.oggio88.worth.value.ArrayValue;
|
||||
import org.oggio88.worth.value.BooleanValue;
|
||||
import org.oggio88.worth.value.FloatValue;
|
||||
import org.oggio88.worth.value.IntegerValue;
|
||||
import org.oggio88.worth.value.ObjectValue;
|
||||
import org.oggio88.worth.value.StringValue;
|
||||
import org.oggio88.worth.value.*;
|
||||
import org.oggio88.worth.xface.Parser;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
@@ -16,18 +11,22 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Stack;
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
public class ValueParser implements Parser {
|
||||
|
||||
@RequiredArgsConstructor
|
||||
protected static class StackLevel {
|
||||
public final Value value;
|
||||
public final long expectedSize;
|
||||
}
|
||||
|
||||
protected static class ArrayStackLevel extends StackLevel {
|
||||
public ArrayStackLevel() {
|
||||
super(new ArrayValue());
|
||||
super(new ArrayValue(), -1);
|
||||
}
|
||||
public ArrayStackLevel(long expectedSize) {
|
||||
super(new ArrayValue(), expectedSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,14 +34,18 @@ public class ValueParser implements Parser {
|
||||
public String currentKey;
|
||||
|
||||
public ObjectStackLevel() {
|
||||
super(ObjectValue.newInstance());
|
||||
super(ObjectValue.newInstance(), -1);
|
||||
}
|
||||
|
||||
public ObjectStackLevel(long expectedSize) {
|
||||
super(ObjectValue.newInstance(), expectedSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected Stack<StackLevel> stack;
|
||||
protected ArrayDeque<StackLevel> stack;
|
||||
|
||||
private void add2Last(Value value) {
|
||||
StackLevel last = stack.lastElement();
|
||||
StackLevel last = stack.getFirst();
|
||||
ArrayStackLevel asl;
|
||||
ObjectStackLevel osl;
|
||||
if ((asl = WorthUtils.dynamicCast(last, ArrayStackLevel.class)) != null)
|
||||
@@ -54,7 +57,7 @@ public class ValueParser implements Parser {
|
||||
}
|
||||
|
||||
protected ValueParser() {
|
||||
stack = new Stack<>();
|
||||
stack = new ArrayDeque<>();
|
||||
stack.push(new ArrayStackLevel());
|
||||
}
|
||||
|
||||
@@ -77,6 +80,11 @@ public class ValueParser implements Parser {
|
||||
stack.push(new ObjectStackLevel());
|
||||
}
|
||||
|
||||
protected void beginObject(long size) {
|
||||
stack.push(new ObjectStackLevel(size));
|
||||
}
|
||||
|
||||
|
||||
protected void endObject() {
|
||||
add2Last(stack.pop().value);
|
||||
}
|
||||
@@ -85,12 +93,16 @@ public class ValueParser implements Parser {
|
||||
stack.push(new ArrayStackLevel());
|
||||
}
|
||||
|
||||
protected void beginArray(long size) {
|
||||
stack.push(new ArrayStackLevel(size));
|
||||
}
|
||||
|
||||
protected void endArray() {
|
||||
add2Last(stack.pop().value);
|
||||
}
|
||||
|
||||
protected void objectKey(String key) {
|
||||
ObjectStackLevel osl = (ObjectStackLevel) stack.lastElement();
|
||||
ObjectStackLevel osl = (ObjectStackLevel) stack.getFirst();
|
||||
osl.currentKey = key;
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,22 @@
|
||||
package org.oggio88.worth.serialization.binary;
|
||||
|
||||
public enum BinaryMarker {
|
||||
Float(0x0),
|
||||
Int(0x1),
|
||||
Null(0x2),
|
||||
True(0x3),
|
||||
False(0x4),
|
||||
Reference(0x5),
|
||||
Id(0x6),
|
||||
EmptyString(0x0d),
|
||||
LargeString(0x5d),
|
||||
EmptyObject(0x5e),
|
||||
LargeObject(0xae),
|
||||
EmptyArray(0xaf),
|
||||
LargeArray(0xff);
|
||||
|
||||
public byte value;
|
||||
BinaryMarker(int b) {
|
||||
value = (byte) b;
|
||||
}
|
||||
}
|
@@ -0,0 +1,175 @@
|
||||
package org.oggio88.worth.serialization.binary;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.oggio88.worth.exception.NotImplementedException;
|
||||
import org.oggio88.worth.serialization.ValueDumper;
|
||||
import org.oggio88.worth.utils.Leb128;
|
||||
import org.oggio88.worth.value.ArrayValue;
|
||||
import org.oggio88.worth.value.ObjectValue;
|
||||
import org.oggio88.worth.xface.Dumper;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.oggio88.worth.utils.WorthUtils.dynamicCast;
|
||||
|
||||
public class JBONDumper extends ValueDumper {
|
||||
|
||||
public static Dumper newInstance() {
|
||||
return new JBONDumper();
|
||||
}
|
||||
|
||||
protected OutputStream os;
|
||||
|
||||
@Override
|
||||
public void dump(Value value, Writer writer) {
|
||||
throw new NotImplementedException(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void dump(Value value, OutputStream outputStream) {
|
||||
this.os = outputStream;
|
||||
final Consumer<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:
|
||||
ArrayValue arrayValue = dynamicCast(v, ArrayValue.class);
|
||||
stack.push(new ArrayStackLevel(arrayValue));
|
||||
beginArray(arrayValue.size());
|
||||
break;
|
||||
case OBJECT:
|
||||
ObjectValue objectValue = dynamicCast(v, ObjectValue.class);
|
||||
stack.push(new ObjectStackLevel(objectValue));
|
||||
beginObject(objectValue.size());
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handle_value.accept(value);
|
||||
while (stack.size() > 0) {
|
||||
StackLevel last = stack.getFirst();
|
||||
ArrayStackLevel arrayStackLevel;
|
||||
ObjectStackLevel objectStackLevel;
|
||||
if ((arrayStackLevel = dynamicCast(last, ArrayStackLevel.class)) != null) {
|
||||
if (arrayStackLevel.hasNext()) {
|
||||
handle_value.accept(arrayStackLevel.next());
|
||||
} else {
|
||||
endArray();
|
||||
stack.pop();
|
||||
}
|
||||
} else if ((objectStackLevel = dynamicCast(last, ObjectStackLevel.class)) != null) {
|
||||
if (objectStackLevel.hasNext()) {
|
||||
Map.Entry<String, Value> entry = objectStackLevel.next();
|
||||
objectKey(entry.getKey());
|
||||
handle_value.accept(entry.getValue());
|
||||
} else {
|
||||
endObject();
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.os.flush();
|
||||
this.os = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void beginObject(int size) {
|
||||
if(size == 0) {
|
||||
os.write(BinaryMarker.EmptyObject.value);
|
||||
} else {
|
||||
os.write(BinaryMarker.LargeObject.value);
|
||||
Leb128.encode(os, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void endObject() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void beginArray(int size) {
|
||||
if(size == 0) {
|
||||
os.write(BinaryMarker.EmptyArray.value);
|
||||
} else {
|
||||
os.write(BinaryMarker.LargeArray.value);
|
||||
Leb128.encode(os, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void endArray() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void objectKey(String key) {
|
||||
byte[] bytes = key.getBytes();
|
||||
Leb128.encode(os, bytes.length);
|
||||
os.write(key.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void stringValue(String value) {
|
||||
if(value.isEmpty()) {
|
||||
os.write(BinaryMarker.EmptyString.value);
|
||||
} else {
|
||||
os.write(BinaryMarker.LargeString.value);
|
||||
byte[] bytes = value.getBytes();
|
||||
Leb128.encode(os, bytes.length);
|
||||
os.write(value.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void integerValue(long value) {
|
||||
os.write(BinaryMarker.Int.value);
|
||||
Leb128.encode(os, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void floatValue(double value) {
|
||||
os.write(BinaryMarker.Float.value);
|
||||
Leb128.encode(os, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void booleanValue(boolean value) {
|
||||
if(value) {
|
||||
os.write(BinaryMarker.True.value);
|
||||
} else {
|
||||
os.write(BinaryMarker.False.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void nullValue() {
|
||||
os.write(BinaryMarker.Null.value);
|
||||
}
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
package org.oggio88.worth.serialization.binary;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.oggio88.worth.buffer.LookAheadInputStream;
|
||||
import org.oggio88.worth.exception.ParseException;
|
||||
import org.oggio88.worth.serialization.ValueParser;
|
||||
import org.oggio88.worth.utils.Leb128;
|
||||
import org.oggio88.worth.utils.WorthUtils;
|
||||
import org.oggio88.worth.xface.Parser;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class JBONParser extends ValueParser {
|
||||
|
||||
private int cursor = 0;
|
||||
|
||||
|
||||
private <T 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)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Value parse(InputStream is) {
|
||||
final LookAheadInputStream stream = new LookAheadInputStream(is) {
|
||||
@Override
|
||||
public int read() {
|
||||
int result = super.read();
|
||||
++cursor;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
stream.read();
|
||||
|
||||
try {
|
||||
Leb128.Leb128Decoder decoder = new Leb128.Leb128Decoder(stream);
|
||||
ObjectStackLevel osl;
|
||||
while (true) {
|
||||
if ((osl = WorthUtils.dynamicCast(stack.getFirst(), ObjectStackLevel.class)) != null && osl.currentKey == null) {
|
||||
int size = (int) decoder.decode();
|
||||
byte[] buffer = new byte[size];
|
||||
stream.read(buffer);
|
||||
String text = new String(buffer);
|
||||
objectKey(text);
|
||||
}
|
||||
byte b;
|
||||
{
|
||||
int c = stream.read();
|
||||
if(c == -1) {
|
||||
break;
|
||||
}
|
||||
b = (byte) c;
|
||||
}
|
||||
if(b == BinaryMarker.Null.value) {
|
||||
nullValue();
|
||||
} else if(b == BinaryMarker.True.value) {
|
||||
booleanValue(true);
|
||||
} else if(b == BinaryMarker.False.value) {
|
||||
booleanValue(false);
|
||||
} else if(b == BinaryMarker.Int.value) {
|
||||
integerValue(decoder.decode());
|
||||
} else if(b == BinaryMarker.Float.value) {
|
||||
floatValue(decoder.decodeDouble());
|
||||
} else if(b == BinaryMarker.EmptyString.value) {
|
||||
stringValue("");
|
||||
} else if(b == BinaryMarker.LargeString.value) {
|
||||
byte[] buffer = new byte[(int) decoder.decode()];
|
||||
stream.read(buffer);
|
||||
String text = new String(buffer);
|
||||
stringValue(text);
|
||||
} else if(b == BinaryMarker.EmptyArray.value) {
|
||||
beginArray(0);
|
||||
} else if(b == BinaryMarker.LargeArray.value) {
|
||||
long size = decoder.decode();
|
||||
beginArray(size);
|
||||
} else if(b == BinaryMarker.EmptyObject.value) {
|
||||
beginObject(0);
|
||||
} else if(b == BinaryMarker.LargeObject.value) {
|
||||
long size = decoder.decode();
|
||||
beginObject(size);
|
||||
} else {
|
||||
throw new ParseException(String.format("Illegal byte at position %d: 0x%02x", cursor, b));
|
||||
}
|
||||
while(stack.size() > 0) {
|
||||
if ((osl = WorthUtils.dynamicCast(stack.getFirst(), ObjectStackLevel.class)) != null
|
||||
&& osl.value.size() == osl.expectedSize) {
|
||||
endObject();
|
||||
continue;
|
||||
}
|
||||
ArrayStackLevel asl;
|
||||
if((asl = WorthUtils.dynamicCast(stack.getFirst(), ArrayStackLevel.class)) != null
|
||||
&& asl.value.size() == asl.expectedSize) {
|
||||
endArray();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stack.size() > 1) {
|
||||
ValueParser.StackLevel last = stack.getFirst();
|
||||
String type;
|
||||
if(last instanceof ArrayStackLevel) {
|
||||
type = "array";
|
||||
} else {
|
||||
type = "object";
|
||||
}
|
||||
throw error(ParseException::new, "Unfinished %s", type);
|
||||
}
|
||||
return WorthUtils.dynamicCast(stack.getFirst(), ArrayStackLevel.class).value.get(0);
|
||||
} catch (NumberFormatException | NegativeArraySizeException e) {
|
||||
throw error(ParseException::new, e.getMessage());
|
||||
} finally {
|
||||
stack.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static Parser newInstance() {
|
||||
return new JBONParser();
|
||||
}
|
||||
}
|
@@ -21,7 +21,7 @@ public class JSONDumper extends ValueDumper {
|
||||
return new JSONDumper();
|
||||
}
|
||||
|
||||
protected Writer writer;
|
||||
private Writer writer;
|
||||
|
||||
private String escapeString(String value){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -81,19 +81,21 @@ public class JSONDumper extends ValueDumper {
|
||||
stringValue(v.asString());
|
||||
break;
|
||||
case ARRAY:
|
||||
stack.push(new ArrayStackLevel(dynamicCast(v, ArrayValue.class)));
|
||||
beginArray();
|
||||
ArrayValue arrayValue = dynamicCast(v, ArrayValue.class);
|
||||
stack.push(new ArrayStackLevel(arrayValue));
|
||||
beginArray(arrayValue.size());
|
||||
break;
|
||||
case OBJECT:
|
||||
ObjectValue objectValue = dynamicCast(v, ObjectValue.class);
|
||||
stack.push(new ObjectStackLevel(dynamicCast(v, ObjectValue.class)));
|
||||
beginObject();
|
||||
beginObject(objectValue.size());
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handle_value.accept(value);
|
||||
while (stack.size() > 0) {
|
||||
StackLevel last = stack.lastElement();
|
||||
StackLevel last = stack.getFirst();
|
||||
ArrayStackLevel arrayStackLevel;
|
||||
ObjectStackLevel objectStackLevel;
|
||||
if ((arrayStackLevel = dynamicCast(last, ArrayStackLevel.class)) != null) {
|
||||
@@ -127,7 +129,7 @@ public class JSONDumper extends ValueDumper {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void beginObject() {
|
||||
protected void beginObject(int size) {
|
||||
this.writer.write("{");
|
||||
}
|
||||
|
||||
@@ -139,7 +141,7 @@ public class JSONDumper extends ValueDumper {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void beginArray() {
|
||||
protected void beginArray(int size) {
|
||||
this.writer.write("[");
|
||||
}
|
||||
|
||||
|
@@ -69,7 +69,7 @@ public class JSONParser extends ValueParser {
|
||||
}
|
||||
}
|
||||
|
||||
private final String readString(LookAheadTextInputStream stream) {
|
||||
private String readString(LookAheadTextInputStream stream) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean escape = false;
|
||||
boolean start = false;
|
||||
@@ -116,7 +116,7 @@ public class JSONParser extends ValueParser {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private final void consumeExpected(LookAheadTextInputStream stream, String expected, String errorMessage) {
|
||||
private void consumeExpected(LookAheadTextInputStream stream, String expected, String errorMessage) {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
int c = stream.getCurrentByte();
|
||||
@@ -185,11 +185,9 @@ public class JSONParser extends ValueParser {
|
||||
throw error(ParseException::new, nfe.getMessage());
|
||||
}
|
||||
} else if (c == '\"') {
|
||||
if (currentLine == 125)
|
||||
System.out.print("");
|
||||
String text = readString(stream);
|
||||
ObjectStackLevel osl;
|
||||
if ((osl = WorthUtils.dynamicCast(stack.lastElement(), ObjectStackLevel.class)) != null && osl.currentKey == null) {
|
||||
if ((osl = WorthUtils.dynamicCast(stack.getFirst(), ObjectStackLevel.class)) != null && osl.currentKey == null) {
|
||||
objectKey(text);
|
||||
} else {
|
||||
stringValue(text);
|
||||
@@ -208,16 +206,16 @@ public class JSONParser extends ValueParser {
|
||||
}
|
||||
if (stack.size() > 1) {
|
||||
char c;
|
||||
if (stack.lastElement() instanceof ArrayStackLevel) {
|
||||
if (stack.getFirst() instanceof ArrayStackLevel) {
|
||||
c = ']';
|
||||
} else if (stack.lastElement() instanceof ObjectStackLevel) {
|
||||
} else if (stack.getFirst() instanceof ObjectStackLevel) {
|
||||
c = '}';
|
||||
} else {
|
||||
throw new NotImplementedException("This should never happen");
|
||||
}
|
||||
throw error(ParseException::new, "Missing '%c' token", c);
|
||||
}
|
||||
return WorthUtils.dynamicCast(stack.lastElement(), ArrayStackLevel.class).value.get(0);
|
||||
return WorthUtils.dynamicCast(stack.getFirst(), ArrayStackLevel.class).value.get(0);
|
||||
} catch (NumberFormatException e) {
|
||||
throw error(ParseException::new, e.getMessage());
|
||||
} finally {
|
||||
|
77
src/main/java/org/oggio88/worth/utils/Leb128.java
Normal file
77
src/main/java/org/oggio88/worth/utils/Leb128.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package org.oggio88.worth.utils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class Leb128 {
|
||||
|
||||
public static long reverse(long n) {
|
||||
long res = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
long b = (n & (0xFFL << (i * 8))) >>> (i * 8);
|
||||
res |= b << ((7 - i) * 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static int encode(OutputStream os, double input) {
|
||||
return encode(os, reverse(Double.doubleToLongBits(input)));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static int encode(OutputStream os, long input) {
|
||||
int bytes_written = 0;
|
||||
long number = input >= 0 ? (input << 1) : (-(input + 1)) << 1 | 1;
|
||||
while(number > 127) {
|
||||
os.write((int) (number & 127) | 128);
|
||||
bytes_written++;
|
||||
number >>= 7;
|
||||
}
|
||||
os.write((int) number);
|
||||
return ++bytes_written;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class Leb128Decoder {
|
||||
@Getter
|
||||
private int bytesRead = 0;
|
||||
|
||||
private final InputStream is;
|
||||
|
||||
public byte decodeByte() {
|
||||
return (byte) decode();
|
||||
}
|
||||
|
||||
public short decodeShort() {
|
||||
return (short) decode();
|
||||
}
|
||||
|
||||
public int decodeInt() {
|
||||
return (int) decode();
|
||||
}
|
||||
|
||||
public double decodeDouble() {
|
||||
return Double.longBitsToDouble(reverse(decode()));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public long decode() {
|
||||
long res = 0;
|
||||
for(int i = 0; i < (8 * 8 + 6) / 7; i++) {
|
||||
int c = is.read();
|
||||
bytesRead++;
|
||||
if(c < 0) {
|
||||
throw new IllegalArgumentException("Unexpected end of file");
|
||||
}
|
||||
byte b = (byte) c;
|
||||
res |= ((long)(b & 127)) << (i * 7);
|
||||
if(b >= 0) break;
|
||||
}
|
||||
return (res & 1) != 0 ? - (res >> 1) - 1 : (res >> 1);
|
||||
}
|
||||
}
|
||||
}
|
61
src/main/java/org/oggio88/worth/utils/ValueWalker.java
Normal file
61
src/main/java/org/oggio88/worth/utils/ValueWalker.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package org.oggio88.worth.utils;
|
||||
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ValueWalker {
|
||||
|
||||
private Value parent;
|
||||
|
||||
public ValueWalker(Value root) {
|
||||
parent = root;
|
||||
}
|
||||
|
||||
public ValueWalker get(String key) {
|
||||
if(parent.type() == Value.Type.OBJECT) {
|
||||
parent = parent.get(key);
|
||||
} else {
|
||||
parent = Value.Null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValueWalker get(int index) {
|
||||
if(parent.type() == Value.Type.ARRAY) {
|
||||
parent = parent.get(index);
|
||||
} else {
|
||||
parent = Value.Null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Value get() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public <T> Optional<T> map(Function<Value, T> callback) {
|
||||
if(isPresent()) {
|
||||
return Optional.of(callback.apply(parent));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> Optional<T> flatMap(Function<Value, Optional<T>> callback) {
|
||||
if(isPresent()) {
|
||||
return callback.apply(parent);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPresent() {
|
||||
return !isEmpty();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return parent.type() == Value.Type.NULL;
|
||||
}
|
||||
}
|
@@ -1,19 +1,20 @@
|
||||
package org.oggio88.worth.utils;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.oggio88.worth.buffer.CircularBuffer;
|
||||
import org.oggio88.worth.exception.ParseException;
|
||||
import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class WorthUtils {
|
||||
|
||||
@SneakyThrows
|
||||
public static <T> 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;
|
||||
@@ -22,8 +23,114 @@ public class WorthUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean equalsNullSafe(Object o1, Object o2) {
|
||||
if (o1 == null) return o2 == null;
|
||||
else return o1.equals(o2);
|
||||
public static <T> Stream<T> iterable2stream(Iterable<T> iterable) {
|
||||
return StreamSupport.stream(iterable.spliterator(), false);
|
||||
}
|
||||
|
||||
public static void writeObject2File(String fileName, Object o) {
|
||||
writeObject2File(new File(fileName), o);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void writeObject2File(File file, Object o) {
|
||||
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file.getPath()))) {
|
||||
writer.write(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String readFile2String(File file) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try (Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file.getPath())))) {
|
||||
char[] buffer = new char[1024];
|
||||
while (true) {
|
||||
int read = reader.read(buffer);
|
||||
builder.append(buffer, 0, read);
|
||||
if (read < buffer.length) break;
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String readResource2String(String classpath) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (Reader reader = new InputStreamReader(WorthUtils.class.getResourceAsStream(classpath))) {
|
||||
char[] buffer = new char[1024];
|
||||
while (true) {
|
||||
int read = reader.read(buffer);
|
||||
sb.append(buffer, 0, read);
|
||||
if (read < buffer.length) break;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> T newThrowable(Class<T> cls, String format, Object... args) {
|
||||
Constructor<T> constructor = cls.getConstructor(String.class);
|
||||
return constructor.newInstance(String.format(format, args));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> T newThrowable(Class<T> cls, Throwable throwable, String format, Object... args) {
|
||||
Constructor<T> constructor = cls.getConstructor(String.class, Throwable.class);
|
||||
return constructor.newInstance(String.format(format, args), throwable);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> void raise(Class<T> cls, Throwable throwable, String format, Object... args) {
|
||||
throw newThrowable(cls, throwable, format, args);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> void raise(Class<T> cls, String format, Object... args) {
|
||||
throw newThrowable(cls, format, args);
|
||||
}
|
||||
|
||||
public static Value getOrNull(Value root, String... keys) {
|
||||
Value result = root;
|
||||
for (String key : keys) {
|
||||
result = result.get(key);
|
||||
if (result == null) {
|
||||
result = Value.Null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> T getOrNull(Value root, Function<Value, T> callback, String... keys) {
|
||||
Value result = getOrNull(root, keys);
|
||||
return result.type() == Value.Type.NULL ? null : callback.apply(result);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T> T getOrThrow(Value root, Function<Value, T> success, Supplier<Throwable> error, String... keys) {
|
||||
Value result = getOrNull(root, keys);
|
||||
if (result.type() == Value.Type.NULL) {
|
||||
throw error.get();
|
||||
} else {
|
||||
return success.apply(result);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Predicate<T> not(Predicate<T> p) {
|
||||
return p.negate();
|
||||
}
|
||||
|
||||
public static <V, T> Stream<V> flatMap(Stream<T> stream,
|
||||
Function<? super T, Optional<? extends V>> mappingFunction) {
|
||||
return stream.map(mappingFunction).filter(Optional::isPresent).map(Optional::get);
|
||||
}
|
||||
|
||||
public static <T> Stream<T> optional2Stream(Optional<T> optional) {
|
||||
return optional.map(Stream::of).orElse(Stream.empty());
|
||||
}
|
||||
|
||||
public static void setSystemPropertyIfNotDefined(String key, String value) {
|
||||
if(System.getProperty(key) == null) {
|
||||
System.setProperty(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import org.oggio88.worth.xface.Value;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class ArrayValue implements Value, Iterable<Value> {
|
||||
@@ -32,7 +33,12 @@ public class ArrayValue implements Value, Iterable<Value> {
|
||||
|
||||
@Override
|
||||
public Value get(int index) {
|
||||
return value.get(index);
|
||||
int sz = size();
|
||||
if(index < sz) {
|
||||
return value.get(Math.floorMod(index, sz));
|
||||
} else {
|
||||
return Value.Null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,6 +69,7 @@ public class ArrayValue implements Value, Iterable<Value> {
|
||||
return value.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return value.size();
|
||||
}
|
||||
|
@@ -6,19 +6,16 @@ import org.oggio88.worth.xface.Value;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.oggio88.worth.utils.WorthUtils.equalsNullSafe;
|
||||
|
||||
|
||||
public interface ObjectValue extends Value, Iterable<Map.Entry<String, Value>> {
|
||||
|
||||
boolean listBasedImplementation = Boolean.valueOf(
|
||||
System.getProperty("org.oggio88.javason.value.ObjectValue.listBasedImplementation", "false"));
|
||||
System.getProperty(ObjectValue.class.getName() + ".listBasedImplementation", "false"));
|
||||
boolean preserveKeyOrder = Boolean.valueOf(
|
||||
System.getProperty("org.oggio88.javason.value.MapObjectValue.preserveKeyOrder", "false"));
|
||||
System.getProperty(ObjectValue.class.getName() + ".preserveKeyOrder", "false"));
|
||||
|
||||
static ObjectValue newInstance() {
|
||||
if (listBasedImplementation) {
|
||||
return new MapObjectValue();
|
||||
return new ListObjectValue();
|
||||
} else {
|
||||
return new MapObjectValue();
|
||||
}
|
||||
@@ -63,7 +60,7 @@ class MapObjectValue implements ObjectValue {
|
||||
private final Map<String, Value> value;
|
||||
|
||||
public MapObjectValue() {
|
||||
this.value = ObjectValue.preserveKeyOrder ? new LinkedHashMap() : new HashMap();
|
||||
this.value = ObjectValue.preserveKeyOrder ? new LinkedHashMap<>() : new HashMap<>();
|
||||
}
|
||||
|
||||
public MapObjectValue(Map<String, Value> value) {
|
||||
@@ -72,25 +69,17 @@ class MapObjectValue implements ObjectValue {
|
||||
|
||||
@Override
|
||||
public Map<String, Value> asObject() {
|
||||
return value;
|
||||
return Collections.unmodifiableMap(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value get(String key) {
|
||||
Value result = value.get(key);
|
||||
if (result == null) {
|
||||
result = Value.Null;
|
||||
value.put(key, result);
|
||||
}
|
||||
return result;
|
||||
return value.getOrDefault(key, Value.Null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getOrDefault(String key, Value defaultValue) {
|
||||
if (value.containsKey(key))
|
||||
return value.get(key);
|
||||
else
|
||||
return defaultValue;
|
||||
return value.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,13 +107,18 @@ class MapObjectValue implements ObjectValue {
|
||||
public Iterator<Map.Entry<String, Value>> iterator() {
|
||||
return value.entrySet().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return value.size();
|
||||
}
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
class ListObjectValue implements ObjectValue {
|
||||
|
||||
private final List<Map.Entry<String, Value>> value = new ArrayList();
|
||||
private final List<Map.Entry<String, Value>> value = new ArrayList<>();
|
||||
|
||||
public ListObjectValue(Map<String, Value> map) {
|
||||
this.value.addAll(map.entrySet());
|
||||
@@ -132,17 +126,17 @@ class ListObjectValue implements ObjectValue {
|
||||
|
||||
@Override
|
||||
public Map<String, Value> asObject() {
|
||||
Map<String, Value> result = preserveKeyOrder ? new LinkedHashMap() : new HashMap();
|
||||
Map<String, Value> result = preserveKeyOrder ? new LinkedHashMap<>() : new HashMap<>();
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value get(String key) {
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
if(equalsNullSafe(entry.getKey(), key)) return entry.getValue();
|
||||
if(Objects.equals(entry.getKey(), key)) return entry.getValue();
|
||||
}
|
||||
return Value.Null;
|
||||
}
|
||||
@@ -150,7 +144,7 @@ class ListObjectValue implements ObjectValue {
|
||||
@Override
|
||||
public Value getOrDefault(String key, Value defaultValue) {
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
if(equalsNullSafe(entry.getKey(), key)) return entry.getValue();
|
||||
if(Objects.equals(entry.getKey(), key)) return entry.getValue();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
@@ -158,7 +152,7 @@ class ListObjectValue implements ObjectValue {
|
||||
@Override
|
||||
public Value getOrPut(String key, Value value2Put) {
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
if(equalsNullSafe(entry.getKey(), key)) return entry.getValue();
|
||||
if(Objects.equals(entry.getKey(), key)) return entry.getValue();
|
||||
}
|
||||
put(key, value2Put);
|
||||
return value2Put;
|
||||
@@ -173,7 +167,7 @@ class ListObjectValue implements ObjectValue {
|
||||
@Override
|
||||
public boolean has(String key) {
|
||||
for (Map.Entry<String, Value> entry : value) {
|
||||
if(equalsNullSafe(entry.getKey(), key)) return true;
|
||||
if(Objects.equals(entry.getKey(), key)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -182,4 +176,9 @@ class ListObjectValue implements ObjectValue {
|
||||
public Iterator<Map.Entry<String, Value>> iterator() {
|
||||
return value.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return value.size();
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import org.oggio88.worth.value.NullValue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface Value {
|
||||
|
||||
@@ -40,6 +41,10 @@ public interface Value {
|
||||
throw new TypeException("Not an object");
|
||||
}
|
||||
|
||||
default int size() {
|
||||
throw new TypeException("Neither an array nor an object");
|
||||
}
|
||||
|
||||
default void add(Value value) {
|
||||
throw new TypeException("Not an array");
|
||||
}
|
||||
|
Reference in New Issue
Block a user