001/* 002 * Copyright 2016-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util.json; 022 023 024 025import java.io.BufferedInputStream; 026import java.io.Closeable; 027import java.io.InputStream; 028import java.io.IOException; 029import java.nio.charset.StandardCharsets; 030import java.util.ArrayList; 031import java.util.LinkedHashMap; 032import java.util.Map; 033 034import com.unboundid.util.ByteStringBuffer; 035import com.unboundid.util.Debug; 036import com.unboundid.util.StaticUtils; 037import com.unboundid.util.ThreadSafety; 038import com.unboundid.util.ThreadSafetyLevel; 039 040import static com.unboundid.util.json.JSONMessages.*; 041 042 043 044/** 045 * This class provides a mechanism for reading JSON objects from an input 046 * stream. It assumes that any non-ASCII data that may be read from the input 047 * stream is encoded as UTF-8. 048 */ 049@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 050public final class JSONObjectReader 051 implements Closeable 052{ 053 // The buffer used to hold the bytes of the object currently being read. 054 private final ByteStringBuffer currentObjectBytes; 055 056 // A buffer to use to hold strings being decoded. 057 private final ByteStringBuffer stringBuffer; 058 059 // The input stream from which JSON objects will be read. 060 private final InputStream inputStream; 061 062 063 064 /** 065 * Creates a new JSON object reader that will read objects from the provided 066 * input stream. 067 * 068 * @param inputStream The input stream from which the data should be read. 069 */ 070 public JSONObjectReader(final InputStream inputStream) 071 { 072 this(inputStream, true); 073 } 074 075 076 077 /** 078 * Creates a new JSON object reader that will read objects from the provided 079 * input stream. 080 * 081 * @param inputStream The input stream from which the data should be 082 * read. 083 * @param bufferInputStream Indicates whether to buffer the input stream. 084 * This should be {@code false} if the input stream 085 * could be used for any purpose other than reading 086 * JSON objects after one or more objects are read. 087 */ 088 public JSONObjectReader(final InputStream inputStream, 089 final boolean bufferInputStream) 090 { 091 if (bufferInputStream && (! (inputStream instanceof BufferedInputStream))) 092 { 093 this.inputStream = new BufferedInputStream(inputStream); 094 } 095 else 096 { 097 this.inputStream = inputStream; 098 } 099 100 currentObjectBytes = new ByteStringBuffer(); 101 stringBuffer = new ByteStringBuffer(); 102 } 103 104 105 106 /** 107 * Reads the next JSON object from the input stream. 108 * 109 * @return The JSON object that was read, or {@code null} if the end of the 110 * end of the stream has been reached.. 111 * 112 * @throws IOException If a problem is encountered while reading from the 113 * input stream. 114 * 115 * @throws JSONException If the data read 116 */ 117 public JSONObject readObject() 118 throws IOException, JSONException 119 { 120 // Skip over any whitespace before the beginning of the next object. 121 skipWhitespace(); 122 currentObjectBytes.clear(); 123 124 125 // The JSON object must start with an open curly brace. 126 final Object firstToken = readToken(true); 127 if (firstToken == null) 128 { 129 return null; 130 } 131 132 if (! firstToken.equals('{')) 133 { 134 throw new JSONException(ERR_OBJECT_READER_ILLEGAL_START_OF_OBJECT.get( 135 String.valueOf(firstToken))); 136 } 137 138 final LinkedHashMap<String,JSONValue> m = new LinkedHashMap<>(10); 139 readObject(m); 140 141 return new JSONObject(m, currentObjectBytes.toString()); 142 } 143 144 145 146 /** 147 * Closes this JSON object reader and the underlying input stream. 148 * 149 * @throws IOException If a problem is encountered while closing the 150 * underlying input stream. 151 */ 152 @Override() 153 public void close() 154 throws IOException 155 { 156 inputStream.close(); 157 } 158 159 160 161 /** 162 * Reads a token from the input stream, skipping over any insignificant 163 * whitespace that may be before the token. The token that is returned will 164 * be one of the following: 165 * <UL> 166 * <LI>A {@code Character} that is an opening curly brace.</LI> 167 * <LI>A {@code Character} that is a closing curly brace.</LI> 168 * <LI>A {@code Character} that is an opening square bracket.</LI> 169 * <LI>A {@code Character} that is a closing square bracket.</LI> 170 * <LI>A {@code Character} that is a colon.</LI> 171 * <LI>A {@code Character} that is a comma.</LI> 172 * <LI>A {@link JSONBoolean}.</LI> 173 * <LI>A {@link JSONNull}.</LI> 174 * <LI>A {@link JSONNumber}.</LI> 175 * <LI>A {@link JSONString}.</LI> 176 * </UL> 177 * 178 * @param allowEndOfStream Indicates whether it is acceptable to encounter 179 * the end of the input stream. This should only 180 * be {@code true} when the token is expected to be 181 * the open parenthesis of the outermost JSON 182 * object. 183 * 184 * @return The token that was read, or {@code null} if the end of the input 185 * stream was reached. 186 * 187 * @throws IOException If a problem is encountered while reading from the 188 * input stream. 189 * 190 * @throws JSONException If a problem was encountered while reading the 191 * token. 192 */ 193 private Object readToken(final boolean allowEndOfStream) 194 throws IOException, JSONException 195 { 196 skipWhitespace(); 197 198 final Byte byteRead = readByte(allowEndOfStream); 199 if (byteRead == null) 200 { 201 return null; 202 } 203 204 switch (byteRead) 205 { 206 case '{': 207 return '{'; 208 case '}': 209 return '}'; 210 case '[': 211 return '['; 212 case ']': 213 return ']'; 214 case ':': 215 return ':'; 216 case ',': 217 return ','; 218 219 case '"': 220 // This is the start of a JSON string. 221 return readString(); 222 223 case 't': 224 case 'f': 225 // This is the start of a JSON true or false value. 226 return readBoolean(); 227 228 case 'n': 229 // This is the start of a JSON null value. 230 return readNull(); 231 232 case '-': 233 case '0': 234 case '1': 235 case '2': 236 case '3': 237 case '4': 238 case '5': 239 case '6': 240 case '7': 241 case '8': 242 case '9': 243 // This is the start of a JSON number value. 244 return readNumber(); 245 246 default: 247 throw new JSONException( 248 ERR_OBJECT_READER_ILLEGAL_FIRST_CHAR_FOR_JSON_TOKEN.get( 249 currentObjectBytes.length(), byteToCharString(byteRead))); 250 } 251 } 252 253 254 255 /** 256 * Skips over any valid JSON whitespace at the current position in the input 257 * stream. 258 * 259 * @throws IOException If a problem is encountered while reading from the 260 * input stream. 261 * 262 * @throws JSONException If a problem is encountered while skipping 263 * whitespace. 264 */ 265 private void skipWhitespace() 266 throws IOException, JSONException 267 { 268 while (true) 269 { 270 inputStream.mark(1); 271 final Byte byteRead = readByte(true); 272 if (byteRead == null) 273 { 274 // We've reached the end of the input stream. 275 return; 276 } 277 278 switch (byteRead) 279 { 280 case ' ': 281 case '\t': 282 case '\n': 283 case '\r': 284 // Spaces, tabs, newlines, and carriage returns are valid JSON 285 // whitespace. 286 break; 287 288 // Technically, JSON does not provide support for comments. But this 289 // implementation will accept three types of comments: 290 // - Comments that start with /* and end with */ (potentially spanning 291 // multiple lines). 292 // - Comments that start with // and continue until the end of the line. 293 // - Comments that start with # and continue until the end of the line. 294 // All comments will be ignored by the parser. 295 case '/': 296 // This probably starts a comment. If so, then the next byte must be 297 // either another forward slash or an asterisk. 298 final byte nextByte = readByte(false); 299 if (nextByte == '/') 300 { 301 // Keep reading until we encounter a newline, a carriage return, or 302 // the end of the input stream. 303 while (true) 304 { 305 final Byte commentByte = readByte(true); 306 if (commentByte == null) 307 { 308 return; 309 } 310 311 if ((commentByte == '\n') || (commentByte == '\r')) 312 { 313 break; 314 } 315 } 316 } 317 else if (nextByte == '*') 318 { 319 // Keep reading until we encounter an asterisk followed by a slash. 320 // If we hit the end of the input stream before that, then that's an 321 // error. 322 while (true) 323 { 324 final Byte commentByte = readByte(false); 325 if (commentByte == '*') 326 { 327 final Byte possibleSlashByte = readByte(false); 328 if (possibleSlashByte == '/') 329 { 330 break; 331 } 332 } 333 } 334 } 335 else 336 { 337 throw new JSONException( 338 ERR_OBJECT_READER_ILLEGAL_SLASH_SKIPPING_WHITESPACE.get( 339 currentObjectBytes.length())); 340 } 341 break; 342 343 case '#': 344 // Keep reading until we encounter a newline, a carriage return, or 345 // the end of the input stream. 346 while (true) 347 { 348 final Byte commentByte = readByte(true); 349 if (commentByte == null) 350 { 351 return; 352 } 353 354 if ((commentByte == '\n') || (commentByte == '\r')) 355 { 356 break; 357 } 358 } 359 break; 360 361 default: 362 // We read a byte that isn't whitespace, so we'll need to reset the 363 // stream so it will be read again, and we'll also need to remove the 364 // that byte from the currentObjectBytes buffer. 365 inputStream.reset(); 366 currentObjectBytes.setLength(currentObjectBytes.length() - 1); 367 return; 368 } 369 } 370 } 371 372 373 374 /** 375 * Reads the next byte from the input stream. 376 * 377 * @param allowEndOfStream Indicates whether it is acceptable to encounter 378 * the end of the input stream. This should only 379 * be {@code true} when the token is expected to be 380 * the open parenthesis of the outermost JSON 381 * object. 382 * 383 * @return The next byte read from the input stream, or {@code null} if the 384 * end of the input stream has been reached and that is acceptable. 385 * 386 * @throws IOException If a problem is encountered while reading from the 387 * input stream. 388 * 389 * @throws JSONException If the end of the input stream is reached when that 390 * is not acceptable. 391 */ 392 private Byte readByte(final boolean allowEndOfStream) 393 throws IOException, JSONException 394 { 395 final int byteRead = inputStream.read(); 396 if (byteRead < 0) 397 { 398 if (allowEndOfStream) 399 { 400 return null; 401 } 402 else 403 { 404 throw new JSONException(ERR_OBJECT_READER_UNEXPECTED_END_OF_STREAM.get( 405 currentObjectBytes.length())); 406 } 407 } 408 409 final byte b = (byte) (byteRead & 0xFF); 410 currentObjectBytes.append(b); 411 return b; 412 } 413 414 415 416 /** 417 * Reads a string from the input stream. The open quotation must have already 418 * been read. 419 * 420 * @return The JSON string that was read. 421 * 422 * @throws IOException If a problem is encountered while reading from the 423 * input stream. 424 * 425 * @throws JSONException If a problem was encountered while reading the JSON 426 * string. 427 */ 428 private JSONString readString() 429 throws IOException, JSONException 430 { 431 // Use a buffer to hold the string being decoded. Also mark the current 432 // position in the bytes that comprise the string representation so that 433 // the JSON string representation (including the opening quote) will be 434 // exactly as it was provided. 435 stringBuffer.clear(); 436 final int jsonStringStartPos = currentObjectBytes.length() - 1; 437 while (true) 438 { 439 final Byte byteRead = readByte(false); 440 441 // See if it's a non-ASCII byte. If so, then assume that it's UTF-8 and 442 // read the appropriate number of remaining bytes. We need to handle this 443 // specially to avoid incorrectly detecting the end of the string because 444 // a subsequent byte in a multi-byte character happens to be the same as 445 // the ASCII quotation mark byte. 446 if ((byteRead & 0x80) == 0x80) 447 { 448 final byte[] charBytes; 449 if ((byteRead & 0xE0) == 0xC0) 450 { 451 // It's a two-byte character. 452 charBytes = new byte[] 453 { 454 byteRead, 455 readByte(false) 456 }; 457 } 458 else if ((byteRead & 0xF0) == 0xE0) 459 { 460 // It's a three-byte character. 461 charBytes = new byte[] 462 { 463 byteRead, 464 readByte(false), 465 readByte(false) 466 }; 467 } 468 else if ((byteRead & 0xF8) == 0xF0) 469 { 470 // It's a four-byte character. 471 charBytes = new byte[] 472 { 473 byteRead, 474 readByte(false), 475 readByte(false), 476 readByte(false) 477 }; 478 } 479 else 480 { 481 // This isn't a valid UTF-8 sequence. 482 throw new JSONException( 483 ERR_OBJECT_READER_INVALID_UTF_8_BYTE_IN_STREAM.get( 484 currentObjectBytes.length(), 485 "0x" + StaticUtils.toHex(byteRead))); 486 } 487 488 stringBuffer.append(new String(charBytes, StandardCharsets.UTF_8)); 489 continue; 490 } 491 492 493 // If the byte that we read was an escape, then we know that whatever 494 // immediately follows it shouldn't be allowed to signal the end of the 495 // string. 496 if (byteRead == '\\') 497 { 498 final byte nextByte = readByte(false); 499 switch (nextByte) 500 { 501 case '"': 502 case '\\': 503 case '/': 504 stringBuffer.append(nextByte); 505 break; 506 case 'b': 507 stringBuffer.append('\b'); 508 break; 509 case 'f': 510 stringBuffer.append('\f'); 511 break; 512 case 'n': 513 stringBuffer.append('\n'); 514 break; 515 case 'r': 516 stringBuffer.append('\r'); 517 break; 518 case 't': 519 stringBuffer.append('\t'); 520 break; 521 case 'u': 522 final char[] hexChars = 523 { 524 (char) (readByte(false) & 0xFF), 525 (char) (readByte(false) & 0xFF), 526 (char) (readByte(false) & 0xFF), 527 (char) (readByte(false) & 0xFF) 528 }; 529 530 try 531 { 532 stringBuffer.append( 533 (char) Integer.parseInt(new String(hexChars), 16)); 534 } 535 catch (final Exception e) 536 { 537 Debug.debugException(e); 538 throw new JSONException( 539 ERR_OBJECT_READER_INVALID_UNICODE_ESCAPE.get( 540 currentObjectBytes.length()), 541 e); 542 } 543 break; 544 default: 545 throw new JSONException( 546 ERR_OBJECT_READER_INVALID_ESCAPED_CHAR.get( 547 currentObjectBytes.length(), byteToCharString(nextByte))); 548 } 549 continue; 550 } 551 552 if (byteRead == '"') 553 { 554 // It's an unescaped quote, so it marks the end of the string. 555 return new JSONString(stringBuffer.toString(), 556 new String(currentObjectBytes.getBackingArray(), 557 jsonStringStartPos, 558 (currentObjectBytes.length() - jsonStringStartPos), 559 StandardCharsets.UTF_8)); 560 } 561 562 final int byteReadInt = (byteRead & 0xFF); 563 if ((byteRead & 0xFF) <= 0x1F) 564 { 565 throw new JSONException(ERR_OBJECT_READER_UNESCAPED_CONTROL_CHAR.get( 566 currentObjectBytes.length(), byteToCharString(byteRead))); 567 } 568 else 569 { 570 stringBuffer.append((char) byteReadInt); 571 } 572 } 573 } 574 575 576 577 /** 578 * Reads a JSON Boolean from the input stream. The first byte of either 't' 579 * or 'f' will have already been read. 580 * 581 * @return The JSON Boolean that was read. 582 * 583 * @throws IOException If a problem is encountered while reading from the 584 * input stream. 585 * 586 * @throws JSONException If a problem was encountered while reading the JSON 587 * Boolean. 588 */ 589 private JSONBoolean readBoolean() 590 throws IOException, JSONException 591 { 592 final byte firstByte = 593 currentObjectBytes.getBackingArray()[currentObjectBytes.length() - 1]; 594 if (firstByte == 't') 595 { 596 if ((readByte(false) == 'r') && 597 (readByte(false) == 'u') && 598 (readByte(false) == 'e')) 599 { 600 return JSONBoolean.TRUE; 601 } 602 603 throw new JSONException(ERR_OBJECT_READER_INVALID_BOOLEAN_TRUE.get( 604 currentObjectBytes.length())); 605 } 606 else 607 { 608 if ((readByte(false) == 'a') && 609 (readByte(false) == 'l') && 610 (readByte(false) == 's') && 611 (readByte(false) == 'e')) 612 { 613 return JSONBoolean.FALSE; 614 } 615 616 throw new JSONException(ERR_OBJECT_READER_INVALID_BOOLEAN_FALSE.get( 617 currentObjectBytes.length())); 618 } 619 } 620 621 622 623 /** 624 * Reads a JSON Boolean from the input stream. The first byte of 'n' will 625 * have already been read. 626 * 627 * @return The JSON null that was read. 628 * 629 * @throws IOException If a problem is encountered while reading from the 630 * input stream. 631 * 632 * @throws JSONException If a problem was encountered while reading the JSON 633 * null. 634 */ 635 private JSONNull readNull() 636 throws IOException, JSONException 637 { 638 if ((readByte(false) == 'u') && 639 (readByte(false) == 'l') && 640 (readByte(false) == 'l')) 641 { 642 return JSONNull.NULL; 643 } 644 645 throw new JSONException(ERR_OBJECT_READER_INVALID_NULL.get( 646 currentObjectBytes.length())); 647 } 648 649 650 651 /** 652 * Reads a JSON number from the input stream. The first byte of the number 653 * will have already been read. 654 * 655 * @throws IOException If a problem is encountered while reading from the 656 * input stream. 657 * 658 * @return The JSON number that was read. 659 * 660 * @throws IOException If a problem is encountered while reading from the 661 * input stream. 662 * 663 * @throws JSONException If a problem was encountered while reading the JSON 664 * number. 665 */ 666 private JSONNumber readNumber() 667 throws IOException, JSONException 668 { 669 // Use a buffer to hold the string representation of the number being 670 // decoded. Since the first byte of the number has already been read, we'll 671 // need to add it into the buffer. 672 stringBuffer.clear(); 673 stringBuffer.append( 674 currentObjectBytes.getBackingArray()[currentObjectBytes.length() - 1]); 675 676 677 // Read until we encounter whitespace, a comma, a closing square bracket, or 678 // a closing curly brace. Then try to parse what we read as a number. 679 while (true) 680 { 681 // Mark the stream so that if we read a byte that isn't part of the 682 // number, we'll be able to rewind the stream so that byte will be read 683 // again by something else. 684 inputStream.mark(1); 685 686 final Byte b = readByte(false); 687 switch (b) 688 { 689 case ' ': 690 case '\t': 691 case '\n': 692 case '\r': 693 case ',': 694 case ']': 695 case '}': 696 // This tell us we're at the end of the number. Rewind the stream so 697 // that we can read this last byte again whatever tries to get the 698 // next token. Also remove it from the end of currentObjectBytes 699 // since it will be re-added when it's read again. 700 inputStream.reset(); 701 currentObjectBytes.setLength(currentObjectBytes.length() - 1); 702 return new JSONNumber(stringBuffer.toString()); 703 704 default: 705 stringBuffer.append(b); 706 } 707 } 708 } 709 710 711 712 /** 713 * Reads a JSON array from the input stream. The opening square bracket will 714 * have already been read. 715 * 716 * @return The JSON array that was read. 717 * 718 * @throws IOException If a problem is encountered while reading from the 719 * input stream. 720 * 721 * @throws JSONException If a problem was encountered while reading the JSON 722 * array. 723 */ 724 private JSONArray readArray() 725 throws IOException, JSONException 726 { 727 // The opening square bracket will have already been consumed, so read 728 // JSON values until we hit a closing square bracket. 729 final ArrayList<JSONValue> values = new ArrayList<>(10); 730 boolean firstToken = true; 731 while (true) 732 { 733 // If this is the first time through, it is acceptable to find a closing 734 // square bracket. Otherwise, we expect to find a JSON value, an opening 735 // square bracket to denote the start of an embedded array, or an opening 736 // curly brace to denote the start of an embedded JSON object. 737 final Object token = readToken(false); 738 if (token instanceof JSONValue) 739 { 740 values.add((JSONValue) token); 741 } 742 else if (token.equals('[')) 743 { 744 values.add(readArray()); 745 } 746 else if (token.equals('{')) 747 { 748 final LinkedHashMap<String,JSONValue> fieldMap = 749 new LinkedHashMap<>(10); 750 values.add(readObject(fieldMap)); 751 } 752 else if (token.equals(']') && firstToken) 753 { 754 // It's an empty array. 755 return JSONArray.EMPTY_ARRAY; 756 } 757 else 758 { 759 throw new JSONException(ERR_OBJECT_READER_INVALID_TOKEN_IN_ARRAY.get( 760 currentObjectBytes.length(), String.valueOf(token))); 761 } 762 763 firstToken = false; 764 765 766 // If we've gotten here, then we found a JSON value. It must be followed 767 // by either a comma (to indicate that there's at least one more value) or 768 // a closing square bracket (to denote the end of the array). 769 final Object nextToken = readToken(false); 770 if (nextToken.equals(']')) 771 { 772 return new JSONArray(values); 773 } 774 else if (! nextToken.equals(',')) 775 { 776 throw new JSONException( 777 ERR_OBJECT_READER_INVALID_TOKEN_AFTER_ARRAY_VALUE.get( 778 currentObjectBytes.length(), String.valueOf(nextToken))); 779 } 780 } 781 } 782 783 784 785 /** 786 * Reads a JSON object from the input stream. The opening curly brace will 787 * have already been read. 788 * 789 * @param fields The map into which to place the fields that are read. The 790 * returned object will include an unmodifiable view of this 791 * map, but the caller may use the map directly if desired. 792 * 793 * @return The JSON object that was read. 794 * 795 * @throws IOException If a problem is encountered while reading from the 796 * input stream. 797 * 798 * @throws JSONException If a problem was encountered while reading the JSON 799 * object. 800 */ 801 private JSONObject readObject(final Map<String,JSONValue> fields) 802 throws IOException, JSONException 803 { 804 boolean firstField = true; 805 while (true) 806 { 807 // Read the next token. It must be a JSONString, unless we haven't read 808 // any fields yet in which case it can be a closing curly brace to 809 // indicate that it's an empty object. 810 final String fieldName; 811 final Object fieldNameToken = readToken(false); 812 if (fieldNameToken instanceof JSONString) 813 { 814 fieldName = ((JSONString) fieldNameToken).stringValue(); 815 if (fields.containsKey(fieldName)) 816 { 817 throw new JSONException(ERR_OBJECT_READER_DUPLICATE_FIELD.get( 818 currentObjectBytes.length(), fieldName)); 819 } 820 } 821 else if (firstField && fieldNameToken.equals('}')) 822 { 823 return new JSONObject(fields); 824 } 825 else 826 { 827 throw new JSONException(ERR_OBJECT_READER_INVALID_TOKEN_IN_OBJECT.get( 828 currentObjectBytes.length(), String.valueOf(fieldNameToken))); 829 } 830 firstField = false; 831 832 // Read the next token. It must be a colon. 833 final Object colonToken = readToken(false); 834 if (! colonToken.equals(':')) 835 { 836 throw new JSONException(ERR_OBJECT_READER_TOKEN_NOT_COLON.get( 837 currentObjectBytes.length(), String.valueOf(colonToken), 838 String.valueOf(fieldNameToken))); 839 } 840 841 // Read the next token. It must be one of the following: 842 // - A JSONValue 843 // - An opening square bracket, designating the start of an array. 844 // - An opening curly brace, designating the start of an object. 845 final Object valueToken = readToken(false); 846 if (valueToken instanceof JSONValue) 847 { 848 fields.put(fieldName, (JSONValue) valueToken); 849 } 850 else if (valueToken.equals('[')) 851 { 852 final JSONArray a = readArray(); 853 fields.put(fieldName, a); 854 } 855 else if (valueToken.equals('{')) 856 { 857 final LinkedHashMap<String,JSONValue> m = new LinkedHashMap<>(10); 858 final JSONObject o = readObject(m); 859 fields.put(fieldName, o); 860 } 861 else 862 { 863 throw new JSONException(ERR_OBJECT_READER_TOKEN_NOT_VALUE.get( 864 currentObjectBytes.length(), String.valueOf(valueToken), 865 String.valueOf(fieldNameToken))); 866 } 867 868 // Read the next token. It must be either a comma (to indicate that 869 // there will be another field) or a closing curly brace (to indicate 870 // that the end of the object has been reached). 871 final Object separatorToken = readToken(false); 872 if (separatorToken.equals('}')) 873 { 874 return new JSONObject(fields); 875 } 876 else if (! separatorToken.equals(',')) 877 { 878 throw new JSONException( 879 ERR_OBJECT_READER_INVALID_TOKEN_AFTER_OBJECT_VALUE.get( 880 currentObjectBytes.length(), String.valueOf(separatorToken), 881 String.valueOf(fieldNameToken))); 882 } 883 } 884 } 885 886 887 888 /** 889 * Retrieves a string representation of the provided byte that is intended to 890 * represent a character. If the provided byte is a printable ASCII 891 * character, then that character will be used. Otherwise, the string 892 * representation will be "0x" followed by the hexadecimal representation of 893 * the byte. 894 * 895 * @param b The byte for which to obtain the string representation. 896 * 897 * @return A string representation of the provided byte. 898 */ 899 private static String byteToCharString(final byte b) 900 { 901 if ((b >= ' ') && (b <= '~')) 902 { 903 return String.valueOf((char) (b & 0xFF)); 904 } 905 else 906 { 907 return "0x" + StaticUtils.toHex(b); 908 } 909 } 910}