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