/**
* personium.io
* Modifications copyright 2014 FUJITSU LIMITED
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* --------------------------------------------------
* This code is based on JsonStreamReaderFactory.java of odata4j-core, and some modifications
* for personium.io are applied by us.
* --------------------------------------------------
* The copyright and the license text of the original code is as follows:
*/
/****************************************************************************
* Copyright (c) 2010 odata4j
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fujitsu.dc.core.odata;
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 com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonParseException;
import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamReader;
import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamReader.JsonEndPropertyEvent;
import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamReader.JsonEvent;
import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamReader.JsonStartPropertyEvent;
import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamReader.JsonValueEvent;
import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamTokenizer;
import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamTokenizer.JsonToken;
import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamTokenizer.JsonTokenType;
/**
* DcJsonStreamReaderFactory.
* JSONをストリームで読み込むReaderのファクトリー
*/
public class DcJsonStreamReaderFactory {
/**
* コンストラクタ.
*/
private DcJsonStreamReaderFactory() {
}
/**
* JsonParseException.
* パースエラー時の例外
*/
public static class JsonParseException extends RuntimeException {
private static final long serialVersionUID = 2362481232045271688L;
/**
* コンストラクタ.
*/
public JsonParseException() {
super();
}
/**
* コンストラクタ.
* @param message メッセージ
* @param cause Throwable
*/
public JsonParseException(String message, Throwable cause) {
super(message, cause);
}
/**
* コンストラクタ.
* @param message メッセージ
*/
public JsonParseException(String message) {
super(message);
}
/**
* コンストラクタ.
* @param cause Throwable
*/
public JsonParseException(Throwable cause) {
super(cause);
}
}
/**
* JsonStreamReader.
*/
public interface JsonStreamReader {
/**
* JsonEventインターフェースクラス.
*/
public interface JsonEvent {
/**
* @return boolean
*/
boolean isStartObject();
/**
* @return boolean
*/
boolean isEndObject();
/**
* @return boolean
*/
boolean isStartProperty();
/**
* @return boolean
*/
boolean isEndProperty();
/**
* @return boolean
*/
boolean isStartArray();
/**
* @return boolean
*/
boolean isEndArray();
/**
* @return boolean
*/
boolean isValue();
/**
* @return JsonStartPropertyEvent
*/
JsonStartPropertyEvent asStartProperty();
/**
* @return JsonEndPropertyEvent
*/
JsonEndPropertyEvent asEndProperty();
/**
* @return JsonValueEvent.
*/
JsonValueEvent asValue();
}
/**
* JsonStartPropertyEvent.
*/
public interface JsonStartPropertyEvent extends JsonEvent {
/**
* @return キー名
*/
String getName();
}
/**
* JsonEndPropertyEvent.
*/
public interface JsonEndPropertyEvent extends JsonEvent {
/**
* JSONの値を文字列で返す.
* @return JSON値
*/
String getValue();
/**
* JSONの値をオブジェクトで返す.
* @return オブジェクト
*/
Object getObject();
}
/**
* JsonValueEvent.
*/
public interface JsonValueEvent extends JsonEvent {
/**
* JSONの値を文字列で返す.
* @return JSON値
*/
String getValue();
}
/**
* 次の値を持っているか.
* @return boolean
*/
boolean hasNext();
/**
* 次のイベントを返却.
* @return JSONイベント
*/
JsonEvent nextEvent();
/**
* returns the JsonEvent that the last call to nextEvent() returned.
* @return the last JsonEvent returned by nextEvent()
*/
JsonEvent previousEvent();
/**
* クローズ.
*/
void close();
}
/**
* JsonStreamTokenizer.
*/
public interface JsonStreamTokenizer {
/**
* JsonTokenType.
*/
enum JsonTokenType {
LEFT_CURLY_BRACKET,
RIGHT_CURLY_BRACKET,
LEFT_BRACKET,
RIGHT_BRACKET,
COMMA,
COLON,
TRUE,
FALSE,
NULL,
NUMBER,
STRING;
}
/**
* JsonToken.
*/
class JsonToken {
/**
* JSONトークンのタイプ.
*/
private final JsonTokenType type;
/**
* JSONトークンの値.
*/
private final String value;
/**
* コンストラクタ.
* @param type JSONトークンのタイプ
*/
public JsonToken(JsonTokenType type) {
this(type, null);
}
/**
* コンストラクタ.
* @param type JSONトークンのタイプ
* @param value JSONトークンの値
*/
public JsonToken(JsonTokenType type, String value) {
this.type = type;
this.value = value;
}
@Override
public String toString() {
StringBuilder bld = new StringBuilder();
bld.append(getType());
if (getValue() != null) {
bld.append("(").append(getValue()).append(")");
}
return bld.toString();
}
/**
* タイプを取得する.
* @return type type
*/
public JsonTokenType getType() {
return type;
}
/**
* 値を取得する.
* @return value value
*/
public String getValue() {
return value;
}
}
/**
* 次の値を持っているか.
* @return boolean
*/
boolean hasNext();
/**
* 次のJSONトークンを返却.
* @return JSONトークン
*/
JsonToken nextToken();
/**
* クローズ.
*/
void close();
}
/**
* JsonStreamReaderを作成する.
* @param reader reader
* @return JsonStreamReader
*/
public static JsonStreamReader createJsonStreamReader(Reader reader) {
return new JsonStreamReaderImpl(reader);
}
/**
* JsonStreamTokenizerを作成する.
* @param reader reader
* @return JsonStreamTokenizer
*/
public static JsonStreamTokenizer createJsonStreamTokenizer(Reader reader) {
return new JsonStreamTokenizerImpl(reader);
}
}
/**
* JsonEventImpl.
*/
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();
}
}
/**
* JsonStartPropertyEventImpl.
*/
class JsonStartPropertyEventImpl extends JsonEventImpl implements JsonStartPropertyEvent {
@Override
public boolean isStartProperty() {
return true;
}
@Override
public String getName() {
return null;
}
}
/**
* JsonEndPropertyEventImpl.
*/
class JsonEndPropertyEventImpl extends JsonEventImpl implements JsonEndPropertyEvent {
@Override
public boolean isEndProperty() {
return true;
}
@Override
public String getValue() {
return null;
}
public Object getObject() {
return null;
}
}
/**
* JsonValueEventImpl.
*/
class JsonValueEventImpl extends JsonEventImpl implements JsonValueEvent {
@Override
public boolean isValue() {
return true;
}
@Override
public String getValue() {
return null;
}
}
/**
* JsonStreamTokenizerImpl.
*/
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);
}
}
/**
* TokenizerState.
*/
enum TokenizerState {
DEFAULT,
STRING,
NUMBER
}
static final int LENGTH_FOUR = 4;
static final int LENGTH_SIXTEEN = 16;
private void move() {
token = null;
StringBuilder buffer = new StringBuilder();
boolean quote = false;
TokenizerState state = TokenizerState.DEFAULT;
int i = 0;
while (token == null && i != -1) {
i = next();
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(LENGTH_FOUR)), LENGTH_SIXTEEN));
} 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");
}
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);
}
}
}
static final int TRUE_LENGTH = 4;
static final int FALSE_LENGTH = 5;
static final int NULL_LENGTH = 4;
static final int INDEX_ZERO = 0;
static final int INDEX_ONE = 1;
static final int INDEX_TWO = 2;
static final int INDEX_THREE = 3;
static final int INDEX_FOUR = 4;
private boolean isTrue(StringBuilder str) {
return str.length() == TRUE_LENGTH
&& str.charAt(INDEX_ZERO) == 't'
&& str.charAt(INDEX_ONE) == 'r'
&& str.charAt(INDEX_TWO) == 'u'
&& str.charAt(INDEX_THREE) == 'e';
}
private boolean isFalse(StringBuilder str) {
return str.length() == FALSE_LENGTH
&& str.charAt(INDEX_ZERO) == 'f'
&& str.charAt(INDEX_ONE) == 'a'
&& str.charAt(INDEX_TWO) == 'l'
&& str.charAt(INDEX_THREE) == 's'
&& str.charAt(INDEX_FOUR) == 'e';
}
private boolean isNull(StringBuilder str) {
return str.length() == NULL_LENGTH
&& str.charAt(INDEX_ZERO) == 'n'
&& str.charAt(INDEX_ONE) == 'u'
&& str.charAt(INDEX_TWO) == 'l'
&& str.charAt(INDEX_THREE) == 'l';
}
}
/**
* ReaderState.
*/
enum ReaderState {
NONE,
OBJECT,
ARRAY,
PROPERTY
}
/**
* JsonStreamReaderImpl.
*/
class JsonStreamReaderImpl implements JsonStreamReader {
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);
}
if (hasNext()) {
JsonToken token = tokenizer.nextToken();
switch (state.peek()) {
case NONE:
if (token.getType() != JsonTokenType.LEFT_CURLY_BRACKET) {
throw new JsonParseException("no JSON format must start with {");
} else {
return createStartObjectEvent();
}
case OBJECT:
if (expectCommaOrEnd) {
if (token.getType() == JsonTokenType.COMMA) {
if (!tokenizer.hasNext()) {
throw new JsonParseException("no JSON format premature end");
}
token = tokenizer.nextToken();
} else if (token.getType() != JsonTokenType.RIGHT_CURLY_BRACKET) {
throw new JsonParseException("no JSON format expected , or ] got " + token.getType());
}
expectCommaOrEnd = false;
}
switch (token.getType()) {
case STRING:
if (!tokenizer.hasNext() || tokenizer.nextToken().getType() != JsonTokenType.COLON) {
throw new JsonParseException("no JSON format : expected afer " + token.getValue());
}
expectCommaOrEnd = true;
return createStartPropertyEvent(token.getValue());
case RIGHT_CURLY_BRACKET:
return createEndObjectEvent();
default:
throw new JsonParseException("no JSON format");
}
case PROPERTY:
switch (token.getType()) {
case STRING:
return createEndPropertyEvent(token.getValue());
case NUMBER:
return createEndPropertyEventNumber(token.getValue());
case TRUE:
case FALSE:
return createEndPropertyEventBoolean(token.getValue());
case NULL:
return createEndPropertyEvent(null);
case LEFT_CURLY_BRACKET:
return createStartObjectEvent();
case LEFT_BRACKET:
return createStartArrayEvent();
default:
throw new JsonParseException("no JSON format");
}
case ARRAY:
if (expectCommaOrEnd) {
if (token.getType() == JsonTokenType.COMMA) {
if (!tokenizer.hasNext()) {
throw new JsonParseException("no JSON format premature end");
}
token = tokenizer.nextToken();
} else if (token.getType() != JsonTokenType.RIGHT_BRACKET) {
throw new JsonParseException("no JSON format expected , or ]");
}
expectCommaOrEnd = false;
}
switch (token.getType()) {
case STRING:
case NUMBER:
case TRUE:
case FALSE:
expectCommaOrEnd = true;
return createValueEvent(token.getValue());
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;
}
default:
break;
}
}
this.previousEvent = null;
throw new RuntimeException("no event");
}
private JsonEvent createStartPropertyEvent(final String name) {
state.push(ReaderState.PROPERTY);
this.previousEvent = new JsonStartPropertyEventImpl() {
@Override
public String getName() {
return name;
}
};
return this.previousEvent;
}
private JsonEvent createEndPropertyEvent(final String value) {
state.pop();
this.previousEvent = new JsonEndPropertyEventImpl() {
@Override
public String getValue() {
return value;
}
@Override
public Object getObject() {
return value;
}
};
return this.previousEvent;
}
private JsonEvent createEndPropertyEventNumber(final String value) {
state.pop();
this.previousEvent = new JsonEndPropertyEventImpl() {
@Override
public String getValue() {
return value;
}
@Override
public Object getObject() {
return Double.parseDouble(value);
}
};
return this.previousEvent;
}
private JsonEvent createEndPropertyEventBoolean(final String value) {
state.pop();
this.previousEvent = new JsonEndPropertyEventImpl() {
@Override
public String getValue() {
return value;
}
@Override
public Object getObject() {
return Boolean.parseBoolean(value);
}
};
return this.previousEvent;
}
private JsonEvent createStartObjectEvent() {
state.push(ReaderState.OBJECT);
expectCommaOrEndStack.push(expectCommaOrEnd);
expectCommaOrEnd = false;
this.previousEvent = new JsonEventImpl() {
@Override
public boolean isStartObject() {
return true;
}
};
return this.previousEvent;
}
private JsonEvent createEndObjectEvent() {
state.pop();
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);
expectCommaOrEndStack.push(expectCommaOrEnd);
expectCommaOrEnd = false;
this.previousEvent = new JsonEventImpl() {
@Override
public boolean isStartArray() {
return true;
}
};
return this.previousEvent;
}
private JsonEvent createEndArrayEvent() {
state.pop();
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) {
this.previousEvent = new JsonValueEventImpl() {
@Override
public String getValue() {
return value;
}
};
return this.previousEvent;
}
@Override
public void close() {
tokenizer.close();
}
@Override
public JsonEvent previousEvent() {
return previousEvent;
}
}