/*
* Copyright 2011, 2012 Odysseus Software GmbH
*
* 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 org.apache.synapse.commons.staxon.core.json.stream.impl;
import java.io.Closeable;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamSource;
import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamToken;
/**
* Default <code>JsonStreamSource</code> implementation.
*/
class JsonStreamSourceImpl implements JsonStreamSource {
/**
* Scanner interface
*/
interface Scanner extends Closeable {
enum Symbol {
START_OBJECT("START_OBJECT"),
SO_ARRAY("SO_ARRAY"),
SO_ELEMENT("SO_ELEMENT"),
SO_COLON_1("SO_COLON_1"),
SO_COLON_2("SO_COLON_2"),
SO_ARRAY_END("SO_ARRAY_END"),
SO_ARRAY_END_2("SO_ARRAY_END_2"),
SO_END("SO_END"),
SO_END_2("SO_END_2"),
SO_OBJECT("SO_OBJECT"),
SO_OBJECT_COL("SO_OBJECT_COL"),
SO_OBJECT_END("SO_OBJECT_END"),
END_OBJECT("END_OBJECT"),
START_ARRAY("START_ARRAY"),
END_ARRAY("END_ARRAY"),
COLON("COLON"),
COMMA("COMMA"),
STRING("STRING"),
NUMBER("NUMBER"),
TRUE("TRUE"),
FALSE("FALSE"),
NULL("NULL"),
EMPTY_OBJ_NAME("EMPTY_OBJ_NAME"),
EMPTY_OBJ_VALUE("EMPTY_OBJ_VALUE"),
EMPTY_OBJ_END("EMPTY_OBJ_END"),
EOF("EOF"),
EOF_OBJ("EOF_OBJ");
private String name = null;
Symbol(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
Symbol nextSymbol() throws IOException;
String getText();
int getCharOffset();
int getLineNumber();
int getColumnNumber();
}
private final Scanner scanner;
private final boolean[] arrays = new boolean[64];
private final boolean closeScanner;
private JsonStreamToken token = null;
private Scanner.Symbol symbol = null;
private int depth = 0;
private boolean peeked = false;
private int lineNumber;
private int columnNumber;
private int charOffset;
JsonStreamSourceImpl(Scanner scanner, boolean closeScanner) {
this.scanner = scanner;
this.closeScanner = closeScanner;
this.lineNumber = scanner.getLineNumber();
this.columnNumber = scanner.getColumnNumber();
this.charOffset = scanner.getCharOffset();
}
private JsonStreamToken startJsonValue() throws IOException {
switch (symbol) {
case FALSE:
case NULL:
case NUMBER:
case TRUE:
case STRING:
return JsonStreamToken.VALUE;
case START_ARRAY:
if (arrays[depth]) {
throw new IOException("Already in an array");
}
arrays[depth] = true;
return JsonStreamToken.START_ARRAY;
case START_OBJECT:
depth++;
return JsonStreamToken.START_OBJECT;
default:
throw new IOException("Unexpected symbol: " + symbol);
}
}
private void require(Scanner.Symbol expected) throws IOException {
if (symbol != expected) {
throw new IOException("Unexpected symbol:" + symbol);
}
}
private JsonStreamToken next() throws IOException {
symbol = scanner.nextSymbol();
if (symbol == Scanner.Symbol.EOF) {
if (depth != 0 || arrays[depth]) {
throw new IOException("Premature EOF");
}
return JsonStreamToken.NONE;
}
if (token == null) {
return startJsonValue();
}
switch (token) {
case NAME:
require(Scanner.Symbol.COLON);
symbol = scanner.nextSymbol();
return startJsonValue();
case END_OBJECT:
case END_ARRAY:
case VALUE:
switch (symbol) {
case COMMA:
symbol = scanner.nextSymbol();
if (arrays[depth]) {
return startJsonValue();
} else {
require(Scanner.Symbol.STRING);
return JsonStreamToken.NAME;
}
case END_ARRAY:
if (!arrays[depth]) {
throw new IOException("Not in an array");
}
arrays[depth] = false;
return JsonStreamToken.END_ARRAY;
case END_OBJECT:
if (arrays[depth]) {
throw new IOException("Unclosed array");
}
if (depth == 0) {
throw new IOException("Not in an object");
}
depth--;
return JsonStreamToken.END_OBJECT;
default:
throw new IOException("Unexpected symbol: " + symbol);
}
case START_OBJECT:
switch (symbol) {
case END_OBJECT:
depth--;
return JsonStreamToken.END_OBJECT;
case STRING:
return JsonStreamToken.NAME;
default:
throw new IOException("Unexpected symbol: " + symbol);
}
case START_ARRAY:
switch (symbol) {
case END_ARRAY:
arrays[depth] = false;
return JsonStreamToken.END_ARRAY;
default:
return startJsonValue();
}
default:
throw new IOException("Unexpected token: " + token);
}
}
public void close() throws IOException {
if (closeScanner) {
scanner.close();
}
}
/**
* Make the next token the current token.
* Save location info from scanner to prevent changing location by peek()
*
* @param token expected token
* @throws IOException
*/
private void poll(JsonStreamToken token) throws IOException {
if (token != peek()) {
throw new IOException("Unexpected token: " + peek());
}
lineNumber = scanner.getLineNumber();
columnNumber = scanner.getColumnNumber();
charOffset = scanner.getCharOffset();
peeked = false;
}
public String name() throws IOException {
poll(JsonStreamToken.NAME);
return scanner.getText();
}
public Value value() throws IOException {
poll(JsonStreamToken.VALUE);
switch (symbol) {
case NULL:
return NULL;
case STRING:
return new Value(scanner.getText());
case TRUE:
return TRUE;
case FALSE:
return FALSE;
case NUMBER:
if (scanner.getText().indexOf('.') < 0 && scanner.getText().toLowerCase().indexOf('e') < 0) {
return new Value(scanner.getText(), new BigInteger(scanner.getText()));
} else {
return new Value(scanner.getText(), new BigDecimal(scanner.getText()));
}
default:
throw new IOException("Not a value token: " + symbol);
}
}
public void startObject() throws IOException {
poll(JsonStreamToken.START_OBJECT);
}
public void endObject() throws IOException {
poll(JsonStreamToken.END_OBJECT);
}
public void startArray() throws IOException {
poll(JsonStreamToken.START_ARRAY);
}
public void endArray() throws IOException {
poll(JsonStreamToken.END_ARRAY);
}
public JsonStreamToken peek() throws IOException {
if (!peeked) {
token = next();
peeked = true;
}
return token;
}
public int getLineNumber() {
return lineNumber + 1;
}
public int getColumnNumber() {
return columnNumber + 1;
}
public int getCharacterOffset() {
return charOffset;
}
public String getPublicId() {
return null;
}
public String getSystemId() {
return null;
}
}