View Javadoc

1   /*
2    * Copyright 2002-2009 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.json.util;
17  
18  import net.sf.json.JSONArray;
19  import net.sf.json.JSONException;
20  import net.sf.json.JSONNull;
21  import net.sf.json.JSONObject;
22  import net.sf.json.JsonConfig;
23  import net.sf.json.regexp.RegexpUtils;
24  
25  
26  /**
27   * A JSONTokener takes a source string and extracts characters and tokens from
28   * it. It is used by the JSONObject and JSONArray constructors to parse JSON
29   * source strings.
30   *
31   * @author JSON.org
32   * @version 4
33   */
34  public class JSONTokener {
35     /**
36      * Get the hex value of a character (base16).
37      *
38      * @param c A character between '0' and '9' or between 'A' and 'F' or between
39      *        'a' and 'f'.
40      * @return An int between 0 and 15, or -1 if c was not a hex digit.
41      */
42     public static int dehexchar( char c ) {
43        if( c >= '0' && c <= '9' ){
44           return c - '0';
45        }
46        if( c >= 'A' && c <= 'F' ){
47           return c - ('A' - 10);
48        }
49        if( c >= 'a' && c <= 'f' ){
50           return c - ('a' - 10);
51        }
52        return -1;
53     }
54  
55     /**
56      * The index of the next character.
57      */
58     private int myIndex;
59  
60     /**
61      * The source string being tokenized.
62      */
63     private String mySource;
64  
65     /**
66      * Construct a JSONTokener from a string.
67      *
68      * @param s A source string.
69      */
70     public JSONTokener( String s ) {
71        this.myIndex = 0;
72        if( s!= null ) {
73           s = s.trim();
74        } else {
75           s = "";
76        }
77        if(  s.length() > 0 ){
78           char first = s.charAt( 0 );
79           char last = s.charAt( s.length() - 1 );
80           if( first == '[' && last != ']' ) {
81              throw syntaxError( "Found starting '[' but missing ']' at the end." );
82           }
83           if( first == '{' && last != '}' ) {
84              throw syntaxError( "Found starting '{' but missing '}' at the end." );
85           }
86        }
87        this.mySource = s;
88     }
89  
90     /**
91      * Back up one character. This provides a sort of lookahead capability, so
92      * that you can test for a digit or letter before attempting to parse the
93      * next number or identifier.
94      */
95     public void back() {
96        if( this.myIndex > 0 ){
97           this.myIndex -= 1;
98        }
99     }
100 
101    public int length() {
102       if( this.mySource == null ){
103          return 0;
104       }
105       return this.mySource.length();
106    }
107 
108    public boolean matches( String pattern ) {
109       String str = this.mySource.substring( this.myIndex );
110       return RegexpUtils.getMatcher( pattern )
111             .matches( str );
112    }
113 
114    /**
115     * Determine if the source string still contains characters that next() can
116     * consume.
117     *
118     * @return true if not yet at the end of the source.
119     */
120    public boolean more() {
121       return this.myIndex < this.mySource.length();
122    }
123 
124    /**
125     * Get the next character in the source string.
126     *
127     * @return The next character, or 0 if past the end of the source string.
128     */
129    public char next() {
130       if( more() ){
131          char c = this.mySource.charAt( this.myIndex );
132          this.myIndex += 1;
133          return c;
134       }
135       return 0;
136    }
137 
138    /**
139     * Consume the next character, and check that it matches a specified
140     * character.
141     *
142     * @param c The character to match.
143     * @return The character.
144     * @throws JSONException if the character does not match.
145     */
146    public char next( char c ) {
147       char n = next();
148       if( n != c ){
149          throw syntaxError( "Expected '" + c + "' and instead saw '" + n + "'." );
150       }
151       return n;
152    }
153 
154    /**
155     * Get the next n characters.
156     *
157     * @param n The number of characters to take.
158     * @return A string of n characters.
159     * @throws JSONException Substring bounds error if there are not n characters
160     *         remaining in the source string.
161     */
162    public String next( int n ) {
163       int i = this.myIndex;
164       int j = i + n;
165       if( j >= this.mySource.length() ){
166          throw syntaxError( "Substring bounds error" );
167       }
168       this.myIndex += n;
169       return this.mySource.substring( i, j );
170    }
171 
172    /**
173     * Get the next char in the string, skipping whitespace and comments
174     * (slashslash, slashstar, and hash).
175     *
176     * @throws JSONException
177     * @return A character, or 0 if there are no more characters.
178     */
179    public char nextClean() {
180       for( ;; ){
181          char c = next();
182          if( c == '/' ){
183             switch( next() ){
184                case '/':
185                   do{
186                      c = next();
187                   }while( c != '\n' && c != '\r' && c != 0 );
188                   break;
189                case '*':
190                   for( ;; ){
191                      c = next();
192                      if( c == 0 ){
193                         throw syntaxError( "Unclosed comment." );
194                      }
195                      if( c == '*' ){
196                         if( next() == '/' ){
197                            break;
198                         }
199                         back();
200                      }
201                   }
202                   break;
203                default:
204                   back();
205                   return '/';
206             }
207          }else if( c == '#' ){
208             do{
209                c = next();
210             }while( c != '\n' && c != '\r' && c != 0 );
211          }else if( c == 0 || c > ' ' ){
212             return c;
213          }
214       }
215    }
216 
217    /**
218     * Return the characters up to the next close quote character. Backslash
219     * processing is done. The formal JSON format does not allow strings in
220     * single quotes, but an implementation is allowed to accept them.
221     *
222     * @param quote The quoting character, either <code>"</code>&nbsp;<small>(double
223     *        quote)</small> or <code>'</code>&nbsp;<small>(single quote)</small>.
224     * @return A String.
225     * @throws JSONException Unterminated string.
226     */
227    public String nextString( char quote ) {
228       char c;
229       StringBuffer sb = new StringBuffer();
230       for( ;; ){
231          c = next();
232          switch( c ){
233             case 0:
234             case '\n':
235             case '\r':
236                throw syntaxError( "Unterminated string" );
237             case '\\':
238                c = next();
239                switch( c ){
240                   case 'b':
241                      sb.append( '\b' );
242                      break;
243                   case 't':
244                      sb.append( '\t' );
245                      break;
246                   case 'n':
247                      sb.append( '\n' );
248                      break;
249                   case 'f':
250                      sb.append( '\f' );
251                      break;
252                   case 'r':
253                      sb.append( '\r' );
254                      break;
255                   case 'u':
256                      sb.append( (char) Integer.parseInt( next( 4 ), 16 ) );
257                      break;
258                   case 'x':
259                      sb.append( (char) Integer.parseInt( next( 2 ), 16 ) );
260                      break;
261                   default:
262                      sb.append( c );
263                }
264                break;
265             default:
266                if( c == quote ){
267                   return sb.toString();
268                }
269                sb.append( c );
270          }
271       }
272    }
273 
274    /**
275     * Get the text up but not including the specified character or the end of
276     * line, whichever comes first.
277     *
278     * @param d A delimiter character.
279     * @return A string.
280     */
281    public String nextTo( char d ) {
282       StringBuffer sb = new StringBuffer();
283       for( ;; ){
284          char c = next();
285          if( c == d || c == 0 || c == '\n' || c == '\r' ){
286             if( c != 0 ){
287                back();
288             }
289             return sb.toString()
290                   .trim();
291          }
292          sb.append( c );
293       }
294    }
295 
296    /**
297     * Get the text up but not including one of the specified delimeter
298     * characters or the end of line, whichever comes first.
299     *
300     * @param delimiters A set of delimiter characters.
301     * @return A string, trimmed.
302     */
303    public String nextTo( String delimiters ) {
304       char c;
305       StringBuffer sb = new StringBuffer();
306       for( ;; ){
307          c = next();
308          if( delimiters.indexOf( c ) >= 0 || c == 0 || c == '\n' || c == '\r' ){
309             if( c != 0 ){
310                back();
311             }
312             return sb.toString()
313                   .trim();
314          }
315          sb.append( c );
316       }
317    }
318 
319    /**
320     * Get the next value. The value can be a Boolean, Double, Integer,
321     * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
322     *
323     * @throws JSONException If syntax error.
324     * @return An object.
325     */
326    public Object nextValue() {
327       return nextValue( new JsonConfig() );
328    }
329 
330    /**
331     * Get the next value. The value can be a Boolean, Double, Integer,
332     * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
333     *
334     * @throws JSONException If syntax error.
335     * @return An object.
336     */
337    public Object nextValue( JsonConfig jsonConfig ) {
338       char c = nextClean();
339       String s;
340 
341       switch( c ){
342          case '"':
343          case '\'':
344             return nextString( c );
345          case '{':
346             back();
347             return JSONObject.fromObject( this, jsonConfig );
348          case '[':
349             back();
350             return JSONArray.fromObject( this, jsonConfig );
351          default:
352             // empty
353       }
354 
355       /*
356        * Handle unquoted text. This could be the values true, false, or null, or
357        * it can be a number. An implementation (such as this one) is allowed to
358        * also accept non-standard forms. Accumulate characters until we reach
359        * the end of the text or a formatting character.
360        */
361 
362       StringBuffer sb = new StringBuffer();
363       char b = c;
364       while( c >= ' ' && ",:]}/\\\"[{;=#".indexOf( c ) < 0 ){
365          sb.append( c );
366          c = next();
367       }
368       back();
369 
370       /*
371        * If it is true, false, or null, return the proper value.
372        */
373 
374       s = sb.toString()
375             .trim();
376       if( s.equals( "" ) ){
377          throw syntaxError( "Missing value." );
378       }
379       if( s.equalsIgnoreCase( "true" ) ){
380          return Boolean.TRUE;
381       }
382       if( s.equalsIgnoreCase( "false" ) ){
383          return Boolean.FALSE;
384       }
385       if( s.equals( "null" ) || 
386           (jsonConfig.isJavascriptCompliant() && s.equals("undefined"))){
387          return JSONNull.getInstance();
388       }
389 
390       /*
391        * If it might be a number, try converting it. We support the 0- and 0x-
392        * conventions. If a number cannot be produced, then the value will just
393        * be a string. Note that the 0-, 0x-, plus, and implied string
394        * conventions are non-standard. A JSON parser is free to accept non-JSON
395        * forms as long as it accepts all correct JSON forms.
396        */
397 
398       if( (b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+' ){
399          if( b == '0' ){
400             if( s.length() > 2 && (s.charAt( 1 ) == 'x' || s.charAt( 1 ) == 'X') ){
401                try{
402                   return new Integer( Integer.parseInt( s.substring( 2 ), 16 ) );
403                }catch( Exception e ){
404                   /* Ignore the error */
405                }
406             }else{
407                try{
408                   return new Integer( Integer.parseInt( s, 8 ) );
409                }catch( Exception e ){
410                   /* Ignore the error */
411                }
412             }
413          }
414          try{
415             return new Integer( s );
416          }catch( Exception e ){
417             try{
418                return new Long( s );
419             }catch( Exception f ){
420                try{
421                   return new Double( s );
422                }catch( Exception g ){
423                   return s;
424                }
425             }
426          }
427       }
428 
429       if( JSONUtils.isFunctionHeader( s ) || JSONUtils.isFunction( s ) ){
430          return s;
431       }
432       switch( peek() ){
433          case ',':
434          case '}':
435          case '{':
436          case '[':
437          case ']':
438             throw new JSONException( "Unquotted string '" + s + "'" );
439       }
440 
441       return s;
442    }
443 
444    /**
445     * Look at the next character in the source string.
446     *
447     * @return The next character, or 0 if past the end of the source string.
448     */
449    public char peek() {
450       if( more() ){
451          char c = this.mySource.charAt( this.myIndex );
452          return c;
453       }
454       return 0;
455    }
456 
457    public void reset() {
458       this.myIndex = 0;
459    }
460 
461    /**
462     * Skip characters until past the requested string. If it is not found, we
463     * are left at the end of the source.
464     *
465     * @param to A string to skip past.
466     */
467    public void skipPast( String to ) {
468       this.myIndex = this.mySource.indexOf( to, this.myIndex );
469       if( this.myIndex < 0 ){
470          this.myIndex = this.mySource.length();
471       }else{
472          this.myIndex += to.length();
473       }
474    }
475 
476    /**
477     * Skip characters until the next character is the requested character. If
478     * the requested character is not found, no characters are skipped.
479     *
480     * @param to A character to skip to.
481     * @return The requested character, or zero if the requested character is not
482     *         found.
483     */
484    public char skipTo( char to ) {
485       char c;
486       int index = this.myIndex;
487       do{
488          c = next();
489          if( c == 0 ){
490             this.myIndex = index;
491             return c;
492          }
493       }while( c != to );
494       back();
495       return c;
496    }
497 
498    /**
499     * Make a JSONException to signal a syntax error.
500     *
501     * @param message The error message.
502     * @return A JSONException object, suitable for throwing
503     */
504    public JSONException syntaxError( String message ) {
505       return new JSONException( message + toString() );
506    }
507 
508    /**
509     * Make a printable string of this JSONTokener.
510     *
511     * @return " at character [this.myIndex] of [this.mySource]"
512     */
513    public String toString() {
514       return " at character " + this.myIndex + " of " + this.mySource;
515    }
516 }