package com.revolsys.record.io.format.json;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.sql.Clob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import com.revolsys.collection.map.LinkedHashMapEx;
import com.revolsys.collection.map.MapEx;
import com.revolsys.io.FileUtil;
import com.revolsys.spring.resource.Resource;
import com.revolsys.util.MathUtil;
public class JsonParser implements Iterator<JsonParser.EventType>, Closeable {
public enum EventType {
booleanValue, colon, comma, endArray, endDocument, endObject, nullValue, number, startArray, startDocument, startObject, string, unknown
}
public static Map<String, Object> getMap(final InputStream in) {
if (in == null) {
return null;
} else {
try (
final JsonParser parser = new JsonParser(in)) {
if (parser.next() == EventType.startDocument) {
return parser.getMap();
} else {
return Collections.emptyMap();
}
}
}
}
public static Map<String, Object> getMap(final Reader reader) {
final JsonParser parser = new JsonParser(reader);
try {
if (parser.next() == EventType.startDocument) {
return parser.getMap();
} else {
return Collections.emptyMap();
}
} finally {
parser.close();
}
}
@SuppressWarnings("unchecked")
public static <V> V read(final InputStream in) {
return (V)read(FileUtil.newUtf8Reader(in));
}
@SuppressWarnings("unchecked")
public static <V> V read(final Object source) {
Reader reader;
if (source instanceof Clob) {
try {
reader = ((Clob)source).getCharacterStream();
} catch (final SQLException e) {
throw new RuntimeException("Unable to read clob", e);
}
} else if (source instanceof Reader) {
reader = (Reader)source;
} else if (source instanceof CharSequence) {
reader = new StringReader(source.toString());
} else {
try {
final Resource resource = Resource.getResource(source);
reader = resource.newBufferedReader();
} catch (final IllegalArgumentException e) {
reader = new StringReader(source.toString());
}
}
try {
final V value = (V)read(reader);
return value;
} finally {
if (source instanceof Clob) {
try {
final Clob clob = (Clob)source;
clob.free();
} catch (final SQLException e) {
throw new RuntimeException("Unable to free clob resources", e);
}
}
}
}
@SuppressWarnings("unchecked")
public static <V> V read(final Reader in) {
try (
final JsonParser parser = new JsonParser(in)) {
if (parser.hasNext()) {
final EventType event = parser.next();
if (event == EventType.startDocument) {
final V value = (V)parser.getValue();
if (parser.hasNext() && parser.next() != EventType.endDocument) {
throw new IllegalStateException("Extra content at end of file: " + parser);
}
return value;
}
}
return null;
}
}
@SuppressWarnings("unchecked")
public static <V> V read(final String in) {
return (V)read(new StringReader(in));
}
private int currentCharacter;
private EventType currentEvent = EventType.startDocument;
private Object currentValue;
private int depth;
private EventType nextEvent = EventType.startDocument;
private Object nextValue;
private final Reader reader;
public JsonParser(final InputStream in) {
this(FileUtil.newUtf8Reader(in));
}
public JsonParser(final Reader reader) {
this.reader = new BufferedReader(reader, 10000);
try {
this.currentCharacter = this.reader.read();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
public JsonParser(final Resource resource) {
this(resource.newBufferedReader());
}
@Override
public void close() {
FileUtil.closeSilent(this.reader);
}
public List<Object> getArray() {
if (getEvent() == EventType.startArray || hasNext() && next() == EventType.startArray) {
EventType event = getEvent();
final List<Object> list = new ArrayList<>();
do {
final Object value = getValue();
if (value instanceof EventType) {
event = (EventType)value;
if (event == EventType.comma) {
throw new IllegalStateException(
"Missing value before ',' " + FileUtil.getString(this.reader, 80));
} else if (event == EventType.endArray) {
if (!list.isEmpty()) {
throw new IllegalStateException(
"Missing value after ',' and before ']' " + FileUtil.getString(this.reader, 80));
}
}
} else {
list.add(value);
event = next();
}
} while (event == EventType.comma);
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not '" + this + ']');
}
return list;
} else {
throw new IllegalStateException("Exepecting start array, not: " + this);
}
}
@SuppressWarnings("unchecked")
public <T> T getCurrentValue() {
return (T)this.currentValue;
}
public int getDepth() {
return this.depth;
}
public double[] getDoubleArray() {
if (getEvent() == EventType.startArray || hasNext() && next() == EventType.startArray) {
EventType event = getEvent();
final List<Number> list = new ArrayList<>();
do {
final Object value = getValue();
if (value instanceof EventType) {
event = (EventType)value;
} else if (value instanceof Number) {
list.add((Number)value);
event = next();
} else {
throw new IllegalArgumentException("Expecting number, not: " + value);
}
} while (event == EventType.comma);
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not: " + event);
}
return MathUtil.toDoubleArray(list);
} else if (getEvent() == EventType.nullValue) {
return null;
} else {
throw new IllegalStateException("Exepecting start array, not: " + getEvent());
}
}
public EventType getEvent() {
return this.currentEvent;
}
public int[] getIntArray() {
if (getEvent() == EventType.startArray || hasNext() && next() == EventType.startArray) {
EventType event = getEvent();
final List<Number> list = new ArrayList<>();
do {
final Object value = getValue();
if (value instanceof EventType) {
event = (EventType)value;
} else if (value instanceof Number) {
list.add((Number)value);
event = next();
} else {
throw new IllegalArgumentException("Expecting number, not: " + value);
}
} while (event == EventType.comma);
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not: " + event);
}
return MathUtil.toIntArray(list);
} else if (getEvent() == EventType.nullValue) {
return null;
} else {
throw new IllegalStateException("Exepecting start array, not: " + getEvent());
}
}
public MapEx getMap() {
if (getEvent() == EventType.startObject || hasNext() && next() == EventType.startObject) {
EventType event = getEvent();
final MapEx map = new LinkedHashMapEx();
do {
if (hasNext() && next() == EventType.string) {
final String key = getStringIntern();
if (hasNext()) {
if (next() == EventType.colon) {
if (hasNext()) {
final Object value = getValue();
if (value instanceof EventType) {
throw new IllegalStateException("Exepecting a value, not: " + key + "=" + value);
}
if (key != null) {
map.put(key, value);
}
}
}
}
event = next();
} else {
event = getEvent();
}
} while (event == EventType.comma);
if (event != EventType.endObject) {
throw new IllegalStateException("Exepecting end object, not:" + event);
}
return map;
} else {
throw new IllegalStateException("Exepecting end object, not:" + getEvent());
}
}
public String getString() {
if (getEvent() == EventType.string || hasNext() && next() == EventType.string) {
return getCurrentValue();
} else {
throw new IllegalStateException("Expecting a string");
}
}
public String getStringIntern() {
final String string = getString();
if (string == null) {
return null;
} else {
return string.intern();
}
}
public Object getValue() {
// TODO empty array
if (hasNext()) {
final EventType event = next();
if (event == EventType.startArray) {
return getArray();
} else if (event == EventType.startObject) {
return this.getMap();
} else if (event == EventType.booleanValue) {
return getCurrentValue();
} else if (event == EventType.nullValue) {
return getCurrentValue();
} else if (event == EventType.string) {
return getCurrentValue();
} else if (event == EventType.number) {
return getCurrentValue();
} else {
return event;
}
} else {
throw new IllegalStateException("Expecting a value not EOF");
}
}
@Override
public boolean hasNext() {
return this.currentEvent != EventType.endDocument;
}
public boolean isEvent(final EventType eventType) {
return this.currentEvent == eventType;
}
public boolean isEvent(final EventType... eventTypes) {
for (final EventType eventType : eventTypes) {
if (this.currentEvent == eventType) {
return true;
}
}
return false;
}
private void moveNext() {
this.nextValue = null;
try {
skipWhitespace();
switch (this.currentCharacter) {
case ',':
this.nextEvent = EventType.comma;
this.currentCharacter = this.reader.read();
break;
case ':':
this.nextEvent = EventType.colon;
this.currentCharacter = this.reader.read();
break;
case '{':
this.nextEvent = EventType.startObject;
this.currentCharacter = this.reader.read();
this.depth++;
break;
case '}':
this.nextEvent = EventType.endObject;
this.currentCharacter = this.reader.read();
this.depth--;
break;
case '[':
this.nextEvent = EventType.startArray;
this.currentCharacter = this.reader.read();
break;
case ']':
this.nextEvent = EventType.endArray;
this.currentCharacter = this.reader.read();
break;
case 't':
for (int i = 0; i < 3; i++) {
this.currentCharacter = this.reader.read();
}
this.nextEvent = EventType.booleanValue;
this.nextValue = Boolean.TRUE;
this.currentCharacter = this.reader.read();
break;
case 'f':
for (int i = 0; i < 4; i++) {
this.currentCharacter = this.reader.read();
}
this.nextEvent = EventType.booleanValue;
this.nextValue = Boolean.FALSE;
this.currentCharacter = this.reader.read();
break;
case 'n':
for (int i = 0; i < 3; i++) {
this.currentCharacter = this.reader.read();
}
this.nextEvent = EventType.nullValue;
this.nextValue = null;
this.currentCharacter = this.reader.read();
break;
case '"':
this.nextEvent = EventType.string;
processString();
this.currentCharacter = this.reader.read();
break;
case '-':
this.nextEvent = EventType.number;
processNumber();
break;
case -1:
this.nextEvent = EventType.endDocument;
break;
default:
if (this.currentCharacter >= '0' && this.currentCharacter <= '9') {
this.nextEvent = EventType.number;
processNumber();
} else {
this.nextEvent = EventType.unknown;
}
break;
}
} catch (final IOException e) {
this.nextEvent = EventType.endDocument;
}
}
@Override
public EventType next() {
if (hasNext()) {
this.currentValue = this.nextValue;
this.currentEvent = this.nextEvent;
moveNext();
return this.currentEvent;
} else {
throw new NoSuchElementException();
}
}
private void processNumber() throws IOException {
final StringBuilder text = new StringBuilder();
if (this.currentCharacter == '-') {
text.append((char)this.currentCharacter);
this.currentCharacter = this.reader.read();
}
while (this.currentCharacter >= '0' && this.currentCharacter <= '9') {
text.append((char)this.currentCharacter);
this.currentCharacter = this.reader.read();
}
if (this.currentCharacter == '.') {
text.append((char)this.currentCharacter);
this.currentCharacter = this.reader.read();
while (this.currentCharacter >= '0' && this.currentCharacter <= '9') {
text.append((char)this.currentCharacter);
this.currentCharacter = this.reader.read();
}
}
if (this.currentCharacter == 'e' || this.currentCharacter == 'E') {
text.append((char)this.currentCharacter);
this.currentCharacter = this.reader.read();
if (this.currentCharacter == '-' || this.currentCharacter == '+') {
text.append((char)this.currentCharacter);
this.currentCharacter = this.reader.read();
}
while (this.currentCharacter >= '0' && this.currentCharacter <= '9') {
text.append((char)this.currentCharacter);
this.currentCharacter = this.reader.read();
}
}
this.nextValue = new BigDecimal(text.toString());
}
private void processString() throws IOException {
final StringBuilder text = new StringBuilder();
this.currentCharacter = this.reader.read();
while (this.currentCharacter != '"' && this.currentCharacter != -1) {
if (this.currentCharacter == '\\') {
this.currentCharacter = this.reader.read();
switch (this.currentCharacter) {
case -1:
break;
case 'b':
text.setLength(text.length() - 1);
break;
case '"':
text.append('"');
break;
case '/':
text.append('/');
break;
case '\\':
text.append('\\');
break;
case 'f':
text.append('\f');
case 'n':
text.append('\n');
break;
case 'r':
text.append('\r');
break;
case 't':
text.append('\t');
break;
case 'u':
final char[] buf = new char[4];
final int readCount = this.reader.read(buf);
final String unicodeText = String.valueOf(buf, 0, readCount);
if (readCount == 4) {
try {
final int unicode = Integer.parseInt(unicodeText, 16);
text.append((char)unicode);
} catch (final NumberFormatException e) {
throw e;
}
} else {
throw new IllegalStateException("Unicode escape not correct " + unicodeText);
}
break;
default:
throw new IllegalStateException(
"Invalid escape character: \\" + (char)this.currentCharacter);
}
} else {
text.append((char)this.currentCharacter);
}
this.currentCharacter = this.reader.read();
}
this.nextValue = text.toString();
}
@Override
public void remove() {
}
/** Skip to next attribute in any object.*/
public String skipToAttribute() {
while (hasNext()) {
final EventType eventType = next();
if (eventType == EventType.string) {
final String key = getStringIntern();
if (hasNext() && next() == EventType.colon) {
return key;
}
}
}
return null;
}
/**
* Skip through the document until the specified object attribute name is
* found.
*
* @param parser The parser.
* @param fieldName The name of the attribute to skip through.
*/
public boolean skipToAttribute(final String fieldName) {
while (hasNext()) {
final EventType eventType = next();
if (eventType == EventType.string) {
final String key = getStringIntern();
if (key.equals(fieldName)) {
if (hasNext() && next() == EventType.colon) {
if (hasNext()) {
next();
return true;
} else {
return false;
}
}
}
} else if (eventType == EventType.unknown) {
return false;
}
}
return false;
}
/** Skip to next attribute in the same object.*/
public String skipToNextAttribute() {
int objectCount = 0;
while (hasNext()) {
final EventType eventType = next();
if (objectCount == 0 && eventType == EventType.string) {
final String key = getStringIntern();
if (hasNext() && next() == EventType.colon) {
return key;
}
} else if (eventType == EventType.startObject) {
objectCount++;
} else if (eventType == EventType.endObject) {
if (objectCount == 0) {
return null;
} else {
objectCount--;
}
}
}
return null;
}
public boolean skipToNextObjectInArray() {
if (isEvent(EventType.startArray, EventType.comma)) {
if (hasNext()) {
final EventType eventType = next();
if (eventType == EventType.startObject) {
return true;
} else if (eventType == EventType.endArray) {
return false;
} else {
throw new IllegalArgumentException("Unexpected element: " + this);
}
}
}
return false;
}
private void skipWhitespace() throws IOException {
while (Character.isWhitespace(this.currentCharacter)) {
this.currentCharacter = this.reader.read();
}
}
@Override
public String toString() {
return this.currentEvent + " : " + this.currentValue + " "
+ Character.toString((char)this.currentCharacter) + FileUtil.getString(this.reader, 80);
}
}