001    package org.apache.tapestry.json;
002    
003    /*
004     Copyright (c) 2002 JSON.org
005    
006     Permission is hereby granted, free of charge, to any person obtaining a copy
007     of this software and associated documentation files (the "Software"), to deal
008     in the Software without restriction, including without limitation the rights
009     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010     copies of the Software, and to permit persons to whom the Software is
011     furnished to do so, subject to the following conditions:
012    
013     The above copyright notice and this permission notice shall be included in all
014     copies or substantial portions of the Software.
015    
016     The Software shall be used for Good, not Evil.
017    
018     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
021     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
023     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
024     SOFTWARE.
025     */
026    
027    import java.text.ParseException;
028    
029    /**
030     * A JSONTokener takes a source string and extracts characters and tokens from
031     * it. It is used by the JSONObject and JSONArray constructors to parse JSON
032     * source strings.
033     * 
034     * <p/>The toString() method has been modified from its original form to provide
035     * easier to understand exception reporting.
036     * 
037     * @author JSON.org, jkuhnert 
038     * @version 1
039     */
040    public class JSONTokener
041    {
042    
043        /**
044         * The index of the next character.
045         */
046        private int myIndex;
047    
048        /**
049         * The source string being tokenized.
050         */
051        private String mySource;
052    
053        /**
054         * Construct a JSONTokener from a string.
055         * 
056         * @param s
057         *            A source string.
058         */
059        public JSONTokener(String s)
060        {
061            this.myIndex = 0;
062            this.mySource = s;
063        }
064    
065        /**
066         * Back up one character. This provides a sort of lookahead capability, so
067         * that you can test for a digit or letter before attempting to parse the
068         * next number or identifier.
069         */
070        public void back()
071        {
072            if (this.myIndex > 0)
073            {
074                this.myIndex -= 1;
075            }
076        }
077    
078        /**
079         * Get the hex value of a character (base16).
080         * 
081         * @param c
082         *            A character between '0' and '9' or between 'A' and 'F' or
083         *            between 'a' and 'f'.
084         * @return An int between 0 and 15, or -1 if c was not a hex digit.
085         */
086        public static int dehexchar(char c)
087        {
088            if (c >= '0' && c <= '9') { return c - '0'; }
089            if (c >= 'A' && c <= 'F') { return c + 10 - 'A'; }
090            if (c >= 'a' && c <= 'f') { return c + 10 - 'a'; }
091            return -1;
092        }
093    
094        /**
095         * Determine if the source string still contains characters that next() can
096         * consume.
097         * 
098         * @return true if not yet at the end of the source.
099         */
100        public boolean more()
101        {
102            return this.myIndex < this.mySource.length();
103        }
104    
105        /**
106         * Get the next character in the source string.
107         * 
108         * @return The next character, or 0 if past the end of the source string.
109         */
110        public char next()
111        {
112            if (more())
113            {
114                char c = this.mySource.charAt(this.myIndex);
115                this.myIndex += 1;
116                return c;
117            }
118            return 0;
119        }
120    
121        /**
122         * Consume the next character, and check that it matches a specified
123         * character.
124         * 
125         * @param c
126         *            The character to match.
127         * @return The character.
128         * @throws ParseException
129         *             if the character does not match.
130         */
131        public char next(char c)
132            throws ParseException
133        {
134            char n = next();
135            if (n != c) { throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'."); }
136            return n;
137        }
138    
139        /**
140         * Get the next n characters.
141         * 
142         * @param n
143         *            The number of characters to take.
144         * @return A string of n characters.
145         * @exception ParseException
146         *                Substring bounds error if there are not n characters
147         *                remaining in the source string.
148         */
149        public String next(int n)
150            throws ParseException
151        {
152            int i = this.myIndex;
153            int j = i + n;
154            if (j >= this.mySource.length()) { throw syntaxError("Substring bounds error"); }
155            this.myIndex += n;
156            return this.mySource.substring(i, j);
157        }
158    
159        /**
160         * Get the next char in the string, skipping whitespace and comments
161         * (slashslash, slashstar, and hash).
162         * 
163         * @throws ParseException
164         * @return A character, or 0 if there are no more characters.
165         */
166        public char nextClean()
167            throws java.text.ParseException
168        {
169            while(true)
170            {
171                char c = next();
172                if (c == '/')
173                {
174                    switch(next())
175                    {
176                    case '/':
177                        do
178                        {
179                            c = next();
180                        } while(c != '\n' && c != '\r' && c != 0);
181                        break;
182                    case '*':
183                        while(true)
184                        {
185                            c = next();
186                            if (c == 0) { throw syntaxError("Unclosed comment."); }
187                            if (c == '*')
188                            {
189                                if (next() == '/')
190                                {
191                                    break;
192                                }
193                                back();
194                            }
195                        }
196                        break;
197                    default:
198                        back();
199                        return '/';
200                    }
201                }
202                else if (c == '#')
203                {
204                    do
205                    {
206                        c = next();
207                    } while(c != '\n' && c != '\r' && c != 0);
208                }
209                else if (c == 0 || c > ' ') { return c; }
210            }
211        }
212    
213        /**
214         * Return the characters up to the next close quote character. Backslash
215         * processing is done. The formal JSON format does not allow strings in
216         * single quotes, but an implementation is allowed to accept them.
217         * 
218         * @param quote
219         *            The quoting character, either <code>"</code>&nbsp;<small>(double
220         *            quote)</small> or <code>'</code>&nbsp;<small>(single
221         *            quote)</small>.
222         * @return A String.
223         * @exception ParseException
224         *                Unterminated string.
225         */
226        public String nextString(char quote)
227            throws ParseException
228        {
229            char c;
230            StringBuffer sb = new StringBuffer();
231            while(true)
232            {
233                c = next();
234                switch(c)
235                {
236                case 0:
237                case '\n':
238                case '\r':
239                    throw syntaxError("Unterminated string");
240                case '\\':
241                    c = next();
242                    switch(c)
243                    {
244                    case 'b':
245                        sb.append('\b');
246                        break;
247                    case 't':
248                        sb.append('\t');
249                        break;
250                    case 'n':
251                        sb.append('\n');
252                        break;
253                    case 'f':
254                        sb.append('\f');
255                        break;
256                    case 'r':
257                        sb.append('\r');
258                        break;
259                    case 'u':
260                        sb.append((char) Integer.parseInt(next(4), 16));
261                        break;
262                    case 'x':
263                        sb.append((char) Integer.parseInt(next(2), 16));
264                        break;
265                    default:
266                        sb.append(c);
267                    }
268                    break;
269                default:
270                    if (c == quote) { return sb.toString(); }
271                    sb.append(c);
272                }
273            }
274        }
275    
276        /**
277         * Get the text up but not including the specified character or the end of
278         * line, whichever comes first.
279         * 
280         * @param d
281         *            A delimiter character.
282         * @return A string.
283         */
284        public String nextTo(char d)
285        {
286            StringBuffer sb = new StringBuffer();
287            while(true)
288            {
289                char c = next();
290                if (c == d || c == 0 || c == '\n' || c == '\r')
291                {
292                    if (c != 0)
293                    {
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 delimeter
304         * characters or the end of line, whichever comes first.
305         * 
306         * @param delimiters
307         *            A set of delimiter characters.
308         * @return A string, trimmed.
309         */
310        public String nextTo(String delimiters)
311        {
312            char c;
313            StringBuffer sb = new StringBuffer();
314            while(true)
315            {
316                c = next();
317                if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r')
318                {
319                    if (c != 0)
320                    {
321                        back();
322                    }
323                    return sb.toString().trim();
324                }
325                sb.append(c);
326            }
327        }
328    
329        /**
330         * Get the next value. The value can be a Boolean, Double, Integer,
331         * JSONArray, JSONObject, or String, or the JSONObject.NULL object.
332         * 
333         * @exception ParseException
334         *                The source does not conform to JSON syntax.
335         * @return An object.
336         */
337        public Object nextValue()
338            throws ParseException
339        {
340            char c = nextClean();
341            String s;
342    
343            switch(c)
344            {
345            case '"':
346            case '\'':
347                return nextString(c);
348            case '{':
349                back();
350                return new JSONObject(this);
351            case '[':
352                back();
353                return new JSONArray(this);
354            }
355    
356            /*
357             * Handle unquoted text. This could be the values true, false, or null,
358             * or it can be a number. An implementation (such as this one) is
359             * allowed to also accept non-standard forms. Accumulate characters
360             * until we reach the end of the text or a formatting character.
361             */
362    
363            StringBuffer sb = new StringBuffer();
364            char b = c;
365            while(c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0)
366            {
367                sb.append(c);
368                c = next();
369            }
370            back();
371    
372            /*
373             * If it is true, false, or null, return the proper value.
374             */
375    
376            s = sb.toString().trim();
377            if (s.equals("")) { throw syntaxError("Missing value."); }
378            if (s.equalsIgnoreCase("true")) { return Boolean.TRUE; }
379            if (s.equalsIgnoreCase("false")) { return Boolean.FALSE; }
380            if (s.equalsIgnoreCase("null")) { return JSONObject.NULL; }
381    
382            /*
383             * If it might be a number, try converting it. We support the 0- and 0x-
384             * conventions. If a number cannot be produced, then the value will just
385             * be a string. Note that the 0-, 0x-, plus, and implied string
386             * conventions are non-standard. A JSON parser is free to accept
387             * non-JSON forms as long as it accepts all correct JSON forms.
388             */
389    
390            if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+')
391            {
392                if (b == '0')
393                {
394                    if (s.length() > 2 && (s.charAt(1) == 'x' || s.charAt(1) == 'X'))
395                    {
396                        try
397                        {
398                            return new Integer(Integer.parseInt(s.substring(2), 16));
399                        }
400                        catch (Exception e)
401                        {
402                            /* Ignore the error */
403                        }
404                    }
405                    else
406                    {
407                        try
408                        {
409                            return new Integer(Integer.parseInt(s, 8));
410                        }
411                        catch (Exception e)
412                        {
413                            /* Ignore the error */
414                        }
415                    }
416                }
417                try
418                {
419                    return new Integer(s);
420                }
421                catch (Exception e)
422                {
423                    /* Ignore the error */
424                }
425                try
426                {
427                    return new Double(s);
428                }
429                catch (Exception e)
430                {
431                    /* Ignore the error */
432                }
433            }
434            return s;
435        }
436    
437        /**
438         * Skip characters until the next character is the requested character. If
439         * the requested character is not found, no characters are skipped.
440         * 
441         * @param to
442         *            A character to skip to.
443         * @return The requested character, or zero if the requested character is
444         *         not found.
445         */
446        public char skipTo(char to)
447        {
448            char c;
449            int index = this.myIndex;
450            do
451            {
452                c = next();
453                if (c == 0)
454                {
455                    this.myIndex = index;
456                    return c;
457                }
458            } while(c != to);
459            back();
460            return c;
461        }
462    
463        /**
464         * Skip characters until past the requested string. If it is not found, we
465         * are left at the end of the source.
466         * 
467         * @param to
468         *            A string to skip past.
469         */
470        public void skipPast(String to)
471        {
472            this.myIndex = this.mySource.indexOf(to, this.myIndex);
473            if (this.myIndex < 0)
474            {
475                this.myIndex = this.mySource.length();
476            }
477            else
478            {
479                this.myIndex += to.length();
480            }
481        }
482    
483        /**
484         * Make a ParseException to signal a syntax error.
485         * 
486         * @param message
487         *            The error message.
488         * @return A ParseException object, suitable for throwing
489         */
490        public ParseException syntaxError(String message)
491        {
492            return new ParseException(message + toString(), this.myIndex);
493        }
494    
495        /**
496         * Make a printable string of this JSONTokener.
497         * 
498         * @return " at character [this.myIndex] of [this.mySource]"
499         */
500        public String toString()
501        {
502            String before = this.mySource.substring(0, this.myIndex);
503            String after = this.mySource.substring(this.myIndex);
504            
505            return " at character " + this.myIndex + " of " + before + ">>missing value<<" + after;
506        }
507    
508        /**
509         * Convert <code>%</code><i>hh</i> sequences to single characters, and
510         * convert plus to space.
511         * 
512         * @param s
513         *            A string that may contain <code>+</code>&nbsp;<small>(plus)</small>
514         *            and <code>%</code><i>hh</i> sequences.
515         * @return The unescaped string.
516         */
517        public static String unescape(String s)
518        {
519            int len = s.length();
520            StringBuffer b = new StringBuffer();
521            for(int i = 0; i < len; ++i)
522            {
523                char c = s.charAt(i);
524                if (c == '+')
525                {
526                    c = ' ';
527                }
528                else if (c == '%' && i + 2 < len)
529                {
530                    int d = dehexchar(s.charAt(i + 1));
531                    int e = dehexchar(s.charAt(i + 2));
532                    if (d >= 0 && e >= 0)
533                    {
534                        c = (char) (d * 16 + e);
535                        i += 2;
536                    }
537                }
538                b.append(c);
539            }
540            return b.toString();
541        }
542    }