switched from CircularBuffer to LookAheadTextInputStream

This commit is contained in:
2018-09-09 23:38:29 +01:00
parent 135c56151c
commit 21870cf8a3
5 changed files with 187 additions and 47 deletions

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);
} }
} }

View File

@@ -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() {