package org.odata4j.format.json;
import java.io.IOException;
import java.io.Reader;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Stack;
import org.odata4j.core.ODataConstants;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonParseException;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamReader;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamReader.JsonEndPropertyEvent;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamReader.JsonEvent;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamReader.JsonStartPropertyEvent;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamReader.JsonValueEvent;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamTokenizer;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamTokenizer.JsonToken;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamTokenizer.JsonTokenType;
public class JsonStreamReaderFactory {
public static class JsonParseException extends RuntimeException {
private static final long serialVersionUID = 2362481232045271688L;
public JsonParseException() {}
public JsonParseException(String message, Throwable cause) {
super(message, cause);
}
public JsonParseException(String message) {
super(message);
}
public JsonParseException(Throwable cause) {
super(cause);
}
}
public interface JsonStreamReader {
public static interface JsonEvent {
boolean isStartObject();
boolean isEndObject();
boolean isStartProperty();
boolean isEndProperty();
boolean isStartArray();
boolean isEndArray();
boolean isValue();
JsonStartPropertyEvent asStartProperty();
JsonEndPropertyEvent asEndProperty();
JsonValueEvent asValue();
}
public static interface JsonStartPropertyEvent extends JsonEvent {
String getName();
}
public static interface JsonEndPropertyEvent extends JsonEvent {
// returns a value if it is a simple property and
// not an other JsonObject or JsonArray
String getValue();
JsonTokenType getValueTokenType();
}
public static interface JsonValueEvent extends JsonEvent {
String getValue();
}
boolean hasNext();
JsonEvent nextEvent();
/**
* returns the JsonEvent that the last call to nextEvent() returned.
*
* @return the last JsonEvent returned by nextEvent()
*/
JsonEvent previousEvent();
void skipNestedEvents();
void close();
}
public interface JsonStreamTokenizer {
public enum JsonTokenType {
LEFT_CURLY_BRACKET,
RIGHT_CURLY_BRACKET,
LEFT_BRACKET,
RIGHT_BRACKET,
COMMA,
COLON,
TRUE,
FALSE,
NULL,
NUMBER,
STRING,
NaN;
}
public class JsonToken {
public final JsonTokenType type;
public final String value;
public JsonToken(JsonTokenType type) {
this(type, null);
}
public JsonToken(JsonTokenType type, String value) {
this.type = type;
this.value = value;
}
@Override
public String toString() {
StringBuilder bld = new StringBuilder();
bld.append(type);
if (value != null) {
bld.append("(").append(value).append(")");
}
return bld.toString();
}
}
boolean hasNext();
JsonToken nextToken();
void close();
}
public static JsonStreamReader createJsonStreamReader(Reader reader) {
return new JsonStreamReaderImpl(reader);
}
public static JsonStreamTokenizer createJsonStreamTokenizer(Reader reader) {
return new JsonStreamTokenizerImpl(reader);
}
}
class JsonEventImpl implements JsonEvent {
@Override
public boolean isStartObject() {
return false;
}
@Override
public boolean isEndObject() {
return false;
}
@Override
public boolean isStartProperty() {
return false;
}
@Override
public boolean isEndProperty() {
return false;
}
@Override
public boolean isStartArray() {
return false;
}
@Override
public boolean isEndArray() {
return false;
}
@Override
public boolean isValue() {
return false;
}
@Override
public JsonStartPropertyEvent asStartProperty() {
return (JsonStartPropertyEvent) this;
}
@Override
public JsonEndPropertyEvent asEndProperty() {
return (JsonEndPropertyEvent) this;
}
@Override
public JsonValueEvent asValue() {
return (JsonValueEvent) this;
}
public String toString() {
StringBuilder bld = new StringBuilder();
if (isStartObject()) {
bld.append("StartObject('{')");
} else if (isEndObject()) {
bld.append("EndObject('}')");
} else if (isStartArray()) {
bld.append("StartArray('[')");
} else if (isEndArray()) {
bld.append("EndArray(']')");
} else if (isStartProperty()) {
bld.append("StartProperty(").append(asStartProperty().getName()).append(")");
} else if (isEndProperty()) {
if (asEndProperty().getValue() == null) {
bld.append("EndProperty(").append("<null>").append(")");
} else {
bld.append("EndProperty(").append(asEndProperty().getValue()).append(")");
}
} else if (isValue()) {
if (asValue().getValue() == null) {
bld.append("Value(").append("<null>").append(")");
} else {
bld.append("Value(").append(asValue().getValue()).append(")");
}
}
return bld.toString();
}
}
class JsonStartPropertyEventImpl extends JsonEventImpl implements JsonStartPropertyEvent {
@Override
public boolean isStartProperty() {
return true;
}
@Override
public String getName() {
return null;
}
}
class JsonEndPropertyEventImpl extends JsonEventImpl implements JsonEndPropertyEvent {
@Override
public boolean isEndProperty() {
return true;
}
@Override
public String getValue() {
return null;
}
@Override
public JsonTokenType getValueTokenType() {
return null;
}
}
class JsonValueEventImpl extends JsonEventImpl implements JsonValueEvent {
@Override
public boolean isValue() {
return true;
}
@Override
public String getValue() {
return null;
}
}
class JsonStreamTokenizerImpl implements JsonStreamTokenizer {
private Reader reader;
private JsonToken token;
private NumberFormat nf = DecimalFormat.getNumberInstance(Locale.US);
private int pushedBack = -1;
JsonStreamTokenizerImpl(Reader reader) {
if (reader == null)
throw new NullPointerException();
this.reader = reader;
move();
}
public boolean hasNext() {
return token != null;
}
public JsonToken nextToken() {
JsonToken cur = token;
move();
return cur;
}
public void close() {
try {
reader.close();
} catch (IOException ioe) {
throw new JsonParseException(ioe);
}
}
enum TokenizerState {
DEFAULT,
STRING,
NUMBER
}
private void move() {
token = null;
StringBuilder buffer = new StringBuilder();
boolean quote = false;
TokenizerState state = TokenizerState.DEFAULT;
int i;
while (token == null && (i = next()) != -1) {
char c = (char) i;
if (state == TokenizerState.DEFAULT) {
if ('{' == c) {
token = new JsonToken(JsonTokenType.LEFT_CURLY_BRACKET);
} else if ('}' == c) {
if (hasConstantParsed(buffer)) {
pushBack(i);
} else {
token = new JsonToken(JsonTokenType.RIGHT_CURLY_BRACKET);
}
} else if ('[' == c) {
token = new JsonToken(JsonTokenType.LEFT_BRACKET);
} else if (']' == c) {
if (hasConstantParsed(buffer)) {
pushBack(i);
} else {
token = new JsonToken(JsonTokenType.RIGHT_BRACKET);
}
} else if (':' == c) {
token = new JsonToken(JsonTokenType.COLON);
} else if (',' == c) {
if (hasConstantParsed(buffer)) {
pushBack(i);
} else {
token = new JsonToken(JsonTokenType.COMMA);
}
} else if ('"' == c) {
if (buffer.length() > 0) {
throw new JsonParseException("no JSON format");
}
state = TokenizerState.STRING;
} else if ('-' == c || Character.isDigit(c)) {
buffer.append(c);
state = TokenizerState.NUMBER;
} else if (Character.isWhitespace(c)) {
hasConstantParsed(buffer);
} else {
buffer.append(c);
}
} else if (state == TokenizerState.STRING) {
if (quote) {
if ('b' == c) {
buffer.append('\b');
} else if ('f' == c) {
buffer.append('\f');
} else if ('n' == c) {
buffer.append('\n');
} else if ('r' == c) {
buffer.append('\r');
} else if ('t' == c) {
buffer.append('\t');
} else if ('/' == c) {
buffer.append('/');
} else if ('\\' == c) {
buffer.append('\\');
} else if ('"' == c) {
buffer.append('"');
} else if ('u' == c) {
buffer.append((char) Integer.parseInt(new String(next(4)), 16));
} else {
throw new JsonParseException("illegal escaped character " + c);
}
quote = false;
} else if ('\\' == c) {
quote = true;
} else if ('"' == c) {
token = new JsonToken(JsonTokenType.STRING, buffer.toString());
} else {
buffer.append(c);
quote = false;
}
} else {
if ('-' == c || Character.isDigit(c)
|| 'E' == c || 'e' == c
|| '+' == c || '.' == c) {
buffer.append(c); // a valid character in a number
} else {
// must be done with the number.
pushBack(i);
checkNumberFormat(buffer);
token = new JsonToken(JsonTokenType.NUMBER, buffer.toString());
}
}
}
if (state == TokenizerState.NUMBER) {
checkNumberFormat(buffer);
token = new JsonToken(JsonTokenType.NUMBER, buffer.toString());
}
}
private Number checkNumberFormat(StringBuilder memory) {
try {
return nf.parse(memory.toString());
} catch (Exception nfe) {
throw new JsonParseException("", nfe);
}
}
private boolean hasConstantParsed(StringBuilder memory) {
if (isTrue(memory)) {
token = new JsonToken(JsonTokenType.TRUE, "true");
} else if (isFalse(memory)) {
token = new JsonToken(JsonTokenType.FALSE, "false");
} else if (isNull(memory)) {
token = new JsonToken(JsonTokenType.NULL, "null");
} else if (isNaN(memory)) {
token = new JsonToken(JsonTokenType.NaN, ODataConstants.NaN_value);
}
return token != null;
}
private void pushBack(int c) {
if (pushedBack != -1) {
throw new IllegalStateException("can push back only one character");
} else {
pushedBack = c;
}
}
private char[] next(int count) {
char[] ret = new char[count];
for (int i = 0; i < count; i++) {
ret[i] = (char) next();
}
return ret;
}
private int next() {
if (pushedBack != -1) {
int ret = pushedBack;
pushedBack = -1;
return ret;
} else {
try {
return reader.read();
} catch (IOException ioe) {
throw new JsonParseException(ioe);
}
}
}
private boolean isTrue(StringBuilder str) {
return str.length() == 4
&& str.charAt(0) == 't'
&& str.charAt(1) == 'r'
&& str.charAt(2) == 'u'
&& str.charAt(3) == 'e';
}
private boolean isFalse(StringBuilder str) {
return str.length() == 5
&& str.charAt(0) == 'f'
&& str.charAt(1) == 'a'
&& str.charAt(2) == 'l'
&& str.charAt(3) == 's'
&& str.charAt(4) == 'e';
}
private boolean isNull(StringBuilder str) {
return str.length() == 4
&& str.charAt(0) == 'n'
&& str.charAt(1) == 'u'
&& str.charAt(2) == 'l'
&& str.charAt(3) == 'l';
}
private boolean isNaN(StringBuilder str) {
return str.length() == 3
&& str.charAt(0) == 'N'
&& str.charAt(1) == 'a'
&& str.charAt(2) == 'N';
}
}
enum ReaderState {
NONE,
OBJECT,
ARRAY,
PROPERTY
}
class JsonStreamReaderImpl implements JsonStreamReader {
private static final boolean DUMP = false;
private static void dump(String msg) {
if (DUMP) System.out.println(msg);
}
private JsonStreamTokenizerImpl tokenizer;
private Stack<ReaderState> state = new Stack<ReaderState>();
private Stack<Boolean> expectCommaOrEndStack = new Stack<Boolean>();
private boolean expectCommaOrEnd;
private boolean fireEndPropertyEvent;
private JsonEvent previousEvent = null;
JsonStreamReaderImpl(Reader reader) {
this.state.push(ReaderState.NONE);
this.tokenizer = new JsonStreamTokenizerImpl(reader);
}
@Override
public boolean hasNext() {
return tokenizer.hasNext();
}
@Override
public JsonEvent nextEvent() {
if (fireEndPropertyEvent) {
if (state.peek() != ReaderState.PROPERTY) {
throw new IllegalStateException("State is " + state.peek());
}
fireEndPropertyEvent = false;
return createEndPropertyEvent(null, JsonTokenType.NULL);
}
if (hasNext()) {
JsonToken token = tokenizer.nextToken();
switch (state.peek()) {
case NONE:
if (token.type != JsonTokenType.LEFT_CURLY_BRACKET) {
throw new JsonParseException("no JSON format must start with {");
} else {
return createStartObjectEvent();
}
case OBJECT:
if (expectCommaOrEnd) {
if (token.type == JsonTokenType.COMMA) {
if (!tokenizer.hasNext()) {
throw new JsonParseException("no JSON format premature end");
}
token = tokenizer.nextToken();
} else if (token.type != JsonTokenType.RIGHT_CURLY_BRACKET) {
throw new JsonParseException("no JSON format expected , or ] got " + token.type);
}
expectCommaOrEnd = false;
}
switch (token.type) {
case STRING:
if (!tokenizer.hasNext() || tokenizer.nextToken().type != JsonTokenType.COLON) {
throw new JsonParseException("no JSON format : expected afer " + token.value);
}
expectCommaOrEnd = true;
return createStartPropertyEvent(token.value);
case RIGHT_CURLY_BRACKET:
return createEndObjectEvent();
default:
throw new JsonParseException("no JSON format");
}
case PROPERTY:
switch (token.type) {
case STRING:
case NUMBER:
case TRUE:
case FALSE:
return createEndPropertyEvent(token.value, token.type);
case NULL:
return createEndPropertyEvent(null, token.type);
case LEFT_CURLY_BRACKET:
return createStartObjectEvent();
case LEFT_BRACKET:
return createStartArrayEvent();
default:
throw new JsonParseException("no JSON format");
}
case ARRAY:
if (expectCommaOrEnd) {
if (token.type == JsonTokenType.COMMA) {
if (!tokenizer.hasNext()) {
throw new JsonParseException("no JSON format premature end");
}
token = tokenizer.nextToken();
} else if (token.type != JsonTokenType.RIGHT_BRACKET) {
throw new JsonParseException("no JSON format expected , or ]");
}
expectCommaOrEnd = false;
}
switch (token.type) {
case STRING:
case NUMBER:
case TRUE:
case FALSE:
case NaN:
expectCommaOrEnd = true;
return createValueEvent(token.value);
case NULL:
expectCommaOrEnd = true;
return createValueEvent(null);
case LEFT_CURLY_BRACKET:
expectCommaOrEnd = true;
return createStartObjectEvent();
case LEFT_BRACKET:
expectCommaOrEnd = true;
return createStartArrayEvent();
case RIGHT_BRACKET:
return createEndArrayEvent();
default:
break;
}
}
}
this.previousEvent = null;
throw new RuntimeException("no event");
}
private JsonEvent createStartPropertyEvent(final String name) {
state.push(ReaderState.PROPERTY);
dump("jsonp start property: " + name);
this.previousEvent = new JsonStartPropertyEventImpl() {
@Override
public String getName() {
return name;
}
};
return this.previousEvent;
}
private JsonEvent createEndPropertyEvent(final String value, final JsonTokenType valueTokenType) {
state.pop();
dump("jsonp end property: " + value);
this.previousEvent = new JsonEndPropertyEventImpl() {
@Override
public String getValue() {
return value;
}
@Override
public JsonTokenType getValueTokenType() {
return valueTokenType;
}
};
return this.previousEvent;
}
private JsonEvent createStartObjectEvent() {
state.push(ReaderState.OBJECT);
dump("jsonp start object");
expectCommaOrEndStack.push(expectCommaOrEnd);
expectCommaOrEnd = false;
this.previousEvent = new JsonEventImpl() {
@Override
public boolean isStartObject() {
return true;
}
};
return this.previousEvent;
}
private JsonEvent createEndObjectEvent() {
state.pop();
dump("jsonp end object");
expectCommaOrEnd = expectCommaOrEndStack.pop();
// if the end of the object is also the of
// a property, we need to fire the
// endPropertyEvent before going forward.
if (state.peek() == ReaderState.PROPERTY) {
fireEndPropertyEvent = true;
}
this.previousEvent = new JsonEventImpl() {
@Override
public boolean isEndObject() {
return true;
}
};
return this.previousEvent;
}
private JsonEvent createStartArrayEvent() {
state.push(ReaderState.ARRAY);
dump("jsonp start array");
expectCommaOrEndStack.push(expectCommaOrEnd);
expectCommaOrEnd = false;
this.previousEvent = new JsonEventImpl() {
@Override
public boolean isStartArray() {
return true;
}
};
return this.previousEvent;
}
private JsonEvent createEndArrayEvent() {
state.pop();
dump("jsonp end array");
expectCommaOrEnd = expectCommaOrEndStack.pop();
// if the end of the array is also the of
// a property, we need to fire the
// endPropertyEvent before going forward.
if (state.peek() == ReaderState.PROPERTY) {
fireEndPropertyEvent = true;
}
this.previousEvent = new JsonEventImpl() {
@Override
public boolean isEndArray() {
return true;
}
};
return this.previousEvent;
}
private JsonEvent createValueEvent(final String value) {
dump("jsonp value: " + value);
this.previousEvent = new JsonValueEventImpl() {
@Override
public String getValue() {
return value;
}
};
return this.previousEvent;
}
@Override
public JsonEvent previousEvent() {
return previousEvent;
}
@Override
public void skipNestedEvents() {
if (!previousEvent.isStartProperty() && !previousEvent.isStartObject() && !previousEvent.isStartArray())
return;
// skip until stack element pushed by a start event has been removed by the corresponding end event
int stackSize = state.size();
while (hasNext() && state.size() >= stackSize)
nextEvent();
}
@Override
public void close() {
tokenizer.close();
}
}