All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.chenillekit.google.utils.JSONTokener Maven / Gradle / Ivy

/*
 * 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy