org.chenillekit.google.utils.JSONTokener Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of chenillekit-google Show documentation
Show all versions of chenillekit-google Show documentation
A collection of sophisticated Ajax-enabled components based on the prototype/script.aculo.us JavaScript library
and many more ...
/*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* Copyright 2008 by chenillekit.org
*
* 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
*
*/
package org.chenillekit.google.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
/**
* A JSONTokener takes a source string and extracts characters and tokens from
* it. It is used by the JSONObject and JSONArray constructors to parse
* JSON source strings.
*
* @author JSON.org
* @version 3
*/
public class JSONTokener
{
private int index;
private Reader reader;
private char lastChar;
private boolean useLastChar;
/**
* Construct a JSONTokener from a string.
*
* @param reader A reader.
*/
public JSONTokener(Reader reader)
{
this.reader = reader.markSupported() ?
reader : new BufferedReader(reader);
this.useLastChar = false;
this.index = 0;
}
/**
* Construct a JSONTokener from a string.
*
* @param s A source string.
*/
public JSONTokener(String s)
{
this(new StringReader(s));
}
/**
* Back up one character. This provides a sort of lookahead capability,
* so that you can test for a digit or letter before attempting to parse
* the next number or identifier.
*/
public void back() throws JSONException
{
if (useLastChar || index <= 0)
{
throw new JSONException("Stepping back two steps is not supported");
}
index -= 1;
useLastChar = true;
}
/**
* Get the hex value of a character (base16).
*
* @param c A character between '0' and '9' or between 'A' and 'F' or
* between 'a' and 'f'.
*
* @return An int between 0 and 15, or -1 if c was not a hex digit.
*/
public static int dehexchar(char c)
{
if (c >= '0' && c <= '9')
{
return c - '0';
}
if (c >= 'A' && c <= 'F')
{
return c - ('A' - 10);
}
if (c >= 'a' && c <= 'f')
{
return c - ('a' - 10);
}
return -1;
}
/**
* Determine if the source string still contains characters that next()
* can consume.
*
* @return true if not yet at the end of the source.
*/
public boolean more() throws JSONException
{
char nextChar = next();
if (nextChar == 0)
{
return false;
}
back();
return true;
}
/**
* Get the next character in the source string.
*
* @return The next character, or 0 if past the end of the source string.
*/
public char next() throws JSONException
{
if (this.useLastChar)
{
this.useLastChar = false;
if (this.lastChar != 0)
{
this.index += 1;
}
return this.lastChar;
}
int c;
try
{
c = this.reader.read();
}
catch (IOException exc)
{
throw new JSONException(exc);
}
if (c <= 0)
{ // End of stream
this.lastChar = 0;
return 0;
}
this.index += 1;
this.lastChar = (char) c;
return this.lastChar;
}
/**
* Consume the next character, and check that it matches a specified
* character.
*
* @param c The character to match.
*
* @return The character.
*
* @throws JSONException if the character does not match.
*/
public char next(char c) throws JSONException
{
char n = next();
if (n != c)
{
throw syntaxError("Expected '" + c + "' and instead saw '" +
n + "'");
}
return n;
}
/**
* Get the next n characters.
*
* @param n The number of characters to take.
*
* @return A string of n characters.
*
* @throws JSONException Substring bounds error if there are not
* n characters remaining in the source string.
*/
public String next(int n) throws JSONException
{
if (n == 0)
{
return "";
}
char[] buffer = new char[n];
int pos = 0;
if (this.useLastChar)
{
this.useLastChar = false;
buffer[0] = this.lastChar;
pos = 1;
}
try
{
int len;
while ((pos < n) && ((len = reader.read(buffer, pos, n - pos)) != -1))
{
pos += len;
}
}
catch (IOException exc)
{
throw new JSONException(exc);
}
this.index += pos;
if (pos < n)
{
throw syntaxError("Substring bounds error");
}
this.lastChar = buffer[n - 1];
return new String(buffer);
}
/**
* Get the next char in the string, skipping whitespace
* and comments (slashslash, slashstar, and hash).
*
* @return A character, or 0 if there are no more characters.
*
* @throws JSONException
*/
public char nextClean() throws JSONException
{
for (; ;)
{
char c = next();
if (c == '/')
{
switch (next())
{
case '/':
do
{
c = next();
}
while (c != '\n' && c != '\r' && c != 0);
break;
case '*':
for (; ;)
{
c = next();
if (c == 0)
{
throw syntaxError("Unclosed comment");
}
if (c == '*')
{
if (next() == '/')
{
break;
}
back();
}
}
break;
default:
back();
return '/';
}
}
else if (c == '#')
{
do
{
c = next();
}
while (c != '\n' && c != '\r' && c != 0);
}
else if (c == 0 || c > ' ')
{
return c;
}
}
}
/**
* Return the characters up to the next close quote character.
* Backslash processing is done. The formal JSON format does not
* allow strings in single quotes, but an implementation is allowed to
* accept them.
*
* @param quote The quoting character, either
* "
(double quote) or
* '
(single quote).
*
* @return A String.
*
* @throws JSONException Unterminated string.
*/
public String nextString(char quote) throws JSONException
{
char c;
StringBuffer sb = new StringBuffer();
for (; ;)
{
c = next();
switch (c)
{
case 0:
case '\n':
case '\r':
throw syntaxError("Unterminated string");
case '\\':
c = next();
switch (c)
{
case 'b':
sb.append('\b');
break;
case 't':
sb.append('\t');
break;
case 'n':
sb.append('\n');
break;
case 'f':
sb.append('\f');
break;
case 'r':
sb.append('\r');
break;
case 'u':
sb.append((char) Integer.parseInt(next(4), 16));
break;
case 'x':
sb.append((char) Integer.parseInt(next(2), 16));
break;
default:
sb.append(c);
}
break;
default:
if (c == quote)
{
return sb.toString();
}
sb.append(c);
}
}
}
/**
* Get the text up but not including the specified character or the
* end of line, whichever comes first.
*
* @param d A delimiter character.
*
* @return A string.
*/
public String nextTo(char d) throws JSONException
{
StringBuffer sb = new StringBuffer();
for (; ;)
{
char c = next();
if (c == d || c == 0 || c == '\n' || c == '\r')
{
if (c != 0)
{
back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the text up but not including one of the specified delimiter
* characters or the end of line, whichever comes first.
*
* @param delimiters A set of delimiter characters.
*
* @return A string, trimmed.
*/
public String nextTo(String delimiters) throws JSONException
{
char c;
StringBuffer sb = new StringBuffer();
for (; ;)
{
c = next();
if (delimiters.indexOf(c) >= 0 || c == 0 ||
c == '\n' || c == '\r')
{
if (c != 0)
{
back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the next value. The value can be a Boolean, Double, Integer,
* JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
*
* @return An object.
*
* @throws JSONException If syntax error.
*/
public Object nextValue() throws JSONException
{
char c = nextClean();
String s;
switch (c)
{
case '"':
case '\'':
return nextString(c);
case '{':
back();
return new JSONObject(this);
case '[':
case '(':
back();
return new JSONArray(this);
}
/*
* Handle unquoted text. This could be the values true, false, or
* null, or it can be a number. An implementation (such as this one)
* is allowed to also accept non-standard forms.
*
* Accumulate characters until we reach the end of the text or a
* formatting character.
*/
StringBuffer sb = new StringBuffer();
char b = c;
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0)
{
sb.append(c);
c = next();
}
back();
/*
* If it is true, false, or null, return the proper value.
*/
s = sb.toString().trim();
if (s.equals(""))
{
throw syntaxError("Missing value");
}
if (s.equalsIgnoreCase("true"))
{
return Boolean.TRUE;
}
if (s.equalsIgnoreCase("false"))
{
return Boolean.FALSE;
}
if (s.equalsIgnoreCase("null"))
{
return JSONObject.NULL;
}
/*
* If it might be a number, try converting it. We support the 0- and 0x-
* conventions. If a number cannot be produced, then the value will just
* be a string. Note that the 0-, 0x-, plus, and implied string
* conventions are non-standard. A JSON parser is free to accept
* non-JSON forms as long as it accepts all correct JSON forms.
*/
if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+')
{
if (b == '0')
{
if (s.length() > 2 &&
(s.charAt(1) == 'x' || s.charAt(1) == 'X'))
{
try
{
return Integer.parseInt(s.substring(2), 16);
}
catch (Exception e)
{
/* Ignore the error */
}
}
else
{
try
{
return Integer.parseInt(s, 8);
}
catch (Exception e)
{
/* Ignore the error */
}
}
}
try
{
return new Integer(s);
}
catch (Exception e)
{
try
{
return new Long(s);
}
catch (Exception f)
{
try
{
return new Double(s);
}
catch (Exception g)
{
return s;
}
}
}
}
return s;
}
/**
* Skip characters until the next character is the requested character.
* If the requested character is not found, no characters are skipped.
*
* @param to A character to skip to.
*
* @return The requested character, or zero if the requested character
* is not found.
*/
public char skipTo(char to) throws JSONException
{
char c;
try
{
int startIndex = this.index;
reader.mark(Integer.MAX_VALUE);
do
{
c = next();
if (c == 0)
{
reader.reset();
this.index = startIndex;
return c;
}
}
while (c != to);
}
catch (IOException exc)
{
throw new JSONException(exc);
}
back();
return c;
}
/**
* Make a JSONException to signal a syntax error.
*
* @param message The error message.
*
* @return A JSONException object, suitable for throwing
*/
public JSONException syntaxError(String message)
{
return new JSONException(message + toString());
}
/**
* Make a printable string of this JSONTokener.
*
* @return " at character [this.index]"
*/
public String toString()
{
return " at character " + index;
}
}