Coverage Report - net.sf.json.util.JSONTokener
 
Classes in this File Line Coverage Branch Coverage Complexity
JSONTokener
0%
0/170
0%
0/173
5.857
 
 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  0
       if( c >= '0' && c <= '9' ){
 44  0
          return c - '0';
 45  
       }
 46  0
       if( c >= 'A' && c <= 'F' ){
 47  0
          return c - ('A' - 10);
 48  
       }
 49  0
       if( c >= 'a' && c <= 'f' ){
 50  0
          return c - ('a' - 10);
 51  
       }
 52  0
       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  0
    public JSONTokener( String s ) {
 71  0
       this.myIndex = 0;
 72  0
       if( s!= null ) {
 73  0
          s = s.trim();
 74  
       } else {
 75  0
          s = "";
 76  
       }
 77  0
       if(  s.length() > 0 ){
 78  0
          char first = s.charAt( 0 );
 79  0
          char last = s.charAt( s.length() - 1 );
 80  0
          if( first == '[' && last != ']' ) {
 81  0
             throw syntaxError( "Found starting '[' but missing ']' at the end." );
 82  
          }
 83  0
          if( first == '{' && last != '}' ) {
 84  0
             throw syntaxError( "Found starting '{' but missing '}' at the end." );
 85  
          }
 86  
       }
 87  0
       this.mySource = s;
 88  0
    }
 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  0
       if( this.myIndex > 0 ){
 97  0
          this.myIndex -= 1;
 98  
       }
 99  0
    }
 100  
 
 101  
    public int length() {
 102  0
       if( this.mySource == null ){
 103  0
          return 0;
 104  
       }
 105  0
       return this.mySource.length();
 106  
    }
 107  
 
 108  
    public boolean matches( String pattern ) {
 109  0
       String str = this.mySource.substring( this.myIndex );
 110  0
       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  0
       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  0
       if( more() ){
 131  0
          char c = this.mySource.charAt( this.myIndex );
 132  0
          this.myIndex += 1;
 133  0
          return c;
 134  
       }
 135  0
       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  0
       char n = next();
 148  0
       if( n != c ){
 149  0
          throw syntaxError( "Expected '" + c + "' and instead saw '" + n + "'." );
 150  
       }
 151  0
       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  0
       int i = this.myIndex;
 164  0
       int j = i + n;
 165  0
       if( j >= this.mySource.length() ){
 166  0
          throw syntaxError( "Substring bounds error" );
 167  
       }
 168  0
       this.myIndex += n;
 169  0
       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  0
          char c = next();
 182  0
          if( c == '/' ){
 183  0
             switch( next() ){
 184  
                case '/':
 185  
                   do{
 186  0
                      c = next();
 187  0
                   }while( c != '\n' && c != '\r' && c != 0 );
 188  0
                   break;
 189  
                case '*':
 190  
                   for( ;; ){
 191  0
                      c = next();
 192  0
                      if( c == 0 ){
 193  0
                         throw syntaxError( "Unclosed comment." );
 194  
                      }
 195  0
                      if( c == '*' ){
 196  0
                         if( next() == '/' ){
 197  0
                            break;
 198  
                         }
 199  0
                         back();
 200  
                      }
 201  
                   }
 202  
                   break;
 203  
                default:
 204  0
                   back();
 205  0
                   return '/';
 206  
             }
 207  0
          }else if( c == '#' ){
 208  
             do{
 209  0
                c = next();
 210  0
             }while( c != '\n' && c != '\r' && c != 0 );
 211  0
          }else if( c == 0 || c > ' ' ){
 212  0
             return c;
 213  
          }
 214  0
       }
 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  0
       StringBuffer sb = new StringBuffer();
 230  
       for( ;; ){
 231  0
          c = next();
 232  0
          switch( c ){
 233  
             case 0:
 234  
             case '\n':
 235  
             case '\r':
 236  0
                throw syntaxError( "Unterminated string" );
 237  
             case '\\':
 238  0
                c = next();
 239  0
                switch( c ){
 240  
                   case 'b':
 241  0
                      sb.append( '\b' );
 242  0
                      break;
 243  
                   case 't':
 244  0
                      sb.append( '\t' );
 245  0
                      break;
 246  
                   case 'n':
 247  0
                      sb.append( '\n' );
 248  0
                      break;
 249  
                   case 'f':
 250  0
                      sb.append( '\f' );
 251  0
                      break;
 252  
                   case 'r':
 253  0
                      sb.append( '\r' );
 254  0
                      break;
 255  
                   case 'u':
 256  0
                      sb.append( (char) Integer.parseInt( next( 4 ), 16 ) );
 257  0
                      break;
 258  
                   case 'x':
 259  0
                      sb.append( (char) Integer.parseInt( next( 2 ), 16 ) );
 260  0
                      break;
 261  
                   default:
 262  0
                      sb.append( c );
 263  
                }
 264  0
                break;
 265  
             default:
 266  0
                if( c == quote ){
 267  0
                   return sb.toString();
 268  
                }
 269  0
                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  0
       StringBuffer sb = new StringBuffer();
 283  
       for( ;; ){
 284  0
          char c = next();
 285  0
          if( c == d || c == 0 || c == '\n' || c == '\r' ){
 286  0
             if( c != 0 ){
 287  0
                back();
 288  
             }
 289  0
             return sb.toString()
 290  
                   .trim();
 291  
          }
 292  0
          sb.append( c );
 293  0
       }
 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  0
       StringBuffer sb = new StringBuffer();
 306  
       for( ;; ){
 307  0
          c = next();
 308  0
          if( delimiters.indexOf( c ) >= 0 || c == 0 || c == '\n' || c == '\r' ){
 309  0
             if( c != 0 ){
 310  0
                back();
 311  
             }
 312  0
             return sb.toString()
 313  
                   .trim();
 314  
          }
 315  0
          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  0
       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  0
       char c = nextClean();
 339  
       String s;
 340  
 
 341  0
       switch( c ){
 342  
          case '"':
 343  
          case '\'':
 344  0
             return nextString( c );
 345  
          case '{':
 346  0
             back();
 347  0
             return JSONObject.fromObject( this, jsonConfig );
 348  
          case '[':
 349  0
             back();
 350  0
             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  0
       StringBuffer sb = new StringBuffer();
 363  0
       char b = c;
 364  0
       while( c >= ' ' && ",:]}/\\\"[{;=#".indexOf( c ) < 0 ){
 365  0
          sb.append( c );
 366  0
          c = next();
 367  
       }
 368  0
       back();
 369  
 
 370  
       /*
 371  
        * If it is true, false, or null, return the proper value.
 372  
        */
 373  
 
 374  0
       s = sb.toString()
 375  
             .trim();
 376  0
       if( s.equals( "" ) ){
 377  0
          throw syntaxError( "Missing value." );
 378  
       }
 379  0
       if( s.equalsIgnoreCase( "true" ) ){
 380  0
          return Boolean.TRUE;
 381  
       }
 382  0
       if( s.equalsIgnoreCase( "false" ) ){
 383  0
          return Boolean.FALSE;
 384  
       }
 385  0
       if( s.equals( "null" ) || 
 386  
           (jsonConfig.isJavascriptCompliant() && s.equals("undefined"))){
 387  0
          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  0
       if( (b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+' ){
 399  0
          if( b == '0' ){
 400  0
             if( s.length() > 2 && (s.charAt( 1 ) == 'x' || s.charAt( 1 ) == 'X') ){
 401  
                try{
 402  0
                   return new Integer( Integer.parseInt( s.substring( 2 ), 16 ) );
 403  0
                }catch( Exception e ){
 404  
                   /* Ignore the error */
 405  0
                }
 406  
             }else{
 407  
                try{
 408  0
                   return new Integer( Integer.parseInt( s, 8 ) );
 409  0
                }catch( Exception e ){
 410  
                   /* Ignore the error */
 411  
                }
 412  
             }
 413  
          }
 414  
          try{
 415  0
             return new Integer( s );
 416  0
          }catch( Exception e ){
 417  
             try{
 418  0
                return new Long( s );
 419  0
             }catch( Exception f ){
 420  
                try{
 421  0
                   return new Double( s );
 422  0
                }catch( Exception g ){
 423  0
                   return s;
 424  
                }
 425  
             }
 426  
          }
 427  
       }
 428  
 
 429  0
       if( JSONUtils.isFunctionHeader( s ) || JSONUtils.isFunction( s ) ){
 430  0
          return s;
 431  
       }
 432  0
       switch( peek() ){
 433  
          case ',':
 434  
          case '}':
 435  
          case '{':
 436  
          case '[':
 437  
          case ']':
 438  0
             throw new JSONException( "Unquotted string '" + s + "'" );
 439  
       }
 440  
 
 441  0
       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  0
       if( more() ){
 451  0
          char c = this.mySource.charAt( this.myIndex );
 452  0
          return c;
 453  
       }
 454  0
       return 0;
 455  
    }
 456  
 
 457  
    public void reset() {
 458  0
       this.myIndex = 0;
 459  0
    }
 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  0
       this.myIndex = this.mySource.indexOf( to, this.myIndex );
 469  0
       if( this.myIndex < 0 ){
 470  0
          this.myIndex = this.mySource.length();
 471  
       }else{
 472  0
          this.myIndex += to.length();
 473  
       }
 474  0
    }
 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  0
       int index = this.myIndex;
 487  
       do{
 488  0
          c = next();
 489  0
          if( c == 0 ){
 490  0
             this.myIndex = index;
 491  0
             return c;
 492  
          }
 493  0
       }while( c != to );
 494  0
       back();
 495  0
       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  0
       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  0
       return " at character " + this.myIndex + " of " + this.mySource;
 515  
    }
 516  
 }