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> <small>(double 220 * quote)</small> or <code>'</code> <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> <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 }