![JAR search and dependency download from the Maven repository](/logo.png)
org.marc4j.util.JsonParser Maven / Gradle / Ivy
Show all versions of freelib-marc4j Show documentation
package org.marc4j.util;
// Created by Lawrence Dol. Released into the public domain.
//
// Source is licensed for any use, provided this copyright notice is retained.
// No warranty for any purpose whatsoever is implied or expressed. The author
// is not liable for any losses of any kind, direct or indirect, which result
// from the use of this software.
import java.io.BufferedInputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.ArrayList;
/**
* A pull-event parser for JSON data.
*
* Note that the event and member values are only valid while the parser is not
* searching for the next event.
*
* Threading Design : [x] Single Threaded [ ] Thread-safe [ ] Immutable [ ]
* Isolated
*
* @author Lawrence Dol
* @since Build 2007.0726.0032
*/
public class JsonParser extends Object {
// *****************************************************************************
// INSTANCE PROPERTIES
// *****************************************************************************
// PARSER CONSTANTS
private final boolean optEolIsComma; // parser option
private final boolean optInternKeywords; // parser option
private final boolean optInternValues; // parser option
private final boolean optMultilineComments; // parser option
private final boolean optMultilineStrings; // parser option
private final boolean optSingleQuoteStrings; // parser option
private final boolean optPreloadInput; // parser option
private final boolean optUnquotedKeywords; // parser option
/** stack of object data for nested objects **/
private final ArrayList objectStack;
private final StringBuilder accumulator; // text accumulator
// PARSER VARIABLES
private String inpName; // the name of the input source (for location
// reporting)
private Reader inpReader; // input inpReader
private boolean inpClose; // close input inpReader when it reaches end of
// stream
private int inpLine; // input line number
private int inpColumn; // input column
private ObjectData objectData; // current object data
private int pushBack; // push-back character (will be returned by next read)
// EVENT VARIABLES
private int evtCode; // current event code
private int evtLine; // current event line number
private int evtColumn; // current event column number
private String mbrName; // current event member name
private String mbrValue; // current event member value
// *****************************************************************************
// INSTANCE CREATE/DELETE
// *****************************************************************************
/**
* Construct a JSON parser from a character input source.
*
* @param opt Parsing option flags, combined from OPT_xxx values.
*/
public JsonParser(final int opt) {
super();
optEolIsComma = ((opt & OPT_EOL_IS_COMMA) != 0);
optInternKeywords = ((opt & OPT_INTERN_KEYWORDS) != 0);
optInternValues = ((opt & OPT_INTERN_VALUES) != 0);
optMultilineComments = ((opt & OPT_MULTILINE_COMMENTS) != 0);
optMultilineStrings = ((opt & OPT_MULTILINE_STRINGS) != 0);
optSingleQuoteStrings = ((opt & OPT_SINGLE_QUOTE_STRINGS) != 0);
optPreloadInput = ((opt & OPT_PRELOAD_INPUT) != 0);
optUnquotedKeywords = ((opt & OPT_UNQUOTED_KEYWORDS) != 0);
objectStack = new ArrayList();
accumulator = new StringBuilder();
reset(true);
}
/**
* Reset this parser, and clear any internal caches or pools used to improve
* performance. The parser may be reused if desired. This should be called
* after completing the parsing of all input sources.
*/
public void close() {
reset(true);
}
/**
* Reset this parser to be ready for more input after completing the parsing
* of some input. This includes closing the input source if it was set with
* the flag which requests this behavior. This should be called between
* input sources.
*/
public void reset() {
reset(false);
}
private void reset(final boolean all) {
if (inpClose && inpReader != null) {
try {
inpReader.close();
} catch (final Throwable ign) {
;
}
}
objectStack.clear();
accumulator.setLength(0);
if (all) {
accumulator.trimToSize();
}
inpName = null;
inpReader = null;
inpLine = 1;
inpColumn = 0;
objectData = new ObjectData("");
pushBack = -1;
evtCode = 0;
evtLine = 0;
evtColumn = 0;
mbrName = "";
mbrValue = "";
}
// *****************************************************************************
// INSTANCE METHODS - ACCESSORS
// *****************************************************************************
/**
* Get the name assigned to the input source at construction. This is
* informational only.
*/
public String getInputName() {
return inpName;
}
/** Get the current location in the input source. */
public Location getInputLocation() {
return createLocation(inpLine, inpColumn);
}
/** Get the name of the current object. */
public String getObjectName() {
return objectData.name;
}
/** Get the code for the current event. */
public int getEventCode() {
return evtCode;
}
/** Get the name for the current event. */
public String getEventName() {
return getEventName(evtCode);
}
/** Get the input source line number for the current event. */
public int getEventLine() {
return evtLine;
}
/** Get the input source column number for the current event. */
public int getEventColumn() {
return evtColumn;
}
/** Get the input source location for the current event. */
public Location getEventLocation() {
return createLocation(evtLine, evtColumn);
}
/** Get the current object member name. */
public String getMemberName() {
return mbrName;
}
/**
* Get the current object member value (valid only if the current event is
* EVT_OBJECT_MEMBER).
*
* NOTE
*
* All values are returned with EMCA-262 unescaping already applied. However
* if the value was specified in quotes it is return enclosed in quotes in
* order to reflect the explicit indication of a text value. This means that
* the string may contain ambiguous quotes when observed at face value,
* which situation is easily resolve using the static methods
* isQuoted()
and stripQuotes
. Alternatively, the
* methods getTypedMemberValue()
or
* createTypeValue()
may be used to get a Java typed object,
* which may be identified using instanceof
.
*/
public String getMemberValue() {
return mbrValue;
}
/**
* Get the current object member array flag. This indicates that the current
* member is an array.
*/
public boolean getMemberArray() {
return (objectData.arrayDepth != 0);
}
/**
* Get the current object member value applying JSON typing rules (valid
* only if the current event is EVT_OBJECT_MEMBER). All text values must be
* quoted, otherwise they will be treated as a special value or number.
*
* Actual return types are as follows:
*
* - "..." - String value after quotes are stripped.
*
- true - Boolean.TRUE (not case sensitive, unlike strict JSON
* specification requirement)
*
- false - Boolean.FALSE (not case sensitive, unlike strict JSON
* specification requirement)
*
- null - null (not case sensitive, unlike strict JSON specification
* requirement)
*
- Anything else - BigDecimal (exception thrown from the BigDecimal
* constructor if not a valid number)
*
*
* @throws NumberFormatException If an unquoted value which is no null, true
* or false is not a valid decimal number.
*/
static public Object getTypedMemberValue(final String val) {
return createTypedValue(val);
}
// *****************************************************************************
// INSTANCE METHODS
// *****************************************************************************
/**
* Construct a JSON parser from a character input source.
*
* @param inpnam A text description of the source, used only for location
* text.
* @param inpsrc Input source.
* @param inpcls Whether to close the input source at end-of-input.
*/
public JsonParser setInput(final String inpnam, final Reader inpsrc, final boolean inpcls) {
reset(false);
inpName = inpnam;
inpReader = inpsrc;
inpClose = inpcls;
if (optPreloadInput) {
inpReader = preloadInput(inpnam, inpReader, inpClose, 0);
inpClose = true;
}
return this;
}
/**
* Construct a JSON parser from a byte input source.
*
* @param inpnam A text description of the source, used only for location
* text.
* @param inpsrc Input source.
* @param inpenc Character encoding used by the input source.
* @param inpcls Whether to close the input source at end-of-input.
*/
public JsonParser setInput(final String inpnam, final InputStream inpsrc,
final String inpenc, final boolean inpcls) {
reset(false);
inpName = inpnam;
try {
inpReader = new InputStreamReader(inpsrc, inpenc);
} catch (final UnsupportedEncodingException thr) {
throw new Escape(Escape.BAD_ENCODING, "The encoding '" + inpenc +
"' is not supported by this Java Runtime Engine");
}
inpClose = inpcls;
if (optPreloadInput) {
inpReader = preloadInput(inpnam, inpReader, inpClose, 0);
inpClose = true;
}
return this;
}
/**
* Construct a JSON parser from a file input source.
*
* @param inpfil Input source.
* @param inpenc Character encoding used by the input source.
* @param bufsiz Size of input buffer for reading from the file.
*/
public JsonParser setInput(final String inpfil, final String inpenc, final int bufsiz) {
reset(false);
inpName = inpfil;
try {
inpReader =
new InputStreamReader(new BufferedInputStream(
new FileInputStream(inpfil), bufsiz), inpenc);
} catch (final UnsupportedEncodingException thr) {
throw new Escape(Escape.BAD_ENCODING, "The encoding '" + inpenc +
"' is not supported by this Java Runtime Engine", thr);
} catch (final IOException thr) {
throw new Escape(Escape.IOERROR, ("Could not access file \"" +
inpfil + "\": " + thr), thr);
}
inpClose = true;
if (optPreloadInput) {
inpReader =
preloadInput(inpfil, inpReader, inpClose, Math.min(
Integer.MAX_VALUE, (int) new File(inpfil).length()));
inpClose = true;
}
return this;
}
/**
* Construct a JSON parser from a file input source.
*
* @param inpfil Input source.
* @param inpenc Character encoding used by the input source.
* @param bufsiz Size of input buffer for reading from the file.
*/
public JsonParser setInput(final File inpfil, final String inpenc, final int bufsiz) {
reset(false);
inpName = inpfil.toString();
try {
inpReader =
new InputStreamReader(new BufferedInputStream(
new FileInputStream(inpfil), bufsiz), inpenc);
} catch (final UnsupportedEncodingException thr) {
throw new Escape(Escape.BAD_ENCODING, "The encoding '" + inpenc +
"' is not supported by this Java Runtime Engine", thr);
} catch (final IOException thr) {
throw new Escape(Escape.IOERROR, ("Could not access file \"" +
inpfil + "\": " + thr), thr);
}
inpClose = true;
if (optPreloadInput) {
inpReader =
preloadInput(inpfil.toString(), inpReader, inpClose, Math
.min(Integer.MAX_VALUE, (int) inpfil.length()));
inpClose = true;
}
return this;
}
/**
* Parse next event from input source.
*/
public int next() {
try {
if (evtCode != EVT_INPUT_ENDED) {
// previous event code
int pet = evtCode;
// flags when within quotes, and if they are single quotes
boolean qut = false, squ = false;
// flags whether we can accumulate more data (set false after
// closing quote, true after appropriate divider (: or ,))
boolean amd = true;
// flags that the previous input character was whitespace
boolean pws = false;
int ich; // input character as an integer
evtCode = 0;
evtLine = 0;
evtColumn = 0;
if (pet == EVT_OBJECT_BEGIN) {
mbrName = "";
} else if (pet == EVT_OBJECT_ENDED) {
popObjectData();
} else if (pet == EVT_ARRAY_ENDED) {
mbrName = "";
} else if (pet == EVT_OBJECT_MEMBER) {
mbrName = "";
}
mbrValue = null;
while ((ich = readChar()) != -1) {
if (!qut) {
// TEST FOR COMMENTS
if (ich == '*' || ich == '#') {
while ((ich = readChar()) != -1 && ich != '\n') {
;
}
} else if (ich == '/') {
int tmp = readChar();
if (tmp == '/') {
while ((ich = readChar()) != -1 && ich != '\n') {
;
}
} else if (tmp == '*') {
if (!optMultilineComments) {
throw parserError(
Escape.MALFORMED,
"Multiline comment not permitted by parser",
null, evtLine, evtColumn);
}
while ((tmp = readChar()) != -1) {
if (tmp == '*' && (tmp = readChar()) == '/') {
break;
}
}
if (tmp != '/') {
throw parserError(
Escape.MALFORMED,
"Multiline comment not closed before EOF",
null, evtLine, evtColumn);
}
ich = ' ';
} else { // one slash, but not two
unreadChar(tmp);
ich = '/';
}
}
if (ich == -1) {
break;
}
}
// ------------------------------------------------------------------------------------------------
// EACH OF THE FOLLOWING TESTS COMPLETES WITH A CONTINUE OR
// A RETURN OR THROWS (SEQUENCE MATTERS FOR THESE TESTS)
// ------------------------------------------------------------------------------------------------
if (ich == '\\') {
final int lin = inpLine, col = inpColumn;
if ((ich = readChar()) == -1) {
throw parserError(
Escape.BAD_ESCAPE,
"The input stream ended with an incomplete escape sequence",
null, lin, col);
}
switch (ich) {
case '"': {
storeChar('\"');
}
continue; // double quote
case '\\': {
storeChar('\\');
}
continue; // backslash
case '/': {
storeChar('\u2044');
}
continue; // solidus (I was suprised too!!)
case 'b': {
storeChar('\b');
}
continue; // backspace
case 'f': {
storeChar('\f');
}
continue; // form feed (aka vertical tab)
case 'n': {
storeChar('\n');
}
continue; // line feed
case 'r': {
storeChar('\r');
}
continue; // carriage return
case 't': {
storeChar('\t');
}
continue; // horizontal tab
case 'u': { // unicode 0x0000 - 0xFFFF
final int ic1 = readChar();
final int ic2 = readChar();
final int ic3 = readChar();
final int ic4 = readChar();
if (ic4 == -1) {
throw parserError(
Escape.BAD_ESCAPE,
"The input stream ended with an incomplete escape sequence",
null, lin, col);
}
storeChar((char) decodeHexChar((char) ic1,
(char) ic2, (char) ic3, (char) ic4,
lin, col));
}
continue;
case 'x': { // ascii 0x00-0xFF
final int ic1 = readChar();
final int ic2 = readChar();
if (ic2 == -1) {
throw parserError(
Escape.BAD_ESCAPE,
"The input stream ended with an incomplete escape sequence",
null, lin, col);
}
storeChar((char) decodeHexByte((char) ic1,
(char) ic2, lin, col));
}
continue;
default: {
}
throw parserError(
Escape.BAD_ESCAPE,
("The text string contains the invalid escape sequence '\\" + (char) ich),
null, lin, col);
}
}
if (qut) {
if (ich < 0x0020 &&
!(optMultilineStrings && (ich == '\r' || ich == '\n'))) {
throw parserError(
Escape.MALFORMED,
"A quoted literal may not contain any control characters" +
(optMultilineStrings
? " except CR and LF" : "") +
" - controls must be escaped using \\uHHHH");
}
if ((!squ && ich == '"') || (squ && ich == '\'')) {
qut = false;
amd = false;
}
storeChar((char) ich);
continue;
}
if (!optEolIsComma && (ich == '\r' || ich == '\n')) {
// ignore CR & LF if not treating EOL as a comma
continue;
}
if (ich == '"' || (optSingleQuoteStrings && ich == '\'')) {
if (accumulator.length() != 0) {
throw parserError(
Escape.MALFORMED,
"Text was found preceding an unescaped opening quote: \"" +
accumulator +
"\" (this is usually caused by a missing colon, a missing comma or " +
"missing quotes); Text=\"" +
accumulator.toString() + "\"");
}
if (!amd) {
throw parserError(
Escape.MALFORMED,
"A string value cannot contain unescaped quotes (this is usually caused by "
+ "a missing comma between members)");
}
qut = true;
squ = (ich == '\'');
storeChar((char) ich);
continue;
}
switch (ich) {
case ':': {
if (mbrName.length() > 0) {
throw parserError(
Escape.MALFORMED,
"An object member value contained a colon but was not enclosed in quotes "
+ "(this can often be caused by a missing comma between members)");
}
if (objectData.arrayDepth != 0) {
throw parserError(
Escape.MALFORMED,
"An array element cannot be a Name:Value pair - it must be only a value (this "
+ "is most likely caused by misplaced or missing closing bracket)");
}
final String txt = getAccumulatedText(true);
if (txt.length() == 0) {
throw parserError(Escape.MALFORMED,
"An object member name cannot be blank");
}
mbrName = stripQuotes(txt);
amd = true;
if (!optUnquotedKeywords && !isQuoted(txt)) {
throw parserError(Escape.MALFORMED,
"An object member name was not enclosed in quotes");
}
continue;
}
case ',':
case '\r':
case '\n': {
if (accumulator.length() == 0) { // empty value
// ignored
mbrName = ""; // continue as if member-value
// event was previously generated
pet = EVT_OBJECT_MEMBER; // ditto
} else if (pet == EVT_OBJECT_ENDED) {
throw parserError(
Escape.MALFORMED,
"Text was found between an object's closing brace and a subsequent comma or " +
"end of line (this is usually caused by a missing comma); Text=\"" +
accumulator.toString() + "\"");
} else if (pet == EVT_ARRAY_ENDED) {
throw parserError(
Escape.MALFORMED,
"Text was found between an array's closing bracket and a subsequent comma " +
"(this is usually caused by a missing comma); Text=\"" +
accumulator.toString() + "\"");
} else if (objectData.arrayDepth == 0 &&
mbrName.length() == 0) {
throw parserError(
Escape.MALFORMED,
"Object member name or value is missing in a Name:Value pair (this is "
+ "possibly caused by missing array brackets in an array)");
} else {
if (objectData.arrayDepth != 0) {
mbrName = objectData.arrayName;
}
mbrValue = getAccumulatedText(false);
return (evtCode = EVT_OBJECT_MEMBER);
}
amd = true;
continue;
}
case '{': {
if (accumulator.length() != 0) {
throw parserError(
Escape.MALFORMED,
"Text was found preceding an object's opening brace (this is usually caused " +
"by a missing comma or colon, or by using equals instead of a colon)" +
"; Text=\"" +
accumulator.toString() + "\"");
}
pushObjectData();
if (objectData.arrayDepth != 0) {
mbrName = objectData.arrayName;
}
objectData = new ObjectData(mbrName);
return (evtCode = EVT_OBJECT_BEGIN);
}
case '}': {
mbrValue = getAccumulatedText(false);
if (objectData.arrayDepth == 0 &&
mbrName.length() == 0 &&
mbrValue.length() > 0) {
throw parserError(
Escape.MALFORMED,
"Object member name or value is missing in a Name:Value pair (this is "
+ "possibly caused by missing array brackets in an array)");
} else if (mbrValue.length() > 0) {
unreadChar(ich);
if (objectData.arrayDepth != 0) {
mbrName = objectData.arrayName;
}
return (evtCode = EVT_OBJECT_MEMBER);
}
mbrName = objectData.name;
return (evtCode = EVT_OBJECT_ENDED);
}
case '[': {
if (accumulator.length() != 0) {
throw parserError(
Escape.MALFORMED,
"Text was found preceding an array's opening bracket (this is usually caused" +
" by a missing comma or colon, or by using equals instead of a" +
" colon); Text=\"" +
accumulator.toString() + "\"");
}
if (objectData.arrayDepth == 0) {
objectData.arrayName = mbrName;
} else {
mbrName = objectData.arrayName;
}
objectData.arrayDepth++;
return (evtCode = EVT_ARRAY_BEGIN);
}
case ']': {
if (objectData.arrayDepth == 0) {
throw parserError(
Escape.MALFORMED,
"Extraneous closing array bracket detected (this is usually caused by a "
+ "missing opening array bracket)");
}
mbrValue = getAccumulatedText(false);
if (mbrValue.length() > 0) {
unreadChar(ich);
if (objectData.arrayDepth != 0) {
mbrName = objectData.arrayName;
}
return (evtCode = EVT_OBJECT_MEMBER);
}
objectData.arrayDepth--;
mbrName = objectData.arrayName;
if (objectData.arrayDepth == 0) {
objectData.arrayName = "";
}
return (evtCode = EVT_ARRAY_ENDED);
}
default: {
if (Character.isWhitespace((char) ich)) {
pws = true;
} else {
if (!amd) {
throw parserError(
Escape.MALFORMED,
"A string value cannot contain data after its closing quote (this is "
+ "most likely caused by a missing comma between members)");
}
if (pws && accumulator.length() != 0) {
throw parserError(
Escape.MALFORMED,
"Text with embedded spaces was found but not enclosed in quotes (this " +
"is often caused by a missing comma following an unquoted" +
" value); Text=\"" +
accumulator.toString() +
"\"");
}
storeChar((char) ich);
pws = false;
}
continue;
}
}
}
// END OF INPUT REACHED
mbrName = null;
mbrValue = null;
if (objectStack.size() != 0) {
if (qut) {
throw parserError(
Escape.MALFORMED,
"A string's closing quote was missing from the input data (end of input was reached"
+ " before string was terminated)");
} else {
throw parserError(
Escape.MALFORMED,
"An object's closing brace was missing from the input data (end of input was reached"
+ " before object was terminated)");
}
}
if (objectData.arrayDepth != 0) {
throw parserError(Escape.MALFORMED, "Array named '" +
objectData.arrayName +
"' was not ended (end of input was reached before array was terminated)");
}
evtLine = inpLine;
evtColumn = inpColumn;
if (inpClose) {
try {
inpReader.close();
} catch (final Throwable ign) {
;
}
}
}
return (evtCode = EVT_INPUT_ENDED);
} catch (final IOException thr) {
if (inpClose) {
try {
inpReader.close();
} catch (final Throwable ign) {
;
}
}
throw new Escape(Escape.IOERROR, ("I/O Exception: " + thr), thr);
}
}
/**
* Skip the object that the parser is currently positioned at. The current
* event must be EVT_OBJECT_BEGIN.
*/
public void skipObject() {
if (getEventCode() != EVT_OBJECT_BEGIN) {
throw parserError(Escape.INVALID_STATE,
"An object can only be skipped when the current event is EVT_OBJECT_BEGIN");
}
int level = 1;
while (level > 0) {
final int eventCode = next();
if (eventCode == EVT_OBJECT_BEGIN) {
level++;
} else if (eventCode == EVT_OBJECT_ENDED) {
level--;
}
}
}
/**
* Skip the array that the parser is currently positioned at. The current
* event must be EVT_ARRAY_BEGIN.
*/
public void skipArray() {
if (getEventCode() != EVT_ARRAY_BEGIN) {
throw parserError(Escape.INVALID_STATE,
"An object can only be skipped when the current event is EVT_ARRAY_BEGIN");
}
int level = 1;
while (level > 0) {
final int eventCode = next();
if (eventCode == EVT_ARRAY_BEGIN) {
level++;
} else if (eventCode == EVT_ARRAY_ENDED) {
level--;
} else if (eventCode == EVT_OBJECT_BEGIN) {
skipObject();
}
}
}
private void storeChar(final char ch) {
if (evtLine == 0) {
evtLine = inpLine;
evtColumn = inpColumn;
}
accumulator.append(ch);
}
private int decodeHexByte(final char c1, final char c2, final int lin, final int col) {
try {
return decodeHexByte(c1, c2);
} catch (final Exception thr) {
throw parserError(Escape.BAD_ESCAPE, thr.getMessage(), null, lin,
col);
}
}
private int decodeHexChar(final char c1, final char c2, final char c3, final char c4, final int lin,
final int col) {
try {
return decodeHexChar(c1, c2, c3, c4);
} catch (final Exception thr) {
throw parserError(Escape.BAD_ESCAPE, thr.getMessage(), null, lin,
col);
}
}
private Location createLocation(final int lin, final int col) {
return new Location(inpName, lin, col, ((mbrName != null && mbrName
.length() != 0) ? mbrName : objectData.arrayName));
}
// *****************************************************************************
// INSTANCE METHODS - PRIVATE
// *****************************************************************************
private int readChar() throws IOException {
int ich;
if (pushBack != -1) {
ich = pushBack;
pushBack = -1;
inpColumn++;
} else {
if ((ich = inpReader.read()) != -1) {
if (ich == '\n') {
inpColumn = 0;
inpLine++;
} else if (ich == '\uFEFF') {
ich = ' ';
// FEFF is used as BOM; otherwise is a zero width space
} else {
inpColumn++;
}
}
}
return ich;
}
private void unreadChar(final int ich) throws IOException {
if (ich != -1) {
if (pushBack != -1) {
throw parserError(Escape.GENERAL, "Cannot unread '" +
(char) ich + "' the character '" + (char) pushBack +
"' is already pending");
}
pushBack = ich;
if (ich == '\n') {
inpColumn = 0;
inpLine--;
} else {
inpColumn--;
}
}
}
private void pushObjectData() {
objectStack.add(objectData);
}
private void popObjectData() {
final int siz = objectStack.size();
if (siz == 0) {
throw parserError(Escape.MALFORMED,
"An extraneous object closing brace was present in the input data");
}
if (objectData.arrayDepth != 0) {
throw parserError(Escape.MALFORMED, "Array named '" +
objectData.arrayName +
"' was not ended (end of enclosing object was reached before array was ended)");
}
objectData = objectStack.remove(siz - 1);
mbrName = objectData.name;
}
private String getAccumulatedText(final boolean kwd) {
String val;
val = accumulator.toString().trim();
accumulator.setLength(0);
if (kwd) {
if (optInternKeywords) {
val = val.intern();
}
} else {
if (optInternValues) {
val = val.intern();
}
}
return val;
}
private Escape parserError(final int cod, final String txt) {
return parserError(cod, txt, null);
}
private Escape parserError(final int cod, final String txt, final Throwable thr) {
return parserError(cod, txt, thr, inpLine, inpColumn);
}
private Escape parserError(final int cod, final String txt, final Throwable thr, final int lin,
final int col) {
return new Escape(cod, (txt + "; at " + createLocation(lin, col)), thr);
}
// *****************************************************************************
// INSTANCE INNER CLASSES
// *****************************************************************************
// *****************************************************************************
// STATIC NESTED CLASSES
// *****************************************************************************
static public final class Location extends Object {
private final String inpName;
private final int inpLine;
private final int inpCol;
private final String mbrName;
Location(final String inpnam, final int inplin, final int inpcol, final String mbrnam) {
inpName = inpnam;
inpLine = inplin;
inpCol = inpcol;
mbrName = (mbrnam == null || mbrnam.length() == 0 ? null : mbrnam);
}
/**
* Returns a string representation of the parser.
*/
@Override
public String toString() {
if (inpCol > 0) {
return ("Input Source: \"" + inpName + "\", Line: " + inpLine +
", Column: " + inpCol + (mbrName == null ? ""
: (", Member Name: " + mbrName)));
} else {
return ("Input Source: \"" + inpName + "\", Line: " +
(inpLine - 1) + ", Column: EOL" + (mbrName == null ? ""
: (", Member Name: " + mbrName)));
}
}
/**
* Gets the name of the input source.
*
* @return The name of the input source
*/
public String getInputSource() {
return inpName;
}
/**
* Gets the input line count.
*
* @return The input line count
*/
public int getInputLine() {
return inpLine;
}
/**
* Gets the count of the input column.
*
* @return The count of the input column
*/
public int getInputColumn() {
return inpCol;
}
/**
* Gets the member name.
*
* @return The member name
*/
public String getMemberName() {
return mbrName;
}
}
// *****************************************************************************
// STATIC NESTED CLASSES
// *****************************************************************************
static class ObjectData extends Object {
final String name; // object name (may be "")
String arrayName; // array member name for this object (initially "")
int arrayDepth; // array depth to detect when multi-dimensional array
// finally ends
ObjectData(final String nam) {
name = nam;
arrayName = "";
arrayDepth = 0;
}
}
// *****************************************************************************
// STATIC PROPERTIES
// *****************************************************************************
/** Returned from next() when the beginning of an object is read. */
static public final int EVT_OBJECT_BEGIN = 1;
/** Returned from next() when the end of an object is read. */
static public final int EVT_OBJECT_ENDED = 2;
/** Returned from next() when the beginning of a declared array is read. */
static public final int EVT_ARRAY_BEGIN = 3;
/** Returned from next() when the end of a declared array is read. */
static public final int EVT_ARRAY_ENDED = 4;
/** Returned from next() when the end of the input source is reached. */
static public final int EVT_INPUT_ENDED = 5;
/**
* Returned from next() when a simple object member (Name:Value pair or
* array element) is read.
*/
static public final int EVT_OBJECT_MEMBER = 6;
static private String[] EVT_NAMES = {"Invalid", "ObjectBegin",
"ObjectEnded", "ArrayBegin", "ArrayEnded", "InputEnded",
"ObjectMember"};
/** Option to allow keywords to be unquoted. */
static public final int OPT_UNQUOTED_KEYWORDS = 0x00000001;
/** Option to allow an end-of-line to be treated as a comma. */
static public final int OPT_EOL_IS_COMMA = 0x00000002;
/** Option to allow multiline comments using /* and */. */
static public final int OPT_MULTILINE_COMMENTS = 0x00000004;
/**
* Option to allow mutiline strings - this permits strings to be broken over
* multiple lines in an unambigous manner.
*/
static public final int OPT_MULTILINE_STRINGS = 0x00000008;
/** Option to allow single-quotes to be used for strings. */
static public final int OPT_SINGLE_QUOTE_STRINGS = 0x00000010;
/**
* Option to preload file input data when the input source is set - this is
* not a JSON compliance option. Preloading file contents can be useful in
* order to overlap loading and processing of multiple files using a
* deferred processing pipeline.
*/
static public final int OPT_PRELOAD_INPUT = 0x20000000;
/**
* Option to cause keywords to be interned (String.intern()) - this is not a
* JSON compliance option. Interning keywords is typically a good idea and
* is generally recommended.
*/
static public final int OPT_INTERN_KEYWORDS = 0x40000000;
/**
* Option to cause keywords to be interned (String.intern()) - this is not a
* JSON compliance option. Interning values is typically a good idea and is
* generally recommended.
*/
static public final int OPT_INTERN_VALUES = 0x80000000;
/** All options off. Input must conform strictly to the JSON spec. */
static public final int OPT_STRICT = 0;
/**
* Recommended options for messaging parsing mode - OPT_UNQUOTED_KEYWORDS,
* OPT_INTERN_KEYWORDS.
*/
static public final int OPT_MESSAGING =
(OPT_UNQUOTED_KEYWORDS | OPT_INTERN_KEYWORDS);
/**
* Recommended for config parsing mode - OPT_UNQUOTED_KEYWORDS,
* OPT_EOL_IS_COMMA, OPT_MULTILINE_COMMENTS, OPT_SINGLE_QUOTE_STRINGS,
* OPT_INTERN_KEYWORDS, OPT_INTERN_VALUES.
*/
static public final int OPT_CONFIG = (OPT_UNQUOTED_KEYWORDS |
OPT_EOL_IS_COMMA | OPT_MULTILINE_COMMENTS |
OPT_SINGLE_QUOTE_STRINGS | OPT_INTERN_KEYWORDS | OPT_INTERN_VALUES);
/** All options on. */
static public final int OPT_ALL = 0xFFFFFFFF;
// *****************************************************************************
// STATIC INIT & MAIN
// *****************************************************************************
// *****************************************************************************
// STATIC METHODS - PUBLIC UTILITY
// *****************************************************************************
/**
* Create a typed member value applying JSON typing rules. All text values
* must be quoted.
*
* Actual return types are as follows:
*
* - true - Boolean.TRUE (not case sensitive)
*
- false - Boolean.FALSE (not case sensitive)
*
- null - Java null (not case sensitive)
*
- "..." - String value after quotes are stripped
*
- Anything else - BigDecimal (exception thrown from the BigDecimal
* constructor if not a valid number)
*
*
* @throws NumberFormatException If an unquoted value which is no null, true
* or false is not a valid decimal number.
*/
static public Object createTypedValue(final String val) {
if (val.equalsIgnoreCase("null")) {
return null;
} else if (val.equalsIgnoreCase("true")) {
return Boolean.TRUE;
} else if (val.equalsIgnoreCase("false")) {
return Boolean.FALSE;
} else if (isQuoted(val)) {
return stripQuotes(val);
} else if (val.length() == 0) {
return val;
} else {
return new BigDecimal(val);
}
}
/**
* Get a text name for the event code.
*/
static public String getEventName(final int cod) {
if (cod < 1) {
return EVT_NAMES[0];
} else if (cod >= EVT_NAMES.length) {
return ("EVT_" + cod);
} else {
return EVT_NAMES[cod];
}
}
/**
* Strip the text-value-indicating quotes from the supplied member value, if
* any.
*/
static public String stripQuotes(String val) {
if (isQuoted(val)) {
val = val.substring(1, val.length() - 1);
}
return val;
}
/**
* Test if the member value is enclosed in text-value-indicating quotes.
*/
static public boolean isQuoted(final String val) {
int len;
if (val != null && (len = val.length()) > 1) {
final char ch0 = val.charAt(0);
if ((ch0 == '"' || ch0 == '\'') && ch0 == val.charAt(len - 1)) {
return true; // NOTE: Don't test for unescaped quotes because
// the value has already been unescaped as it was
// parsed.
}
}
return false;
}
// *****************************************************************************
// STATIC METHODS - PRIVATE UTILITY
// *****************************************************************************
static private Reader preloadInput(final String inpnam, final Reader inprdr,
final boolean inpcls, final int sizest) throws Escape {
CharArrayWriter wtr; // writer
final char[] buf = new char[10240]; // read buffer
int len; // read length
try {
wtr = new CharArrayWriter(sizest > 0 ? sizest : 10240);
while ((len = inprdr.read(buf)) != -1) {
wtr.write(buf, 0, len);
}
wtr.flush();
if (inpcls) {
wtr.close();
}
return new CharArrayReader(wtr.toCharArray());
} catch (final IOException thr) {
throw new Escape(Escape.IOERROR, ("Could not read input from \"" +
inpnam + "\": " + thr), thr);
}
}
// *****************************************************************************
// STATIC METHODS - HEX ENCODE/DECODE
// *****************************************************************************
static int[] decodeHex; // hex table for decoding hex-based escapes
static {
decodeHex = new int[256];
for (int xa = 0; xa < decodeHex.length; xa++) {
decodeHex[xa] = -1;
}
decodeHex['0'] = 0;
decodeHex['1'] = 1;
decodeHex['2'] = 2;
decodeHex['3'] = 3;
decodeHex['4'] = 4;
decodeHex['5'] = 5;
decodeHex['6'] = 6;
decodeHex['7'] = 7;
decodeHex['8'] = 8;
decodeHex['9'] = 9;
decodeHex['A'] = 10;
decodeHex['B'] = 11;
decodeHex['C'] = 12;
decodeHex['D'] = 13;
decodeHex['E'] = 14;
decodeHex['F'] = 15;
decodeHex['a'] = 10;
decodeHex['b'] = 11;
decodeHex['c'] = 12;
decodeHex['d'] = 13;
decodeHex['e'] = 14;
decodeHex['f'] = 15;
}
static private int decodeHexByte(final char hex1, final char hex2) {
int n1; // nibble 1
int n2; // nibble 2
if (hex1 > 0xFF || (n1 = decodeHex[hex1]) == -1) {
throw new RuntimeException(
"Escape sequence contains the invalid hexadecimal digit '" +
hex1 + "'; not 0-9, a-f or A-F");
}
if (hex2 > 0xFF || (n2 = decodeHex[hex2]) == -1) {
throw new RuntimeException(
"Escape sequence contains the invalid hexadecimal digit '" +
hex2 + "'; not 0-9, a-f or A-F");
}
return ((n1 << 4) | n2);
}
static private int decodeHexChar(final char h1, final char h2, final char h3, final char h4) {
return ((decodeHexByte(h1, h2) << 8) | (decodeHexByte(h3, h4)));
}
// *****************************************************************************
// STATIC NESTED CLASSES - ESCAPE (COULD DROP THIS FOR RUNTIME EXCEPTION)
// *****************************************************************************
static public class Escape extends RuntimeException {
/** General exception. */
static public final int GENERAL = 1;
/** Input/Output error. */
static public final int IOERROR = 2;
/** Invalid encoding. */
static public final int BAD_ENCODING = 3;
/**
* Malformed input data - the data was not valid JSON data-interchange
* format.
*/
static public final int MALFORMED = 4;
/** A bad escape sequence was encountered. */
static public final int BAD_ESCAPE = 6;
/**
* An method was invokeD when the parser was in an invalid state for it.
*/
static public final int INVALID_STATE = 7;
/** An method could not be reflectively retrieved or invoked. */
static public final int METHOD_ERROR = 8;
/** Data-type conversion error. */
static public final int CONVERSION = 9;
/** Minimum code required for any sub-class. */
static public final int SUBCLSMIN = 1000;
private final int code;
/**
* Create an exception with a code and details. It is recommened that
* the detail always include very specific information about the cause
* of the error.
*
* @param code The error code.
* @param detail Specific detail text indicating the cause of the error.
*/
public Escape(final int code, final String detail) {
this(code, detail, null);
}
/**
* Create an exception with a code and details. It is recommened that
* the detail always include very specific information about the cause
* of the error.
*
* @param code The error code.
* @param detail Specific detail text indicating the cause of the error.
* @param cause The causitive throwable object, if any.
*/
public Escape(final int code, final String detail, final Throwable cause) {
super(detail, cause);
this.code = code;
}
/** Return the numeric code for this exception condition. */
public int getCode() {
return code;
}
}
} // END PUBLIC CLASS