From 21870cf8a35ae1a5eab0e58463b0cdadf8de1682 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Sun, 9 Sep 2018 23:38:29 +0100 Subject: [PATCH] switched from CircularBuffer to LookAheadTextInputStream --- .../worth/buffer/LookAheadInputStream.java | 6 +- .../buffer/LookAheadTextInputStream.java | 28 +++++ .../worth/serialization/json/JSONParser.java | 84 +++++++------- .../worth/serialization/json/JSONTest.java | 107 +++++++++++++++++- .../serialization/json/PerformanceTest.java | 9 ++ 5 files changed, 187 insertions(+), 47 deletions(-) create mode 100644 src/main/java/org/oggio88/worth/buffer/LookAheadTextInputStream.java diff --git a/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java b/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java index 25827e5..57372b4 100644 --- a/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java +++ b/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java @@ -1,6 +1,7 @@ package org.oggio88.worth.buffer; -import java.io.IOException; +import lombok.SneakyThrows; + import java.io.InputStream; public class LookAheadInputStream extends InputStream { @@ -13,7 +14,8 @@ public class LookAheadInputStream extends InputStream { } @Override - public int read() throws IOException { + @SneakyThrows + public int read() { int result = currentByte; currentByte = stream.read(); return result; diff --git a/src/main/java/org/oggio88/worth/buffer/LookAheadTextInputStream.java b/src/main/java/org/oggio88/worth/buffer/LookAheadTextInputStream.java new file mode 100644 index 0000000..d4a4ba0 --- /dev/null +++ b/src/main/java/org/oggio88/worth/buffer/LookAheadTextInputStream.java @@ -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; + } +} diff --git a/src/main/java/org/oggio88/worth/serialization/json/JSONParser.java b/src/main/java/org/oggio88/worth/serialization/json/JSONParser.java index f7faeae..d62f9e6 100644 --- a/src/main/java/org/oggio88/worth/serialization/json/JSONParser.java +++ b/src/main/java/org/oggio88/worth/serialization/json/JSONParser.java @@ -1,7 +1,7 @@ package org.oggio88.worth.serialization.json; 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.NotImplementedException; 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'; } - private static int parseHex(CircularBuffer circularBuffer) { + private static int parseHex(LookAheadTextInputStream stream) { int result = 0; while (true) { - int c = circularBuffer.next(); + int c = stream.getCurrentByte(); if (c >= '0' && c <= '9') { result = result << 4; result += (c - '0'); @@ -41,23 +41,25 @@ public class JSONParser extends ValueParser { result = result << 4; result += 10 + (c - 'A'); } else { - circularBuffer.prev(); break; } + stream.read(); } return result; } - private final void parseNumber(CircularBuffer circularBuffer) { + private final void parseNumber(LookAheadTextInputStream stream) { StringBuilder sb = new StringBuilder(); while (true) { - int b = circularBuffer.next(); - if (isDecimal(b)) { + int b = stream.getCurrentByte(); + if (b < 0) { + break; + } else if (isDecimal(b)) { sb.appendCodePoint(b); } else { - circularBuffer.prev(); break; } + stream.read(); } String text = sb.toString(); 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(); boolean escape = false; + boolean start = false; while (true) { - int c = circularBuffer.next(); + int c = stream.getCurrentByte(); if (c < 0) { - circularBuffer.prev(); break; } else if (escape) { + escape = false; switch (c) { case '"': sb.append('\"'); @@ -93,31 +96,38 @@ public class JSONParser extends ValueParser { sb.append('\\'); break; case 'u': - int codePoint = parseHex(circularBuffer); + stream.read(); + int codePoint = parseHex(stream); sb.appendCodePoint(codePoint); - break; + continue; default: throw error(ParseException::new, "Unrecognized escape sequence '\\%c'", c); } - escape = false; } else if (c == '\\') { escape = true; } else if (c == '\"') { - break; + if (start) break; + else start = true; } else { sb.appendCodePoint(c); } + stream.read(); } return sb.toString(); } - private final void consumeExpected(CircularBuffer circularBuffer, String expected, String errorMessage) { - for (int i = 0; i < expected.length(); i++) { - int c = circularBuffer.next(); + private final void consumeExpected(LookAheadTextInputStream stream, String expected, String errorMessage) { + int i = 0; + while (true) { + int c = stream.getCurrentByte(); if (c < 0) { throw error(IOException::new, "Unexpected end of stream"); } 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 @SneakyThrows public Value parse(Reader reader) { - final CircularBuffer circularBuffer = new CircularBuffer(reader, 8) { - + final LookAheadTextInputStream stream = new LookAheadTextInputStream(reader) { @Override - public int next() { - int result = super.next(); + public int read() { + int result = super.read(); if (result == '\n') { ++currentLine; currentColumn = 1; @@ -152,26 +161,14 @@ public class JSONParser extends ValueParser { } return result; } - - @Override - public int prev() { - int result = super.prev(); - if (result == '\n') { - --currentLine; - } else { - --currentColumn; - } - return result; - } }; try { while (true) { - int c = circularBuffer.next(); + int c = stream.getCurrentByte(); if (c == -1) { break; } else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { - continue; } else if (c == '{') { beginObject(); } else if (c == '}') { @@ -181,14 +178,16 @@ public class JSONParser extends ValueParser { } else if (c == ']') { endArray(); } else if (isDecimal(c)) { - circularBuffer.prev(); try { - parseNumber(circularBuffer); + parseNumber(stream); + continue; } catch (NumberFormatException nfe) { - + throw error(ParseException::new, nfe.getMessage()); } } else if (c == '\"') { - String text = readString(circularBuffer); + if (currentLine == 125) + System.out.print(""); + String text = readString(stream); ObjectStackLevel osl; if ((osl = WorthUtils.dynamicCast(stack.lastElement(), ObjectStackLevel.class)) != null && osl.currentKey == null) { objectKey(text); @@ -196,15 +195,16 @@ public class JSONParser extends ValueParser { stringValue(text); } } else if (c == 't') { - consumeExpected(circularBuffer, "rue", "Unrecognized boolean value"); + consumeExpected(stream, "true", "Unrecognized boolean value"); booleanValue(true); } else if (c == 'f') { - consumeExpected(circularBuffer, "alse", "Unrecognized boolean value"); + consumeExpected(stream, "false", "Unrecognized boolean value"); booleanValue(false); } else if (c == 'n') { - consumeExpected(circularBuffer, "ull", "Unrecognized null value"); + consumeExpected(stream, "null", "Unrecognized null value"); nullValue(); } + stream.read(); } if (stack.size() > 1) { char c; diff --git a/src/test/java/org/oggio88/worth/serialization/json/JSONTest.java b/src/test/java/org/oggio88/worth/serialization/json/JSONTest.java index 0efd8cf..6d435b0 100644 --- a/src/test/java/org/oggio88/worth/serialization/json/JSONTest.java +++ b/src/test/java/org/oggio88/worth/serialization/json/JSONTest.java @@ -7,6 +7,7 @@ import lombok.SneakyThrows; import org.junit.Assert; import org.junit.Test; import org.oggio88.worth.buffer.CircularBuffer; +import org.oggio88.worth.buffer.LookAheadTextInputStream; import org.oggio88.worth.exception.NotImplementedException; import org.oggio88.worth.utils.WorthUtils; 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 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 @SneakyThrows public void consistencyTest() { @@ -116,7 +213,9 @@ public class JSONTest { ObjectMapper om = new ObjectMapper(); JsonNode jsonNode = om.readTree(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"; byte[] buffer = new String(hex).getBytes(); ByteArrayInputStream bais = new ByteArrayInputStream(buffer); - Method method = JSONParser.class.getDeclaredMethod("parseHex", CircularBuffer.class); + Method method = JSONParser.class.getDeclaredMethod("parseHex", LookAheadTextInputStream.class); 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); } } diff --git a/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java b/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java index dd17a80..81d5606 100644 --- a/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java +++ b/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java @@ -67,6 +67,15 @@ public class PerformanceTest { return baous.toByteArray(); } + @Test + @Ignore + @SneakyThrows + public void profilerTest() { + while (true) { + Value value = new JSONParser().parse(getClass().getResourceAsStream("/wordpress.json")); + } + } + @Test @SneakyThrows public void loopTest() {