/**
* Copyright (C) 2010-2011 J.W.Marsden
*
* 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 jsonij.json;
import static jsonij.json.Constants.BACKSPACE;
import static jsonij.json.Constants.BACKSPACE_CHAR;
import static jsonij.json.Constants.OPEN_ARRAY;
import static jsonij.json.Constants.OPEN_OBJECT;
import static jsonij.json.Constants.CARRIAGE_RETURN;
import static jsonij.json.Constants.CARRIAGE_RETURN_CHAR;
import static jsonij.json.Constants.DECIMAL_POINT;
import static jsonij.json.Constants.DIGITS;
import static jsonij.json.Constants.QUOTATION_MARK;
import static jsonij.json.Constants.CLOSE_ARRAY;
import static jsonij.json.Constants.CLOSE_OBJECT;
import static jsonij.json.Constants.ESCAPE;
import static jsonij.json.Constants.EXPS;
import static jsonij.json.Constants.FALSE_STR;
import static jsonij.json.Constants.FORM_FEED;
import static jsonij.json.Constants.FORM_FEED_CHAR;
import static jsonij.json.Constants.HEX_CHAR;
import static jsonij.json.Constants.MINUS;
import static jsonij.json.Constants.NAME_SEPARATOR;
import static jsonij.json.Constants.NEW_LINE;
import static jsonij.json.Constants.NEW_LINE_CHAR;
import static jsonij.json.Constants.NULL_STR;
import static jsonij.json.Constants.PLUS;
import static jsonij.json.Constants.QUOTATION;
import static jsonij.json.Constants.REVERSE_SOLIDUS;
import static jsonij.json.Constants.SOLIDUS_CHAR;
import static jsonij.json.Constants.TAB;
import static jsonij.json.Constants.TAB_CHAR;
import static jsonij.json.Constants.TRUE_STR;
import static jsonij.json.Constants.VALUE_SEPARATOR;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import jsonij.json.JSONReader.JSONStringReader;
import jsonij.parser.ParserException;
/**
*
*
* @author jmarsden
* @version 1
*/
public class JSONParser {
protected static final Logger logger = Logger.getLogger(JSONParser.class.getName());
protected Locale locale;
public JSONParser() {
locale = Locale.ENGLISH;
}
/**
* @return the locale
*/
public Locale getLocale() {
return locale;
}
/**
* @param locale the locale to set
*/
public Locale setLocale(Locale locale) {
return this.locale = locale;
}
public Value parse(java.lang.String target) throws IOException, ParserException {
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), "parse(String target)", target);
}
Value value = null;
ByteArrayInputStream targetArrayStream = new ByteArrayInputStream(target.getBytes());
InputStreamReader targetReader = new InputStreamReader(targetArrayStream);
value = parse(targetReader);
targetReader.close();
targetArrayStream.close();
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), "parse(String target)", value);
}
return value;
}
public synchronized Value parse(Reader targetReader) throws IOException, ParserException {
String method = "parse(JSONReader targetReader)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, targetReader);
}
if (targetReader == null) {
throw new NullPointerException();
}
JSONReader target = new JSONReader(targetReader);
int r = target.peek();
if (r == -1) {
throw new JSONParserException("invalidEmpty");
}
Value value = null;
if (r == OPEN_OBJECT) {
value = parseObject(target);
} else if (r == OPEN_ARRAY) {
value = parseArray(target);
} else {
throw new JSONParserException("invalidExpecting2", (char) OPEN_OBJECT, (char) OPEN_ARRAY, (char) r);
}
if (target.peek() != -1) {
// TODO: Extra Junk. Add Warning.
}
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
public Value parseValue(java.lang.String target) throws IOException, ParserException {
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), "parse(String target)", target);
}
Value value = null;
ByteArrayInputStream targetArrayStream = new ByteArrayInputStream(target.getBytes());
InputStreamReader targetReader = new InputStreamReader(targetArrayStream);
value = parseValue(new JSONReader(targetReader));
targetReader.close();
targetArrayStream.close();
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), "parse(String target)", value);
}
return value;
}
/**
* Parse a JSON Value from the target.
*
* @param target Reader to read from.
* @return The JSON Value instance just parsed(getMessages().getString("invalidValue")
* @throws IOException IO Exception
* @throws ParserException JSON Parser Exception
*/
protected Value parseValue(JSONReader target) throws IOException, ParserException {
String method = "parseValue(JSONReader target)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, target);
}
Value value = null;
if (target.peek() == OPEN_OBJECT) {
value = parseObject(target);
} else if (target.peek() == OPEN_ARRAY) {
value = parseArray(target);
} else if (target.peek() == QUOTATION) {
value = parseString(target);
} else if (ConstantUtility.isNumeric(target.peek())) {
value = parseNumeric(target);
} else if (target.peek() == TRUE_STR.charAt(0)) {
value = parseTrue(target);
} else if (target.peek() == FALSE_STR.charAt(0)) {
value = parseFalse(target);
} else if (target.peek() == NULL_STR.charAt(0)) {
value = parseNull(target);
} else {
throw new JSONParserException("invalidUnexpected", target.getLineNumber(), target.getPositionNumber(), (char) target.peek());
}
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
/**
* Parses a JSON Object Value from the reader.
*
* @param target The reader to read the value from
* @return The Object Value
* @throws IOException IO Exception
* @throws ParserException JSON Parser Exception
*/
protected JSON.Object<JSON.String, Value> parseObject(JSONReader target) throws IOException, ParserException {
String method = "parseObject(JSONReader target)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, target);
}
if (target.peek() != OPEN_OBJECT) {
throw new JSONParserException("invalidObjectExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) OPEN_OBJECT, (char) target.peek());
}
target.read();
JSON.Object<JSON.String, Value> value = new JSON.Object<JSON.String, Value>();
if (target.peek() != CLOSE_OBJECT) {
JSON.String attributeName = (JSON.String) parseString(target);
if (target.peek() == NAME_SEPARATOR) {
target.read();
} else {
throw new JSONParserException("invalidObjectExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) NAME_SEPARATOR, (char) target.peek());
}
Value attributeValue = parseValue(target);
value.put(attributeName, attributeValue);
while (target.peek() == VALUE_SEPARATOR) {
target.read();
attributeName = (JSON.String) parseString(target);
if (target.peek() == NAME_SEPARATOR) {
target.read();
} else {
throw new JSONParserException("invalidObjectExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) NAME_SEPARATOR, (char) target.peek());
}
attributeValue = parseValue(target);
value.put(attributeName, attributeValue);
}
}
if (target.peek() == CLOSE_OBJECT) {
target.read();
} else {
throw new JSONParserException("invalidArrayExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) CLOSE_OBJECT, (char) target.peek());
}
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
/**
* Parses a JSON Array Value from the reader.
*
* @param target The reader to read the value from
* @return The Array Value
* @throws IOException IO Exception
* @throws ParserException JSON Parser Exception
*/
protected JSON.Array<Value> parseArray(JSONReader target) throws IOException, ParserException {
String method = "parseArray(JSONReader target)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, target);
}
if (target.peek() != OPEN_ARRAY) {
throw new JSONParserException("invalidArrayExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) OPEN_ARRAY, (char) target.peek());
}
target.read();
JSON.Array<Value> value = new JSON.Array<Value>();
if (target.peek() != CLOSE_ARRAY) {
Value arrayValue = parseValue(target);
value.add(arrayValue);
while (target.peek() == VALUE_SEPARATOR) {
target.read();
arrayValue = parseValue(target);
value.add(arrayValue);
}
}
if (target.peek() == CLOSE_ARRAY) {
target.read();
} else {
throw new JSONParserException("invalidArrayExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) CLOSE_ARRAY, (char) target.peek());
}
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
/**
* Parses a JSON String Value from the reader.
*
* @param target The reader to read the value from
* @return The String Value
* @throws IOException IO Exception
* @throws ParserException JSON Parser Exception
*/
protected JSON.String parseString(JSONReader target) throws IOException, ParserException {
String method = "parseString(JSONReader target)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, target);
}
JSON.String value = null;
if (target.peek() != QUOTATION) {
throw new JSONParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) QUOTATION, (char) target.peek());
}
String valueString = "";
JSONStringReader targetString = target.getStringReader();
targetString.read();
while (true) {
if (targetString.peek() == QUOTATION) {
break;
} else if (targetString.peek() == REVERSE_SOLIDUS) {
targetString.read();
switch (targetString.peek()) {
case QUOTATION:
valueString += (char) QUOTATION_MARK;
targetString.read();
break;
case REVERSE_SOLIDUS:
valueString += (char) ESCAPE;
targetString.read();
break;
case SOLIDUS_CHAR:
valueString += (char) SOLIDUS_CHAR;
targetString.read();
break;
case BACKSPACE_CHAR:
valueString += (char) BACKSPACE;
targetString.read();
break;
case FORM_FEED_CHAR:
valueString += (char) FORM_FEED;
targetString.read();
break;
case NEW_LINE_CHAR:
valueString += (char) NEW_LINE;
targetString.read();
break;
case CARRIAGE_RETURN_CHAR:
valueString += (char) CARRIAGE_RETURN;
targetString.read();
break;
case TAB_CHAR:
valueString += (char) TAB;
targetString.read();
break;
case HEX_CHAR:
targetString.read();
String unicodeString = "";
for (int i = 0; i < 4; i++) {
if (ConstantUtility.isHexDigit(targetString.peek())) {
unicodeString += (char) targetString.read();
} else {
throw new JSONParserException("invalidStringHex", target.getLineNumber(), target.getPositionNumber(), targetString.peek());
}
}
int unicodeInt = Integer.parseInt(unicodeString.toUpperCase(), 16);
if (Character.isHighSurrogate((char) unicodeInt)) {
String highSurrogateString = unicodeString;
int highSurrogate = unicodeInt;
unicodeString = "";
if (targetString.peek() == REVERSE_SOLIDUS) {
targetString.read();
} else {
throw new JSONParserException("invalidStringMissingSurrogate", target.getLineNumber(), target.getPositionNumber(), REVERSE_SOLIDUS, targetString.peek());
}
if (targetString.peek() == HEX_CHAR) {
targetString.read();
} else {
throw new JSONParserException("invalidStringMissingSurrogate", target.getLineNumber(), target.getPositionNumber(), HEX_CHAR, targetString.peek());
}
for (int i = 0; i < 4; i++) {
if (ConstantUtility.isHexDigit(targetString.peek())) {
unicodeString += (char) targetString.read();
} else {
throw new JSONParserException("invalidStringHex", target.getLineNumber(), target.getPositionNumber(), targetString.peek());
}
}
String lowSurrogateString = unicodeString;
int lowSurrogate = Integer.parseInt(unicodeString.toUpperCase(), 16);
if (Character.isSurrogatePair((char) highSurrogate, (char) lowSurrogate)) {
valueString += new String(Character.toChars(Character.toCodePoint((char) highSurrogate, (char) lowSurrogate)));
} else {
throw new JSONParserException("invalidStringSurrogates", target.getLineNumber(), target.getPositionNumber(), highSurrogateString, lowSurrogateString);
}
} else {
valueString += (char) unicodeInt;
}
break;
default:
throw new JSONParserException("invalidStringEscape", target.getLineNumber(), target.getPositionNumber(), targetString.peek());
}
} else {
if (ConstantUtility.isValidStringChar(targetString.peek())) {
valueString += (char) targetString.read();
} else {
throw new JSONParserException("invalidStringValue", target.getLineNumber(), target.getPositionNumber(), (char) targetString.peek());
}
}
}
if (targetString.peek() != QUOTATION) {
throw new JSONParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) QUOTATION, (char) targetString.peek());
} else {
targetString.read();
}
targetString.close();
value = new JSON.String(valueString);
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
/**
* Parses a JSON Numeric Value from the reader.
*
* @param target The reader to reawarningsd the value from
* @return The Value of the numeric
* @throws IOException IO Exception
* @throws ParserException JSON Parser Exception
*/
protected JSON.Numeric parseNumeric(JSONReader target) throws IOException, ParserException {
String method = "parseNumeric(JSONReader target)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, target);
}
JSON.Numeric value = null;
java.lang.String numericString = "";
NumberFormat format = null;
Number number = null;
if (target.peek() == MINUS) {
target.read();
numericString += (char) MINUS;
if (!(target.peek() >= DIGITS[0] && target.peek() <= DIGITS[9])) {
throw new JSONParserException("invalidNumericExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) target.peek());
}
}
if (target.peek() == DIGITS[0]) {
numericString += (char) target.read();
} else if (target.peek() >= DIGITS[1] && target.peek() <= DIGITS[9]) {
numericString += (char) target.read();
while (ConstantUtility.isDigit(target.peek())) {
numericString += (char) target.read();
}
} else {
throw new JSONParserException("invalidNumericExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) target.peek());
}
if (target.peek() == DECIMAL_POINT) {
target.read();
numericString += (char) DECIMAL_POINT;
if (!(target.peek() >= DIGITS[0] && target.peek() <= DIGITS[9])) {
throw new JSONParserException("invalidNumericExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) target.peek());
}
while (ConstantUtility.isDigit(target.peek())) {
numericString += (char) target.read();
}
}
if (target.peek() == EXPS[0] || target.peek() == EXPS[1]) {
target.read();
numericString += (char) EXPS[1];
if (target.peek() == MINUS) {
target.read();
numericString += (char) MINUS;
} else if (target.peek() == PLUS) {
target.read();
numericString += (char) PLUS;
} else {
numericString += (char) PLUS;
}
if (!(target.peek() >= DIGITS[0] && target.peek() <= DIGITS[9])) {
throw new JSONParserException("invalidNumericExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) target.peek());
}
while (ConstantUtility.isDigit(target.peek())) {
numericString += (char) target.read();
}
}
format = new DecimalFormat();
try {
number = format.parse(numericString);
} catch (ParseException e) {
throw new JSONParserException("invalidNumericParse", numericString, e);
}
if (number != null) {
value = new JSON.Numeric(number);
}
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
/**
* Parses a JSON True Value from the reader.
*
* @param target The reader to read the value from
* @return The False Value
* @throws IOException IO Exception
* @throws ParserException JSON Parser Exception
*/
protected JSON.True parseTrue(JSONReader target) throws IOException, ParserException {
String method = "parseTrue(JSONReader target)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, target);
}
JSON.True value = null;
for (int i = 0; i < TRUE_STR.length(); i++) {
if (target.peek() == TRUE_STR.charAt(i)) {
target.read();
} else {
throw new JSONParserException("invalidValue", target.getLineNumber(), target.getPositionNumber(), TRUE_STR, (char) target.peek());
}
}
value = JSON.TRUE;
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
/**
* Parses a JSON False Value from the reader.
*
* @param target The reader to read the value from
* @return The False Value
* @throws IOException IO Exception
* @throws ParserException JSON Parser Exception
*/
protected JSON.False parseFalse(JSONReader target) throws IOException, ParserException {
String method = "parseFalse(JSONReader target)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, target);
}
JSON.False value = null;
for (int i = 0; i < FALSE_STR.length(); i++) {
if (target.peek() == FALSE_STR.charAt(i)) {
target.read();
} else {
throw new JSONParserException("invalidValue", target.getLineNumber(), target.getPositionNumber(), FALSE_STR, (char) target.peek());
}
}
value = JSON.FALSE;
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
/**
* Parses a JSON Null Value from the reader.
*
* @param target The reader to read the value from
* @return The Null Value
* @throws IOException IO Exception
* @throws ParserException JSON Parser Exception
*/
protected JSON.Null parseNull(JSONReader target) throws IOException, ParserException {
String method = "parseNull(JSONReader target)";
if (logger.isLoggable(Level.FINER)) {
logger.entering(getClass().getName(), method, target);
}
JSON.Null value = null;
for (int i = 0; i < NULL_STR.length(); i++) {
if (target.peek() == NULL_STR.charAt(i)) {
target.read();
} else {
throw new JSONParserException("invalidValue", target.getLineNumber(), target.getPositionNumber(), NULL_STR, (char) target.peek());
}
}
value = JSON.NULL;
if (logger.isLoggable(Level.FINER)) {
logger.exiting(getClass().getName(), method, value);
}
return value;
}
}