switched from CircularBuffer to LookAheadTextInputStream
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package org.oggio88.worth.buffer;
|
package org.oggio88.worth.buffer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class LookAheadInputStream extends InputStream {
|
public class LookAheadInputStream extends InputStream {
|
||||||
@@ -13,7 +14,8 @@ public class LookAheadInputStream extends InputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
@SneakyThrows
|
||||||
|
public int read() {
|
||||||
int result = currentByte;
|
int result = currentByte;
|
||||||
currentByte = stream.read();
|
currentByte = stream.read();
|
||||||
return result;
|
return result;
|
||||||
|
@@ -0,0 +1,28 @@
|
|||||||
|
package org.oggio88.worth.buffer;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
public class LookAheadTextInputStream extends InputStream {
|
||||||
|
|
||||||
|
private final Reader reader;
|
||||||
|
private int currentByte;
|
||||||
|
|
||||||
|
public LookAheadTextInputStream(Reader reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public int read() {
|
||||||
|
int result = currentByte;
|
||||||
|
currentByte = reader.read();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentByte(){
|
||||||
|
return currentByte;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
package org.oggio88.worth.serialization.json;
|
package org.oggio88.worth.serialization.json;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.oggio88.worth.buffer.CircularBuffer;
|
import org.oggio88.worth.buffer.LookAheadTextInputStream;
|
||||||
import org.oggio88.worth.exception.IOException;
|
import org.oggio88.worth.exception.IOException;
|
||||||
import org.oggio88.worth.exception.NotImplementedException;
|
import org.oggio88.worth.exception.NotImplementedException;
|
||||||
import org.oggio88.worth.exception.ParseException;
|
import org.oggio88.worth.exception.ParseException;
|
||||||
@@ -27,10 +27,10 @@ public class JSONParser extends ValueParser {
|
|||||||
return c >= '0' && c <= '9' || c == '+' || c == '-' || c == '.' || c == 'e';
|
return c >= '0' && c <= '9' || c == '+' || c == '-' || c == '.' || c == 'e';
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int parseHex(CircularBuffer circularBuffer) {
|
private static int parseHex(LookAheadTextInputStream stream) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
int c = circularBuffer.next();
|
int c = stream.getCurrentByte();
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
result = result << 4;
|
result = result << 4;
|
||||||
result += (c - '0');
|
result += (c - '0');
|
||||||
@@ -41,23 +41,25 @@ public class JSONParser extends ValueParser {
|
|||||||
result = result << 4;
|
result = result << 4;
|
||||||
result += 10 + (c - 'A');
|
result += 10 + (c - 'A');
|
||||||
} else {
|
} else {
|
||||||
circularBuffer.prev();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
stream.read();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final void parseNumber(CircularBuffer circularBuffer) {
|
private final void parseNumber(LookAheadTextInputStream stream) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
while (true) {
|
while (true) {
|
||||||
int b = circularBuffer.next();
|
int b = stream.getCurrentByte();
|
||||||
if (isDecimal(b)) {
|
if (b < 0) {
|
||||||
|
break;
|
||||||
|
} else if (isDecimal(b)) {
|
||||||
sb.appendCodePoint(b);
|
sb.appendCodePoint(b);
|
||||||
} else {
|
} else {
|
||||||
circularBuffer.prev();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
stream.read();
|
||||||
}
|
}
|
||||||
String text = sb.toString();
|
String text = sb.toString();
|
||||||
if (text.indexOf('.') > 0) {
|
if (text.indexOf('.') > 0) {
|
||||||
@@ -67,15 +69,16 @@ public class JSONParser extends ValueParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String readString(CircularBuffer circularBuffer) {
|
private final String readString(LookAheadTextInputStream stream) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
boolean escape = false;
|
boolean escape = false;
|
||||||
|
boolean start = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
int c = circularBuffer.next();
|
int c = stream.getCurrentByte();
|
||||||
if (c < 0) {
|
if (c < 0) {
|
||||||
circularBuffer.prev();
|
|
||||||
break;
|
break;
|
||||||
} else if (escape) {
|
} else if (escape) {
|
||||||
|
escape = false;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '"':
|
case '"':
|
||||||
sb.append('\"');
|
sb.append('\"');
|
||||||
@@ -93,31 +96,38 @@ public class JSONParser extends ValueParser {
|
|||||||
sb.append('\\');
|
sb.append('\\');
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
int codePoint = parseHex(circularBuffer);
|
stream.read();
|
||||||
|
int codePoint = parseHex(stream);
|
||||||
sb.appendCodePoint(codePoint);
|
sb.appendCodePoint(codePoint);
|
||||||
break;
|
continue;
|
||||||
default:
|
default:
|
||||||
throw error(ParseException::new, "Unrecognized escape sequence '\\%c'", c);
|
throw error(ParseException::new, "Unrecognized escape sequence '\\%c'", c);
|
||||||
}
|
}
|
||||||
escape = false;
|
|
||||||
} else if (c == '\\') {
|
} else if (c == '\\') {
|
||||||
escape = true;
|
escape = true;
|
||||||
} else if (c == '\"') {
|
} else if (c == '\"') {
|
||||||
break;
|
if (start) break;
|
||||||
|
else start = true;
|
||||||
} else {
|
} else {
|
||||||
sb.appendCodePoint(c);
|
sb.appendCodePoint(c);
|
||||||
}
|
}
|
||||||
|
stream.read();
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final void consumeExpected(CircularBuffer circularBuffer, String expected, String errorMessage) {
|
private final void consumeExpected(LookAheadTextInputStream stream, String expected, String errorMessage) {
|
||||||
for (int i = 0; i < expected.length(); i++) {
|
int i = 0;
|
||||||
int c = circularBuffer.next();
|
while (true) {
|
||||||
|
int c = stream.getCurrentByte();
|
||||||
if (c < 0) {
|
if (c < 0) {
|
||||||
throw error(IOException::new, "Unexpected end of stream");
|
throw error(IOException::new, "Unexpected end of stream");
|
||||||
}
|
}
|
||||||
if (c != expected.codePointAt(i)) throw error(ParseException::new, errorMessage);
|
if (c != expected.codePointAt(i)) throw error(ParseException::new, errorMessage);
|
||||||
|
else if (++i >= expected.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
stream.read();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,11 +149,10 @@ public class JSONParser extends ValueParser {
|
|||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public Value parse(Reader reader) {
|
public Value parse(Reader reader) {
|
||||||
final CircularBuffer circularBuffer = new CircularBuffer(reader, 8) {
|
final LookAheadTextInputStream stream = new LookAheadTextInputStream(reader) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int next() {
|
public int read() {
|
||||||
int result = super.next();
|
int result = super.read();
|
||||||
if (result == '\n') {
|
if (result == '\n') {
|
||||||
++currentLine;
|
++currentLine;
|
||||||
currentColumn = 1;
|
currentColumn = 1;
|
||||||
@@ -152,26 +161,14 @@ public class JSONParser extends ValueParser {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int prev() {
|
|
||||||
int result = super.prev();
|
|
||||||
if (result == '\n') {
|
|
||||||
--currentLine;
|
|
||||||
} else {
|
|
||||||
--currentColumn;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
int c = circularBuffer.next();
|
int c = stream.getCurrentByte();
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
} else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
} else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
||||||
continue;
|
|
||||||
} else if (c == '{') {
|
} else if (c == '{') {
|
||||||
beginObject();
|
beginObject();
|
||||||
} else if (c == '}') {
|
} else if (c == '}') {
|
||||||
@@ -181,14 +178,16 @@ public class JSONParser extends ValueParser {
|
|||||||
} else if (c == ']') {
|
} else if (c == ']') {
|
||||||
endArray();
|
endArray();
|
||||||
} else if (isDecimal(c)) {
|
} else if (isDecimal(c)) {
|
||||||
circularBuffer.prev();
|
|
||||||
try {
|
try {
|
||||||
parseNumber(circularBuffer);
|
parseNumber(stream);
|
||||||
|
continue;
|
||||||
} catch (NumberFormatException nfe) {
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw error(ParseException::new, nfe.getMessage());
|
||||||
}
|
}
|
||||||
} else if (c == '\"') {
|
} else if (c == '\"') {
|
||||||
String text = readString(circularBuffer);
|
if (currentLine == 125)
|
||||||
|
System.out.print("");
|
||||||
|
String text = readString(stream);
|
||||||
ObjectStackLevel osl;
|
ObjectStackLevel osl;
|
||||||
if ((osl = WorthUtils.dynamicCast(stack.lastElement(), ObjectStackLevel.class)) != null && osl.currentKey == null) {
|
if ((osl = WorthUtils.dynamicCast(stack.lastElement(), ObjectStackLevel.class)) != null && osl.currentKey == null) {
|
||||||
objectKey(text);
|
objectKey(text);
|
||||||
@@ -196,15 +195,16 @@ public class JSONParser extends ValueParser {
|
|||||||
stringValue(text);
|
stringValue(text);
|
||||||
}
|
}
|
||||||
} else if (c == 't') {
|
} else if (c == 't') {
|
||||||
consumeExpected(circularBuffer, "rue", "Unrecognized boolean value");
|
consumeExpected(stream, "true", "Unrecognized boolean value");
|
||||||
booleanValue(true);
|
booleanValue(true);
|
||||||
} else if (c == 'f') {
|
} else if (c == 'f') {
|
||||||
consumeExpected(circularBuffer, "alse", "Unrecognized boolean value");
|
consumeExpected(stream, "false", "Unrecognized boolean value");
|
||||||
booleanValue(false);
|
booleanValue(false);
|
||||||
} else if (c == 'n') {
|
} else if (c == 'n') {
|
||||||
consumeExpected(circularBuffer, "ull", "Unrecognized null value");
|
consumeExpected(stream, "null", "Unrecognized null value");
|
||||||
nullValue();
|
nullValue();
|
||||||
}
|
}
|
||||||
|
stream.read();
|
||||||
}
|
}
|
||||||
if (stack.size() > 1) {
|
if (stack.size() > 1) {
|
||||||
char c;
|
char c;
|
||||||
|
@@ -7,6 +7,7 @@ import lombok.SneakyThrows;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.oggio88.worth.buffer.CircularBuffer;
|
import org.oggio88.worth.buffer.CircularBuffer;
|
||||||
|
import org.oggio88.worth.buffer.LookAheadTextInputStream;
|
||||||
import org.oggio88.worth.exception.NotImplementedException;
|
import org.oggio88.worth.exception.NotImplementedException;
|
||||||
import org.oggio88.worth.utils.WorthUtils;
|
import org.oggio88.worth.utils.WorthUtils;
|
||||||
import org.oggio88.worth.value.ArrayValue;
|
import org.oggio88.worth.value.ArrayValue;
|
||||||
@@ -87,6 +88,102 @@ public class JSONTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface Callback {
|
||||||
|
void call(Value value, JsonNode jsonNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean compareValueAndJsonNode(Value value, JsonNode jsonNode, Callback cb) {
|
||||||
|
switch (value.type()) {
|
||||||
|
case NULL: {
|
||||||
|
boolean result = jsonNode.getNodeType() == JsonNodeType.NULL;
|
||||||
|
if (result) return true;
|
||||||
|
else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case INTEGER:
|
||||||
|
if (jsonNode.getNodeType() == JsonNodeType.NUMBER) {
|
||||||
|
boolean result = value.asInteger() == jsonNode.asLong();
|
||||||
|
if (result) return true;
|
||||||
|
else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case DOUBLE:
|
||||||
|
if (jsonNode.getNodeType() == JsonNodeType.NUMBER) {
|
||||||
|
boolean result = value.asFloat() == jsonNode.asDouble();
|
||||||
|
if (result) return true;
|
||||||
|
else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case BOOLEAN:
|
||||||
|
if (jsonNode.getNodeType() == JsonNodeType.BOOLEAN) {
|
||||||
|
boolean result = value.asBoolean() == jsonNode.asBoolean();
|
||||||
|
if (result) return true;
|
||||||
|
else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case STRING:
|
||||||
|
if (jsonNode.getNodeType() == JsonNodeType.STRING) {
|
||||||
|
boolean result = value.asString().equals(jsonNode.asText());
|
||||||
|
if (result) return true;
|
||||||
|
else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
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), cb)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
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())) {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
} else if (!compareValueAndJsonNode(entry.getValue(), jsonNode.get(entry.getKey()), cb)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
cb.call(value, jsonNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException("This should never happen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void consistencyTest() {
|
public void consistencyTest() {
|
||||||
@@ -116,7 +213,9 @@ public class JSONTest {
|
|||||||
ObjectMapper om = new ObjectMapper();
|
ObjectMapper om = new ObjectMapper();
|
||||||
JsonNode jsonNode = om.readTree(getTestSource(testFile));
|
JsonNode jsonNode = om.readTree(getTestSource(testFile));
|
||||||
Value value = new JSONParser().parse(getTestSource(testFile));
|
Value value = new JSONParser().parse(getTestSource(testFile));
|
||||||
Assert.assertTrue(compareValueAndJsonNode(value, jsonNode));
|
Assert.assertTrue(compareValueAndJsonNode(value, jsonNode, (v, j) -> {
|
||||||
|
Assert.fail("Difference found");
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,9 +225,11 @@ public class JSONTest {
|
|||||||
String hex = "1F608";
|
String hex = "1F608";
|
||||||
byte[] buffer = new String(hex).getBytes();
|
byte[] buffer = new String(hex).getBytes();
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
|
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
|
||||||
Method method = JSONParser.class.getDeclaredMethod("parseHex", CircularBuffer.class);
|
Method method = JSONParser.class.getDeclaredMethod("parseHex", LookAheadTextInputStream.class);
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
int result = (int) method.invoke(null, new CircularBuffer(new InputStreamReader(bais), 5));
|
LookAheadTextInputStream ltis = new LookAheadTextInputStream(new InputStreamReader(bais));
|
||||||
|
ltis.read();
|
||||||
|
int result = (int) method.invoke(null, ltis);
|
||||||
Assert.assertEquals((int) Integer.valueOf(hex, 16), result);
|
Assert.assertEquals((int) Integer.valueOf(hex, 16), result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -67,6 +67,15 @@ public class PerformanceTest {
|
|||||||
return baous.toByteArray();
|
return baous.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
@SneakyThrows
|
||||||
|
public void profilerTest() {
|
||||||
|
while (true) {
|
||||||
|
Value value = new JSONParser().parse(getClass().getResourceAsStream("/wordpress.json"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void loopTest() {
|
public void loopTest() {
|
||||||
|
Reference in New Issue
Block a user