001    /*
002     * Apache License
003     * Version 2.0, January 2004
004     * http://www.apache.org/licenses/
005     *
006     * Copyright 2008 by chenillekit.org
007     *
008     * Licensed under the Apache License, Version 2.0 (the "License");
009     * you may not use this file except in compliance with the License.
010     * You may obtain a copy of the License at
011     *
012     * http://www.apache.org/licenses/LICENSE-2.0
013     *
014     */
015    
016    package org.chenillekit.google.utils;
017    
018    import java.io.BufferedReader;
019    import java.io.IOException;
020    import java.io.Reader;
021    import java.io.StringReader;
022    
023    /**
024     * A JSONTokener takes a source string and extracts characters and tokens from
025     * it. It is used by the JSONObject and JSONArray constructors to parse
026     * JSON source strings.
027     *
028     * @author JSON.org
029     * @version 3
030     */
031    public class JSONTokener
032    {
033    
034        private int index;
035        private Reader reader;
036        private char lastChar;
037        private boolean useLastChar;
038    
039    
040        /**
041         * Construct a JSONTokener from a string.
042         *
043         * @param reader A reader.
044         */
045        public JSONTokener(Reader reader)
046        {
047            this.reader = reader.markSupported() ?
048                    reader : new BufferedReader(reader);
049            this.useLastChar = false;
050            this.index = 0;
051        }
052    
053    
054        /**
055         * Construct a JSONTokener from a string.
056         *
057         * @param s A source string.
058         */
059        public JSONTokener(String s)
060        {
061            this(new StringReader(s));
062        }
063    
064    
065        /**
066         * Back up one character. This provides a sort of lookahead capability,
067         * so that you can test for a digit or letter before attempting to parse
068         * the next number or identifier.
069         */
070        public void back() throws JSONException
071        {
072            if (useLastChar || index <= 0)
073            {
074                throw new JSONException("Stepping back two steps is not supported");
075            }
076            index -= 1;
077            useLastChar = true;
078        }
079    
080    
081        /**
082         * Get the hex value of a character (base16).
083         *
084         * @param c A character between '0' and '9' or between 'A' and 'F' or
085         *          between 'a' and 'f'.
086         *
087         * @return An int between 0 and 15, or -1 if c was not a hex digit.
088         */
089        public static int dehexchar(char c)
090        {
091            if (c >= '0' && c <= '9')
092            {
093                return c - '0';
094            }
095            if (c >= 'A' && c <= 'F')
096            {
097                return c - ('A' - 10);
098            }
099            if (c >= 'a' && c <= 'f')
100            {
101                return c - ('a' - 10);
102            }
103            return -1;
104        }
105    
106    
107        /**
108         * Determine if the source string still contains characters that next()
109         * can consume.
110         *
111         * @return true if not yet at the end of the source.
112         */
113        public boolean more() throws JSONException
114        {
115            char nextChar = next();
116            if (nextChar == 0)
117            {
118                return false;
119            }
120            back();
121            return true;
122        }
123    
124    
125        /**
126         * Get the next character in the source string.
127         *
128         * @return The next character, or 0 if past the end of the source string.
129         */
130        public char next() throws JSONException
131        {
132            if (this.useLastChar)
133            {
134                this.useLastChar = false;
135                if (this.lastChar != 0)
136                {
137                    this.index += 1;
138                }
139                return this.lastChar;
140            }
141            int c;
142            try
143            {
144                c = this.reader.read();
145            }
146            catch (IOException exc)
147            {
148                throw new JSONException(exc);
149            }
150    
151            if (c <= 0)
152            { // End of stream
153                this.lastChar = 0;
154                return 0;
155            }
156            this.index += 1;
157            this.lastChar = (char) c;
158            return this.lastChar;
159        }
160    
161    
162        /**
163         * Consume the next character, and check that it matches a specified
164         * character.
165         *
166         * @param c The character to match.
167         *
168         * @return The character.
169         *
170         * @throws JSONException if the character does not match.
171         */
172        public char next(char c) throws JSONException
173        {
174            char n = next();
175            if (n != c)
176            {
177                throw syntaxError("Expected '" + c + "' and instead saw '" +
178                        n + "'");
179            }
180            return n;
181        }
182    
183    
184        /**
185         * Get the next n characters.
186         *
187         * @param n The number of characters to take.
188         *
189         * @return A string of n characters.
190         *
191         * @throws JSONException Substring bounds error if there are not
192         *                       n characters remaining in the source string.
193         */
194        public String next(int n) throws JSONException
195        {
196            if (n == 0)
197            {
198                return "";
199            }
200    
201            char[] buffer = new char[n];
202            int pos = 0;
203    
204            if (this.useLastChar)
205            {
206                this.useLastChar = false;
207                buffer[0] = this.lastChar;
208                pos = 1;
209            }
210    
211            try
212            {
213                int len;
214                while ((pos < n) && ((len = reader.read(buffer, pos, n - pos)) != -1))
215                {
216                    pos += len;
217                }
218            }
219            catch (IOException exc)
220            {
221                throw new JSONException(exc);
222            }
223            this.index += pos;
224    
225            if (pos < n)
226            {
227                throw syntaxError("Substring bounds error");
228            }
229    
230            this.lastChar = buffer[n - 1];
231            return new String(buffer);
232        }
233    
234    
235        /**
236         * Get the next char in the string, skipping whitespace
237         * and comments (slashslash, slashstar, and hash).
238         *
239         * @return A character, or 0 if there are no more characters.
240         *
241         * @throws JSONException
242         */
243        public char nextClean() throws JSONException
244        {
245            for (; ;)
246            {
247                char c = next();
248                if (c == '/')
249                {
250                    switch (next())
251                    {
252                        case '/':
253                            do
254                            {
255                                c = next();
256                            }
257                            while (c != '\n' && c != '\r' && c != 0);
258                            break;
259                        case '*':
260                            for (; ;)
261                            {
262                                c = next();
263                                if (c == 0)
264                                {
265                                    throw syntaxError("Unclosed comment");
266                                }
267                                if (c == '*')
268                                {
269                                    if (next() == '/')
270                                    {
271                                        break;
272                                    }
273                                    back();
274                                }
275                            }
276                            break;
277                        default:
278                            back();
279                            return '/';
280                    }
281                }
282                else if (c == '#')
283                {
284                    do
285                    {
286                        c = next();
287                    }
288                    while (c != '\n' && c != '\r' && c != 0);
289                }
290                else if (c == 0 || c > ' ')
291                {
292                    return c;
293                }
294            }
295        }
296    
297    
298        /**
299         * Return the characters up to the next close quote character.
300         * Backslash processing is done. The formal JSON format does not
301         * allow strings in single quotes, but an implementation is allowed to
302         * accept them.
303         *
304         * @param quote The quoting character, either
305         *              <code>"</code>&nbsp;<small>(double quote)</small> or
306         *              <code>'</code>&nbsp;<small>(single quote)</small>.
307         *
308         * @return A String.
309         *
310         * @throws JSONException Unterminated string.
311         */
312        public String nextString(char quote) throws JSONException
313        {
314            char c;
315            StringBuffer sb = new StringBuffer();
316            for (; ;)
317            {
318                c = next();
319                switch (c)
320                {
321                    case 0:
322                    case '\n':
323                    case '\r':
324                        throw syntaxError("Unterminated string");
325                    case '\\':
326                        c = next();
327                        switch (c)
328                        {
329                            case 'b':
330                                sb.append('\b');
331                                break;
332                            case 't':
333                                sb.append('\t');
334                                break;
335                            case 'n':
336                                sb.append('\n');
337                                break;
338                            case 'f':
339                                sb.append('\f');
340                                break;
341                            case 'r':
342                                sb.append('\r');
343                                break;
344                            case 'u':
345                                sb.append((char) Integer.parseInt(next(4), 16));
346                                break;
347                            case 'x':
348                                sb.append((char) Integer.parseInt(next(2), 16));
349                                break;
350                            default:
351                                sb.append(c);
352                        }
353                        break;
354                    default:
355                        if (c == quote)
356                        {
357                            return sb.toString();
358                        }
359                        sb.append(c);
360                }
361            }
362        }
363    
364    
365        /**
366         * Get the text up but not including the specified character or the
367         * end of line, whichever comes first.
368         *
369         * @param d A delimiter character.
370         *
371         * @return A string.
372         */
373        public String nextTo(char d) throws JSONException
374        {
375            StringBuffer sb = new StringBuffer();
376            for (; ;)
377            {
378                char c = next();
379                if (c == d || c == 0 || c == '\n' || c == '\r')
380                {
381                    if (c != 0)
382                    {
383                        back();
384                    }
385                    return sb.toString().trim();
386                }
387                sb.append(c);
388            }
389        }
390    
391    
392        /**
393         * Get the text up but not including one of the specified delimiter
394         * characters or the end of line, whichever comes first.
395         *
396         * @param delimiters A set of delimiter characters.
397         *
398         * @return A string, trimmed.
399         */
400        public String nextTo(String delimiters) throws JSONException
401        {
402            char c;
403            StringBuffer sb = new StringBuffer();
404            for (; ;)
405            {
406                c = next();
407                if (delimiters.indexOf(c) >= 0 || c == 0 ||
408                        c == '\n' || c == '\r')
409                {
410                    if (c != 0)
411                    {
412                        back();
413                    }
414                    return sb.toString().trim();
415                }
416                sb.append(c);
417            }
418        }
419    
420    
421        /**
422         * Get the next value. The value can be a Boolean, Double, Integer,
423         * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
424         *
425         * @return An object.
426         *
427         * @throws JSONException If syntax error.
428         */
429        public Object nextValue() throws JSONException
430        {
431            char c = nextClean();
432            String s;
433    
434            switch (c)
435            {
436                case '"':
437                case '\'':
438                    return nextString(c);
439                case '{':
440                    back();
441                    return new JSONObject(this);
442                case '[':
443                case '(':
444                    back();
445                    return new JSONArray(this);
446            }
447    
448            /*
449             * Handle unquoted text. This could be the values true, false, or
450             * null, or it can be a number. An implementation (such as this one)
451             * is allowed to also accept non-standard forms.
452             *
453             * Accumulate characters until we reach the end of the text or a
454             * formatting character.
455             */
456    
457            StringBuffer sb = new StringBuffer();
458            char b = c;
459            while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0)
460            {
461                sb.append(c);
462                c = next();
463            }
464            back();
465    
466            /*
467             * If it is true, false, or null, return the proper value.
468             */
469    
470            s = sb.toString().trim();
471            if (s.equals(""))
472            {
473                throw syntaxError("Missing value");
474            }
475            if (s.equalsIgnoreCase("true"))
476            {
477                return Boolean.TRUE;
478            }
479            if (s.equalsIgnoreCase("false"))
480            {
481                return Boolean.FALSE;
482            }
483            if (s.equalsIgnoreCase("null"))
484            {
485                return JSONObject.NULL;
486            }
487    
488            /*
489             * If it might be a number, try converting it. We support the 0- and 0x-
490             * conventions. If a number cannot be produced, then the value will just
491             * be a string. Note that the 0-, 0x-, plus, and implied string
492             * conventions are non-standard. A JSON parser is free to accept
493             * non-JSON forms as long as it accepts all correct JSON forms.
494             */
495    
496            if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+')
497            {
498                if (b == '0')
499                {
500                    if (s.length() > 2 &&
501                            (s.charAt(1) == 'x' || s.charAt(1) == 'X'))
502                    {
503                        try
504                        {
505                            return Integer.parseInt(s.substring(2), 16);
506                        }
507                        catch (Exception e)
508                        {
509                            /* Ignore the error */
510                        }
511                    }
512                    else
513                    {
514                        try
515                        {
516                            return Integer.parseInt(s, 8);
517                        }
518                        catch (Exception e)
519                        {
520                            /* Ignore the error */
521                        }
522                    }
523                }
524                try
525                {
526                    return new Integer(s);
527                }
528                catch (Exception e)
529                {
530                    try
531                    {
532                        return new Long(s);
533                    }
534                    catch (Exception f)
535                    {
536                        try
537                        {
538                            return new Double(s);
539                        }
540                        catch (Exception g)
541                        {
542                            return s;
543                        }
544                    }
545                }
546            }
547            return s;
548        }
549    
550    
551        /**
552         * Skip characters until the next character is the requested character.
553         * If the requested character is not found, no characters are skipped.
554         *
555         * @param to A character to skip to.
556         *
557         * @return The requested character, or zero if the requested character
558         *         is not found.
559         */
560        public char skipTo(char to) throws JSONException
561        {
562            char c;
563            try
564            {
565                int startIndex = this.index;
566                reader.mark(Integer.MAX_VALUE);
567                do
568                {
569                    c = next();
570                    if (c == 0)
571                    {
572                        reader.reset();
573                        this.index = startIndex;
574                        return c;
575                    }
576                }
577                while (c != to);
578            }
579            catch (IOException exc)
580            {
581                throw new JSONException(exc);
582            }
583    
584            back();
585            return c;
586        }
587    
588    
589        /**
590         * Make a JSONException to signal a syntax error.
591         *
592         * @param message The error message.
593         *
594         * @return A JSONException object, suitable for throwing
595         */
596        public JSONException syntaxError(String message)
597        {
598            return new JSONException(message + toString());
599        }
600    
601    
602        /**
603         * Make a printable string of this JSONTokener.
604         *
605         * @return " at character [this.index]"
606         */
607        public String toString()
608        {
609            return " at character " + index;
610        }
611    }