001package org.json;
002
003import java.io.BufferedReader;
004import java.io.IOException;
005import java.io.Reader;
006import java.io.StringReader;
007
008/*
009Copyright (c) 2002 JSON.org
010
011Permission is hereby granted, free of charge, to any person obtaining a copy
012of this software and associated documentation files (the "Software"), to deal
013in the Software without restriction, including without limitation the rights
014to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
015copies of the Software, and to permit persons to whom the Software is
016furnished to do so, subject to the following conditions:
017
018The above copyright notice and this permission notice shall be included in all
019copies or substantial portions of the Software.
020
021The Software shall be used for Good, not Evil.
022
023THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
024IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
025FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
026AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
027LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
028OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
029SOFTWARE.
030 */
031
032/**
033 * A JSONTokener takes a source string and extracts characters and tokens from
034 * it. It is used by the JSONObject and JSONArray constructors to parse
035 * JSON source strings.
036 * @author JSON.org
037@version $Id$
038@since Ptolemy II 10.0
039 * @version 2010-02-02
040 */
041public class JSONTokener {
042
043    private int character;
044    private boolean eof;
045    private int index;
046    private int line;
047    private char previous;
048    private Reader reader;
049    private boolean usePrevious;
050
051    /**
052     * Construct a JSONTokener from a reader.
053     *
054     * @param reader     A reader.
055     */
056    public JSONTokener(Reader reader) {
057        this.reader = reader.markSupported() ? reader
058                : new BufferedReader(reader);
059        this.eof = false;
060        this.usePrevious = false;
061        this.previous = 0;
062        this.index = 0;
063        this.character = 1;
064        this.line = 1;
065    }
066
067    /**
068     * Construct a JSONTokener from a string.
069     *
070     * @param s     A source string.
071     */
072    public JSONTokener(String s) {
073        this(new StringReader(s));
074    }
075
076    /**
077     * Back up one character. This provides a sort of lookahead capability,
078     * so that you can test for a digit or letter before attempting to parse
079     * the next number or identifier.
080     */
081    public void back() throws JSONException {
082        if (usePrevious || index <= 0) {
083            throw new JSONException("Stepping back two steps is not supported");
084        }
085        this.index -= 1;
086        this.character -= 1;
087        this.usePrevious = true;
088        this.eof = false;
089    }
090
091    /**
092     * Get the hex value of a character (base16).
093     * @param c A character between '0' and '9' or between 'A' and 'F' or
094     * between 'a' and 'f'.
095     * @return  An int between 0 and 15, or -1 if c was not a hex digit.
096     */
097    public static int dehexchar(char c) {
098        if (c >= '0' && c <= '9') {
099            return c - '0';
100        }
101        if (c >= 'A' && c <= 'F') {
102            return c - ('A' - 10);
103        }
104        if (c >= 'a' && c <= 'f') {
105            return c - ('a' - 10);
106        }
107        return -1;
108    }
109
110    public boolean end() {
111        return eof && !usePrevious;
112    }
113
114    /**
115     * Determine if the source string still contains characters that next()
116     * can consume.
117     * @return true if not yet at the end of the source.
118     */
119    public boolean more() throws JSONException {
120        next();
121        if (end()) {
122            return false;
123        }
124        back();
125        return true;
126    }
127
128    /**
129     * Get the next character in the source string.
130     *
131     * @return The next character, or 0 if past the end of the source string.
132     */
133    public char next() throws JSONException {
134        int c;
135        if (this.usePrevious) {
136            this.usePrevious = false;
137            c = this.previous;
138        } else {
139            try {
140                c = this.reader.read();
141            } catch (IOException exception) {
142                throw new JSONException(exception);
143            }
144
145            if (c <= 0) { // End of stream
146                this.eof = true;
147                c = 0;
148            }
149        }
150        this.index += 1;
151        if (this.previous == '\r') {
152            this.line += 1;
153            this.character = c == '\n' ? 0 : 1;
154        } else if (c == '\n') {
155            this.line += 1;
156            this.character = 0;
157        } else {
158            this.character += 1;
159        }
160        this.previous = (char) c;
161        return this.previous;
162    }
163
164    /**
165     * Consume the next character, and check that it matches a specified
166     * character.
167     * @param c The character to match.
168     * @return The character.
169     * @exception JSONException if the character does not match.
170     */
171    public char next(char c) throws JSONException {
172        char n = next();
173        if (n != c) {
174            throw syntaxError(
175                    "Expected '" + c + "' and instead saw '" + n + "'");
176        }
177        return n;
178    }
179
180    /**
181     * Get the next n characters.
182     *
183     * @param n     The number of characters to take.
184     * @return      A string of n characters.
185     * @exception JSONException
186     *   Substring bounds error if there are not
187     *   n characters remaining in the source string.
188     */
189    public String next(int n) throws JSONException {
190        if (n == 0) {
191            return "";
192        }
193
194        char[] buffer = new char[n];
195        int pos = 0;
196
197        while (pos < n) {
198            buffer[pos] = next();
199            if (end()) {
200                throw syntaxError("Substring bounds error");
201            }
202            pos += 1;
203        }
204        return new String(buffer);
205    }
206
207    /**
208     * Get the next char in the string, skipping whitespace.
209     * @exception JSONException
210     * @return  A character, or 0 if there are no more characters.
211     */
212    public char nextClean() throws JSONException {
213        for (;;) {
214            char c = next();
215            if (c == 0 || c > ' ') {
216                return c;
217            }
218        }
219    }
220
221    /**
222     * Return the characters up to the next close quote character.
223     * Backslash processing is done. The formal JSON format does not
224     * allow strings in single quotes, but an implementation is allowed to
225     * accept them.
226     * @param quote The quoting character, either
227     *      <code>"</code>&nbsp;<small>(double quote)</small> or
228     *      <code>'</code>&nbsp;<small>(single quote)</small>.
229     * @return      A String.
230     * @exception JSONException Unterminated string.
231     */
232    public String nextString(char quote) throws JSONException {
233        char c;
234        StringBuffer sb = new StringBuffer();
235        for (;;) {
236            c = next();
237            switch (c) {
238            case 0:
239            case '\n':
240            case '\r':
241                throw syntaxError("Unterminated string");
242            case '\\':
243                c = next();
244                switch (c) {
245                case 'b':
246                    sb.append('\b');
247                    break;
248                case 't':
249                    sb.append('\t');
250                    break;
251                case 'n':
252                    sb.append('\n');
253                    break;
254                case 'f':
255                    sb.append('\f');
256                    break;
257                case 'r':
258                    sb.append('\r');
259                    break;
260                case 'u':
261                    sb.append((char) Integer.parseInt(next(4), 16));
262                    break;
263                case '"':
264                case '\'':
265                case '\\':
266                case '/':
267                    sb.append(c);
268                    break;
269                default:
270                    throw syntaxError("Illegal escape.");
271                }
272                break;
273            default:
274                if (c == quote) {
275                    return sb.toString();
276                }
277                sb.append(c);
278            }
279        }
280    }
281
282    /**
283     * Get the text up but not including the specified character or the
284     * end of line, whichever comes first.
285     * @param  d A delimiter character.
286     * @return   A string.
287     */
288    public String nextTo(char d) throws JSONException {
289        StringBuffer sb = new StringBuffer();
290        for (;;) {
291            char c = next();
292            if (c == d || c == 0 || c == '\n' || c == '\r') {
293                if (c != 0) {
294                    back();
295                }
296                return sb.toString().trim();
297            }
298            sb.append(c);
299        }
300    }
301
302    /**
303     * Get the text up but not including one of the specified delimiter
304     * characters or the end of line, whichever comes first.
305     * @param delimiters A set of delimiter characters.
306     * @return A string, trimmed.
307     */
308    public String nextTo(String delimiters) throws JSONException {
309        char c;
310        StringBuffer sb = new StringBuffer();
311        for (;;) {
312            c = next();
313            if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n'
314                    || c == '\r') {
315                if (c != 0) {
316                    back();
317                }
318                return sb.toString().trim();
319            }
320            sb.append(c);
321        }
322    }
323
324    /**
325     * Get the next value. The value can be a Boolean, Double, Integer,
326     * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
327     * @exception JSONException If syntax error.
328     *
329     * @return An object.
330     */
331    public Object nextValue() throws JSONException {
332        char c = nextClean();
333        String s;
334
335        switch (c) {
336        case '"':
337        case '\'':
338            return nextString(c);
339        case '{':
340            back();
341            return new JSONObject(this);
342        case '[':
343        case '(':
344            back();
345            return new JSONArray(this);
346        }
347
348        /*
349         * Handle unquoted text. This could be the values true, false, or
350         * null, or it can be a number. An implementation (such as this one)
351         * is allowed to also accept non-standard forms.
352         *
353         * Accumulate characters until we reach the end of the text or a
354         * formatting character.
355         */
356
357        StringBuffer sb = new StringBuffer();
358        while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
359            sb.append(c);
360            c = next();
361        }
362        back();
363
364        s = sb.toString().trim();
365        if (s.equals("")) {
366            throw syntaxError("Missing value");
367        }
368        return JSONObject.stringToValue(s);
369    }
370
371    /**
372     * Skip characters until the next character is the requested character.
373     * If the requested character is not found, no characters are skipped.
374     * @param to A character to skip to.
375     * @return The requested character, or zero if the requested character
376     * is not found.
377     */
378    public char skipTo(char to) throws JSONException {
379        char c;
380        try {
381            int startIndex = this.index;
382            int startCharacter = this.character;
383            int startLine = this.line;
384            reader.mark(Integer.MAX_VALUE);
385            do {
386                c = next();
387                if (c == 0) {
388                    reader.reset();
389                    this.index = startIndex;
390                    this.character = startCharacter;
391                    this.line = startLine;
392                    return c;
393                }
394            } while (c != to);
395        } catch (IOException exc) {
396            throw new JSONException(exc);
397        }
398
399        back();
400        return c;
401    }
402
403    /**
404     * Make a JSONException to signal a syntax error.
405     *
406     * @param message The error message.
407     * @return  A JSONException object, suitable for throwing
408     */
409    public JSONException syntaxError(String message) {
410        return new JSONException(message + toString());
411    }
412
413    /**
414     * Make a printable string of this JSONTokener.
415     *
416     * @return " at {index} [character {character} line {line}]"
417     */
418    @Override
419    public String toString() {
420        return " at " + index + " [character " + this.character + " line "
421                + this.line + "]";
422    }
423}