diff --git a/pom.xml b/pom.xml
index a69f439..dea87ca 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,5 +83,11 @@
2.10.3
test
+
+ org.jetbrains
+ annotations
+ 16.0.2
+ compile
+
diff --git a/src/main/java/nl/sander/jsontoy2/JsonReader.java b/src/main/java/nl/sander/jsontoy2/JsonReader.java
index c34deb3..6179a0d 100644
--- a/src/main/java/nl/sander/jsontoy2/JsonReader.java
+++ b/src/main/java/nl/sander/jsontoy2/JsonReader.java
@@ -2,24 +2,37 @@ package nl.sander.jsontoy2;
import nl.sander.jsontoy2.readers.*;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
import java.io.InputStream;
+import java.lang.ref.SoftReference;
+import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.function.Supplier;
/**
- * public facade
+ * public api
*/
public class JsonReader {
+ private static final ConcurrentMap, Supplier>> readSuppliers = new ConcurrentHashMap<>();
private static final ConcurrentMap, JsonValueReader>> readers = new ConcurrentHashMap<>();
+ private final static ThreadLocal> PARSERS = new ThreadLocal<>();
+
+ static {
+ registerPrimitiveTypeReaders();
+ }
+
/**
- * reads a value for a type that is not known beforehand
+ * reads a value from a stream for a type that is not known beforehand
*
- * @param stream the underlying stream to read
+ * @param inputStream the underlying stream to read
* @return an Object with runtime type as follows:
* "null" => null
* "true"/"false" => Boolean
@@ -29,61 +42,116 @@ public class JsonReader {
* object => HashMap
* array => List
*/
- public static Object read(InputStream stream) {
- return read(new Parser(stream));
+ public static Object read(InputStream inputStream) {
+ InputStream in = ensureBuffered(inputStream);
+ try (Parser parser = getParser(in)) {
+ return read(parser);
+ }
}
+ private static InputStream ensureBuffered(InputStream inputStream) {
+ if (inputStream instanceof BufferedInputStream) {
+ return inputStream;
+ } else {
+ return new BufferedInputStream(inputStream);
+ }
+ }
+
+
+ /**
+ * Reads a value from a string for a type that is not known beforehand
+ *
+ * @param jsonString the Json String to read
+ * @return @see read(InputStream stream)
+ */
public static Object read(String jsonString) {
- return read(new Parser(jsonString));
+ return read(getParser(jsonString));
+ }
+
+ private static Parser getParser(String jsonString) {
+ return getParser(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ private static Parser getParser(InputStream inputStream) {
+ Objects.requireNonNull(inputStream, "File not found");
+ Parser parser;
+ SoftReference parserReference = PARSERS.get();
+ if (parserReference == null || (parser = parserReference.get()) == null) {
+ parser = new Parser(inputStream);
+ parserReference = new SoftReference<>(parser);
+ PARSERS.set(parserReference);
+ } else {
+ parser.init(inputStream);
+ }
+ return parser;
}
static Object read(Parser parser) {
return parser.parseAny();
}
- public static T read(Class type, InputStream reader) {
- return read(type, new Parser(reader));
+ /**
+ * Reads a value from a stream for the given type
+ *
+ * @param type The class for the type that is needed
+ * @param inputStream The stream to read
+ * @param the type that is needed
+ * @return Object the specified type
+ */
+ public static T read(Class type, InputStream inputStream) {
+ Parser parser = getParser(inputStream);
+ T value = read(type, parser);
+ parser.close();
+ return value;
}
+ /**
+ * Reads a value from a stream for the given type
+ *
+ * @param type The class for the type that is needed
+ * @param jsonString The String to read
+ * @param the type that is needed
+ * @return Object the specified type
+ */
public static T read(Class type, String jsonString) {
- return read(type, new Parser(jsonString));
+ return read(type, getParser(jsonString));
}
@SuppressWarnings("unchecked")
- static T read(Class type, Parser parser) {
+ private static T read(Class type, Parser parser) {
return (T) getReader(type).read(parser);
-// class.cast() does not work for primitives;
+// class.cast() does not work well for primitives;
}
private static JsonValueReader> getReader(Class type) {
- return readers.get(type);
+ return readers.computeIfAbsent(type, k -> readSuppliers.get(k).get());
}
- static void register(Class type, JsonValueReader objectReader) {
- readers.put(type, objectReader);
+ private static void register(Class type, Supplier> objectReader) {
+ readSuppliers.put(type, objectReader);
}
- static {
- register(Boolean.class, new BooleanReader());
- register(boolean.class, new BooleanReader());
- register(Integer.class, new IntegerReader());
- register(int.class, new IntegerReader());
- register(Long.class, new LongReader());
- register(long.class, new LongReader());
- register(Byte.class, new ByteReader());
- register(byte.class, new ByteReader());
- register(Short.class, new ShortReader());
- register(short.class, new ShortReader());
- register(Double.class, new DoubleReader());
- register(double.class, new DoubleReader());
- register(Float.class, new FloatReader());
- register(float.class, new FloatReader());
- register(Date.class, new DateReader());
- register(Character.class, new CharReader());
- register(char.class, new CharReader());
- register(String.class, new StringReader());
- register(LocalDateTime.class, new LocalDateTimeReader());
- register(List.class, new ListReader());
- register(Map.class, new MapReader());
+ private static void registerPrimitiveTypeReaders() {
+ register(Boolean.class, BooleanReader::new);
+ register(boolean.class, BooleanReader::new);
+ register(Integer.class, IntegerReader::new);
+ register(int.class, IntegerReader::new);
+ register(Long.class, LongReader::new);
+ register(long.class, LongReader::new);
+ register(Byte.class, ByteReader::new);
+ register(byte.class, ByteReader::new);
+ register(Short.class, ShortReader::new);
+ register(short.class, ShortReader::new);
+ register(Double.class, DoubleReader::new);
+ register(double.class, DoubleReader::new);
+ register(Float.class, FloatReader::new);
+ register(float.class, FloatReader::new);
+ register(Date.class, DateReader::new);
+ register(Character.class, CharReader::new);
+ register(char.class, CharReader::new);
+ register(String.class, StringReader::new);
+ register(LocalDateTime.class, LocalDateTimeReader::new);
+ register(List.class, ListReader::new);
+ register(Map.class, MapReader::new);
}
}
diff --git a/src/main/java/nl/sander/jsontoy2/Lexer.java b/src/main/java/nl/sander/jsontoy2/Lexer.java
index a108e10..2985a16 100644
--- a/src/main/java/nl/sander/jsontoy2/Lexer.java
+++ b/src/main/java/nl/sander/jsontoy2/Lexer.java
@@ -2,21 +2,24 @@ package nl.sander.jsontoy2;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
import java.util.function.Supplier;
/**
* implements lowest level operations on inputstream.
*/
-public class Lexer {
- protected final InputStream inputStream;
+public class Lexer implements AutoCloseable {
+ protected InputStream inputStream;
protected final ByteBuf characterBuffer = new ByteBuf();
protected byte current;
+ protected int charCount = 0;
public Lexer(InputStream inputStream) {
this.inputStream = inputStream;
}
void advance() {
+ charCount++;
try {
current = (byte) inputStream.read();
} catch (IOException e) {
@@ -29,21 +32,22 @@ public class Lexer {
while (current > -1 && Character.isWhitespace(current)) {
current = (byte) inputStream.read();
}
- if (current == -1) {
- throw new JsonParseException("end of source reached");
- }
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
- void eatUntil(char until) {
- while (current > -1 && (current != until | Character.isWhitespace(current))) {
+ void eatUntil(char... until) {
+ while (current > -1 && (!contains(until, current) | Character.isWhitespace(current))) {
advance();
}
advance();
}
+ private boolean contains(char[] characters, byte c) {
+ return Arrays.binarySearch(characters, (char) c) > -1;
+ }
+
void expect(Supplier exceptionSupplier, char... word) {
int increment = 0;
@@ -55,4 +59,12 @@ public class Lexer {
}
}
}
+
+ public void close() {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ throw new JsonParseException(e);
+ }
+ }
}
diff --git a/src/main/java/nl/sander/jsontoy2/Parser.java b/src/main/java/nl/sander/jsontoy2/Parser.java
index 09da074..a4871d2 100644
--- a/src/main/java/nl/sander/jsontoy2/Parser.java
+++ b/src/main/java/nl/sander/jsontoy2/Parser.java
@@ -1,6 +1,5 @@
package nl.sander.jsontoy2;
-import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
@@ -26,8 +25,14 @@ public class Parser extends Lexer {
advance();
}
- protected Parser(String jsonString) {
- super(new ByteArrayInputStream(jsonString.getBytes()));
+ public void init(InputStream inputStream) {
+ this.inputStream = inputStream;
+ linecount = 0;
+ charCount = 0;
+ escaping = false;
+ encoded = false;
+ encodedCodePointBuffer.clear();
+ characterBuffer.clear();
advance();
}
@@ -125,18 +130,23 @@ public class Parser extends Lexer {
if (current != '{') {
throw new JsonParseException("no map found");
}
+ advance();
while (current != -1 && current != '}') {
skipWhitespace();
if (current == '"') {
String key = parseString();
- eatUntil(':');
+ skipWhitespace();
+ if (current == ':') {
+ advance();
+ } else {
+ throw new JsonParseException("expected colon");
+ }
skipWhitespace();
Maybe