Coverage Report - org.apache.tapestry.json.JSONTokener
 
Classes in this File Line Coverage Branch Coverage Complexity
JSONTokener
0%
0/145
0%
0/155
7.353
 
 1  
 package org.apache.tapestry.json;
 2  
 
 3  
 /*
 4  
  Copyright (c) 2002 JSON.org
 5  
 
 6  
  Permission is hereby granted, free of charge, to any person obtaining a copy
 7  
  of this software and associated documentation files (the "Software"), to deal
 8  
  in the Software without restriction, including without limitation the rights
 9  
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  
  copies of the Software, and to permit persons to whom the Software is
 11  
  furnished to do so, subject to the following conditions:
 12  
 
 13  
  The above copyright notice and this permission notice shall be included in all
 14  
  copies or substantial portions of the Software.
 15  
 
 16  
  The Software shall be used for Good, not Evil.
 17  
 
 18  
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 24  
  SOFTWARE.
 25  
  */
 26  
 
 27  
 import java.text.ParseException;
 28  
 
 29  
 /**
 30  
  * A JSONTokener takes a source string and extracts characters and tokens from
 31  
  * it. It is used by the JSONObject and JSONArray constructors to parse JSON
 32  
  * source strings.
 33  
  * 
 34  
  * <p/>The toString() method has been modified from its original form to provide
 35  
  * easier to understand exception reporting.
 36  
  * 
 37  
  * @author JSON.org, jkuhnert 
 38  
  * @version 1
 39  
  */
 40  
 public class JSONTokener
 41  
 {
 42  
 
 43  
     /**
 44  
      * The index of the next character.
 45  
      */
 46  
     private int myIndex;
 47  
 
 48  
     /**
 49  
      * The source string being tokenized.
 50  
      */
 51  
     private String mySource;
 52  
 
 53  
     /**
 54  
      * Construct a JSONTokener from a string.
 55  
      * 
 56  
      * @param s
 57  
      *            A source string.
 58  
      */
 59  
     public JSONTokener(String s)
 60  0
     {
 61  0
         this.myIndex = 0;
 62  0
         this.mySource = s;
 63  0
     }
 64  
 
 65  
     /**
 66  
      * Back up one character. This provides a sort of lookahead capability, so
 67  
      * that you can test for a digit or letter before attempting to parse the
 68  
      * next number or identifier.
 69  
      */
 70  
     public void back()
 71  
     {
 72  0
         if (this.myIndex > 0)
 73  
         {
 74  0
             this.myIndex -= 1;
 75  
         }
 76  0
     }
 77  
 
 78  
     /**
 79  
      * Get the hex value of a character (base16).
 80  
      * 
 81  
      * @param c
 82  
      *            A character between '0' and '9' or between 'A' and 'F' or
 83  
      *            between 'a' and 'f'.
 84  
      * @return An int between 0 and 15, or -1 if c was not a hex digit.
 85  
      */
 86  
     public static int dehexchar(char c)
 87  
     {
 88  0
         if (c >= '0' && c <= '9') { return c - '0'; }
 89  0
         if (c >= 'A' && c <= 'F') { return c + 10 - 'A'; }
 90  0
         if (c >= 'a' && c <= 'f') { return c + 10 - 'a'; }
 91  0
         return -1;
 92  
     }
 93  
 
 94  
     /**
 95  
      * Determine if the source string still contains characters that next() can
 96  
      * consume.
 97  
      * 
 98  
      * @return true if not yet at the end of the source.
 99  
      */
 100  
     public boolean more()
 101  
     {
 102  0
         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  0
         if (more())
 113  
         {
 114  0
             char c = this.mySource.charAt(this.myIndex);
 115  0
             this.myIndex += 1;
 116  0
             return c;
 117  
         }
 118  0
         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  0
         char n = next();
 135  0
         if (n != c) { throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'."); }
 136  0
         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  0
         int i = this.myIndex;
 153  0
         int j = i + n;
 154  0
         if (j >= this.mySource.length()) { throw syntaxError("Substring bounds error"); }
 155  0
         this.myIndex += n;
 156  0
         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  0
             char c = next();
 172  0
             if (c == '/')
 173  
             {
 174  0
                 switch(next())
 175  
                 {
 176  
                 case '/':
 177  
                     do
 178  
                     {
 179  0
                         c = next();
 180  0
                     } while(c != '\n' && c != '\r' && c != 0);
 181  0
                     break;
 182  
                 case '*':
 183  
                     while(true)
 184  
                     {
 185  0
                         c = next();
 186  0
                         if (c == 0) { throw syntaxError("Unclosed comment."); }
 187  0
                         if (c == '*')
 188  
                         {
 189  0
                             if (next() == '/')
 190  
                             {
 191  0
                                 break;
 192  
                             }
 193  0
                             back();
 194  
                         }
 195  
                     }
 196  
                     break;
 197  
                 default:
 198  0
                     back();
 199  0
                     return '/';
 200  
                 }
 201  
             }
 202  0
             else if (c == '#')
 203  
             {
 204  
                 do
 205  
                 {
 206  0
                     c = next();
 207  0
                 } while(c != '\n' && c != '\r' && c != 0);
 208  
             }
 209  0
             else if (c == 0 || c > ' ') { return c; }
 210  0
         }
 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  0
         StringBuffer sb = new StringBuffer();
 231  
         while(true)
 232  
         {
 233  0
             c = next();
 234  0
             switch(c)
 235  
             {
 236  
             case 0:
 237  
             case '\n':
 238  
             case '\r':
 239  0
                 throw syntaxError("Unterminated string");
 240  
             case '\\':
 241  0
                 c = next();
 242  0
                 switch(c)
 243  
                 {
 244  
                 case 'b':
 245  0
                     sb.append('\b');
 246  0
                     break;
 247  
                 case 't':
 248  0
                     sb.append('\t');
 249  0
                     break;
 250  
                 case 'n':
 251  0
                     sb.append('\n');
 252  0
                     break;
 253  
                 case 'f':
 254  0
                     sb.append('\f');
 255  0
                     break;
 256  
                 case 'r':
 257  0
                     sb.append('\r');
 258  0
                     break;
 259  
                 case 'u':
 260  0
                     sb.append((char) Integer.parseInt(next(4), 16));
 261  0
                     break;
 262  
                 case 'x':
 263  0
                     sb.append((char) Integer.parseInt(next(2), 16));
 264  0
                     break;
 265  
                 default:
 266  0
                     sb.append(c);
 267  
                 }
 268  0
                 break;
 269  
             default:
 270  0
                 if (c == quote) { return sb.toString(); }
 271  0
                 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  0
         StringBuffer sb = new StringBuffer();
 287  
         while(true)
 288  
         {
 289  0
             char c = next();
 290  0
             if (c == d || c == 0 || c == '\n' || c == '\r')
 291  
             {
 292  0
                 if (c != 0)
 293  
                 {
 294  0
                     back();
 295  
                 }
 296  0
                 return sb.toString().trim();
 297  
             }
 298  0
             sb.append(c);
 299  0
         }
 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  0
         StringBuffer sb = new StringBuffer();
 314  
         while(true)
 315  
         {
 316  0
             c = next();
 317  0
             if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r')
 318  
             {
 319  0
                 if (c != 0)
 320  
                 {
 321  0
                     back();
 322  
                 }
 323  0
                 return sb.toString().trim();
 324  
             }
 325  0
             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  0
         char c = nextClean();
 341  
         String s;
 342  
 
 343  0
         switch(c)
 344  
         {
 345  
         case '"':
 346  
         case '\'':
 347  0
             return nextString(c);
 348  
         case '{':
 349  0
             back();
 350  0
             return new JSONObject(this);
 351  
         case '[':
 352  0
             back();
 353  0
             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  0
         StringBuffer sb = new StringBuffer();
 364  0
         char b = c;
 365  0
         while(c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0)
 366  
         {
 367  0
             sb.append(c);
 368  0
             c = next();
 369  
         }
 370  0
         back();
 371  
 
 372  
         /*
 373  
          * If it is true, false, or null, return the proper value.
 374  
          */
 375  
 
 376  0
         s = sb.toString().trim();
 377  0
         if (s.equals("")) { throw syntaxError("Missing value."); }
 378  0
         if (s.equalsIgnoreCase("true")) { return Boolean.TRUE; }
 379  0
         if (s.equalsIgnoreCase("false")) { return Boolean.FALSE; }
 380  0
         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  0
         if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+')
 391  
         {
 392  0
             if (b == '0')
 393  
             {
 394  0
                 if (s.length() > 2 && (s.charAt(1) == 'x' || s.charAt(1) == 'X'))
 395  
                 {
 396  
                     try
 397  
                     {
 398  0
                         return new Integer(Integer.parseInt(s.substring(2), 16));
 399  
                     }
 400  0
                     catch (Exception e)
 401  
                     {
 402  
                         /* Ignore the error */
 403  0
                     }
 404  
                 }
 405  
                 else
 406  
                 {
 407  
                     try
 408  
                     {
 409  0
                         return new Integer(Integer.parseInt(s, 8));
 410  
                     }
 411  0
                     catch (Exception e)
 412  
                     {
 413  
                         /* Ignore the error */
 414  
                     }
 415  
                 }
 416  
             }
 417  
             try
 418  
             {
 419  0
                 return new Integer(s);
 420  
             }
 421  0
             catch (Exception e)
 422  
             {
 423  
                 /* Ignore the error */
 424  
             }
 425  
             try
 426  
             {
 427  0
                 return new Double(s);
 428  
             }
 429  0
             catch (Exception e)
 430  
             {
 431  
                 /* Ignore the error */
 432  
             }
 433  
         }
 434  0
         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  0
         int index = this.myIndex;
 450  
         do
 451  
         {
 452  0
             c = next();
 453  0
             if (c == 0)
 454  
             {
 455  0
                 this.myIndex = index;
 456  0
                 return c;
 457  
             }
 458  0
         } while(c != to);
 459  0
         back();
 460  0
         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  0
         this.myIndex = this.mySource.indexOf(to, this.myIndex);
 473  0
         if (this.myIndex < 0)
 474  
         {
 475  0
             this.myIndex = this.mySource.length();
 476  
         }
 477  
         else
 478  
         {
 479  0
             this.myIndex += to.length();
 480  
         }
 481  0
     }
 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  0
         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  0
         String before = this.mySource.substring(0, this.myIndex);
 503  0
         String after = this.mySource.substring(this.myIndex);
 504  
         
 505  0
         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  0
         int len = s.length();
 520  0
         StringBuffer b = new StringBuffer();
 521  0
         for(int i = 0; i < len; ++i)
 522  
         {
 523  0
             char c = s.charAt(i);
 524  0
             if (c == '+')
 525  
             {
 526  0
                 c = ' ';
 527  
             }
 528  0
             else if (c == '%' && i + 2 < len)
 529  
             {
 530  0
                 int d = dehexchar(s.charAt(i + 1));
 531  0
                 int e = dehexchar(s.charAt(i + 2));
 532  0
                 if (d >= 0 && e >= 0)
 533  
                 {
 534  0
                     c = (char) (d * 16 + e);
 535  0
                     i += 2;
 536  
                 }
 537  
             }
 538  0
             b.append(c);
 539  
         }
 540  0
         return b.toString();
 541  
     }
 542  
 }