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