001/* 002 * Copyright 2016-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2019 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.util.ArrayList; 030import java.util.LinkedHashMap; 031import java.util.Map; 032 033import com.unboundid.util.ByteStringBuffer; 034import com.unboundid.util.Debug; 035import com.unboundid.util.StaticUtils; 036import com.unboundid.util.ThreadSafety; 037import com.unboundid.util.ThreadSafetyLevel; 038 039import static com.unboundid.util.json.JSONMessages.*; 040 041 042 043/** 044 * This class provides a mechanism for reading JSON objects from an input 045 * stream. It assumes that any non-ASCII data that may be read from the input 046 * stream is encoded as UTF-8. 047 */ 048@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 049public final class JSONObjectReader 050 implements Closeable 051{ 052 // The buffer used to hold the bytes of the object currently being read. 053 private final ByteStringBuffer currentObjectBytes; 054 055 // A buffer to use to hold strings being decoded. 056 private final ByteStringBuffer stringBuffer; 057 058 // The input stream from which JSON objects will be read. 059 private final InputStream inputStream; 060 061 062 063 /** 064 * Creates a new JSON object reader that will read objects from the provided 065 * input stream. 066 * 067 * @param inputStream The input stream from which the data should be read. 068 */ 069 public JSONObjectReader(final InputStream inputStream) 070 { 071 this(inputStream, true); 072 } 073 074 075 076 /** 077 * Creates a new JSON object reader that will read objects from the provided 078 * input stream. 079 * 080 * @param inputStream The input stream from which the data should be 081 * read. 082 * @param bufferInputStream Indicates whether to buffer the input stream. 083 * This should be {@code false} if the input stream 084 * could be used for any purpose other than reading 085 * JSON objects after one or more objects are read. 086 */ 087 public JSONObjectReader(final InputStream inputStream, 088 final boolean bufferInputStream) 089 { 090 if (bufferInputStream && (! (inputStream instanceof BufferedInputStream))) 091 { 092 this.inputStream = new BufferedInputStream(inputStream); 093 } 094 else 095 { 096 this.inputStream = inputStream; 097 } 098 099 currentObjectBytes = new ByteStringBuffer(); 100 stringBuffer = new ByteStringBuffer(); 101 } 102 103 104 105 /** 106 * Reads the next JSON object from the input stream. 107 * 108 * @return The JSON object that was read, or {@code null} if the end of the 109 * end of the stream has been reached.. 110 * 111 * @throws IOException If a problem is encountered while reading from the 112 * input stream. 113 * 114 * @throws JSONException If the data read 115 */ 116 public JSONObject readObject() 117 throws IOException, JSONException 118 { 119 // Skip over any whitespace before the beginning of the next object. 120 skipWhitespace(); 121 currentObjectBytes.clear(); 122 123 124 // The JSON object must start with an open curly brace. 125 final Object firstToken = readToken(true); 126 if (firstToken == null) 127 { 128 return null; 129 } 130 131 if (! firstToken.equals('{')) 132 { 133 throw new JSONException(ERR_OBJECT_READER_ILLEGAL_START_OF_OBJECT.get( 134 String.valueOf(firstToken))); 135 } 136 137 final LinkedHashMap<String,JSONValue> m = 138 new LinkedHashMap<>(StaticUtils.computeMapCapacity(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(StaticUtils.toUTF8String(charBytes)); 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 StaticUtils.toUTF8String(currentObjectBytes.getBackingArray(), 557 jsonStringStartPos, 558 (currentObjectBytes.length() - jsonStringStartPos))); 559 } 560 561 final int byteReadInt = (byteRead & 0xFF); 562 if ((byteRead & 0xFF) <= 0x1F) 563 { 564 throw new JSONException(ERR_OBJECT_READER_UNESCAPED_CONTROL_CHAR.get( 565 currentObjectBytes.length(), byteToCharString(byteRead))); 566 } 567 else 568 { 569 stringBuffer.append((char) byteReadInt); 570 } 571 } 572 } 573 574 575 576 /** 577 * Reads a JSON Boolean from the input stream. The first byte of either 't' 578 * or 'f' will have already been read. 579 * 580 * @return The JSON Boolean that was read. 581 * 582 * @throws IOException If a problem is encountered while reading from the 583 * input stream. 584 * 585 * @throws JSONException If a problem was encountered while reading the JSON 586 * Boolean. 587 */ 588 private JSONBoolean readBoolean() 589 throws IOException, JSONException 590 { 591 final byte firstByte = 592 currentObjectBytes.getBackingArray()[currentObjectBytes.length() - 1]; 593 if (firstByte == 't') 594 { 595 if ((readByte(false) == 'r') && 596 (readByte(false) == 'u') && 597 (readByte(false) == 'e')) 598 { 599 return JSONBoolean.TRUE; 600 } 601 602 throw new JSONException(ERR_OBJECT_READER_INVALID_BOOLEAN_TRUE.get( 603 currentObjectBytes.length())); 604 } 605 else 606 { 607 if ((readByte(false) == 'a') && 608 (readByte(false) == 'l') && 609 (readByte(false) == 's') && 610 (readByte(false) == 'e')) 611 { 612 return JSONBoolean.FALSE; 613 } 614 615 throw new JSONException(ERR_OBJECT_READER_INVALID_BOOLEAN_FALSE.get( 616 currentObjectBytes.length())); 617 } 618 } 619 620 621 622 /** 623 * Reads a JSON Boolean from the input stream. The first byte of 'n' will 624 * have already been read. 625 * 626 * @return The JSON null that was read. 627 * 628 * @throws IOException If a problem is encountered while reading from the 629 * input stream. 630 * 631 * @throws JSONException If a problem was encountered while reading the JSON 632 * null. 633 */ 634 private JSONNull readNull() 635 throws IOException, JSONException 636 { 637 if ((readByte(false) == 'u') && 638 (readByte(false) == 'l') && 639 (readByte(false) == 'l')) 640 { 641 return JSONNull.NULL; 642 } 643 644 throw new JSONException(ERR_OBJECT_READER_INVALID_NULL.get( 645 currentObjectBytes.length())); 646 } 647 648 649 650 /** 651 * Reads a JSON number from the input stream. The first byte of the number 652 * will have already been read. 653 * 654 * @throws IOException If a problem is encountered while reading from the 655 * input stream. 656 * 657 * @return The JSON number that was read. 658 * 659 * @throws IOException If a problem is encountered while reading from the 660 * input stream. 661 * 662 * @throws JSONException If a problem was encountered while reading the JSON 663 * number. 664 */ 665 private JSONNumber readNumber() 666 throws IOException, JSONException 667 { 668 // Use a buffer to hold the string representation of the number being 669 // decoded. Since the first byte of the number has already been read, we'll 670 // need to add it into the buffer. 671 stringBuffer.clear(); 672 stringBuffer.append( 673 currentObjectBytes.getBackingArray()[currentObjectBytes.length() - 1]); 674 675 676 // Read until we encounter whitespace, a comma, a closing square bracket, or 677 // a closing curly brace. Then try to parse what we read as a number. 678 while (true) 679 { 680 // Mark the stream so that if we read a byte that isn't part of the 681 // number, we'll be able to rewind the stream so that byte will be read 682 // again by something else. 683 inputStream.mark(1); 684 685 final Byte b = readByte(false); 686 switch (b) 687 { 688 case ' ': 689 case '\t': 690 case '\n': 691 case '\r': 692 case ',': 693 case ']': 694 case '}': 695 // This tell us we're at the end of the number. Rewind the stream so 696 // that we can read this last byte again whatever tries to get the 697 // next token. Also remove it from the end of currentObjectBytes 698 // since it will be re-added when it's read again. 699 inputStream.reset(); 700 currentObjectBytes.setLength(currentObjectBytes.length() - 1); 701 return new JSONNumber(stringBuffer.toString()); 702 703 default: 704 stringBuffer.append(b); 705 } 706 } 707 } 708 709 710 711 /** 712 * Reads a JSON array from the input stream. The opening square bracket will 713 * have already been read. 714 * 715 * @return The JSON array that was read. 716 * 717 * @throws IOException If a problem is encountered while reading from the 718 * input stream. 719 * 720 * @throws JSONException If a problem was encountered while reading the JSON 721 * array. 722 */ 723 private JSONArray readArray() 724 throws IOException, JSONException 725 { 726 // The opening square bracket will have already been consumed, so read 727 // JSON values until we hit a closing square bracket. 728 final ArrayList<JSONValue> values = new ArrayList<>(10); 729 boolean firstToken = true; 730 while (true) 731 { 732 // If this is the first time through, it is acceptable to find a closing 733 // square bracket. Otherwise, we expect to find a JSON value, an opening 734 // square bracket to denote the start of an embedded array, or an opening 735 // curly brace to denote the start of an embedded JSON object. 736 final Object token = readToken(false); 737 if (token instanceof JSONValue) 738 { 739 values.add((JSONValue) token); 740 } 741 else if (token.equals('[')) 742 { 743 values.add(readArray()); 744 } 745 else if (token.equals('{')) 746 { 747 final LinkedHashMap<String,JSONValue> fieldMap = 748 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 749 values.add(readObject(fieldMap)); 750 } 751 else if (token.equals(']') && firstToken) 752 { 753 // It's an empty array. 754 return JSONArray.EMPTY_ARRAY; 755 } 756 else 757 { 758 throw new JSONException(ERR_OBJECT_READER_INVALID_TOKEN_IN_ARRAY.get( 759 currentObjectBytes.length(), String.valueOf(token))); 760 } 761 762 firstToken = false; 763 764 765 // If we've gotten here, then we found a JSON value. It must be followed 766 // by either a comma (to indicate that there's at least one more value) or 767 // a closing square bracket (to denote the end of the array). 768 final Object nextToken = readToken(false); 769 if (nextToken.equals(']')) 770 { 771 return new JSONArray(values); 772 } 773 else if (! nextToken.equals(',')) 774 { 775 throw new JSONException( 776 ERR_OBJECT_READER_INVALID_TOKEN_AFTER_ARRAY_VALUE.get( 777 currentObjectBytes.length(), String.valueOf(nextToken))); 778 } 779 } 780 } 781 782 783 784 /** 785 * Reads a JSON object from the input stream. The opening curly brace will 786 * have already been read. 787 * 788 * @param fields The map into which to place the fields that are read. The 789 * returned object will include an unmodifiable view of this 790 * map, but the caller may use the map directly if desired. 791 * 792 * @return The JSON object that was read. 793 * 794 * @throws IOException If a problem is encountered while reading from the 795 * input stream. 796 * 797 * @throws JSONException If a problem was encountered while reading the JSON 798 * object. 799 */ 800 private JSONObject readObject(final Map<String,JSONValue> fields) 801 throws IOException, JSONException 802 { 803 boolean firstField = true; 804 while (true) 805 { 806 // Read the next token. It must be a JSONString, unless we haven't read 807 // any fields yet in which case it can be a closing curly brace to 808 // indicate that it's an empty object. 809 final String fieldName; 810 final Object fieldNameToken = readToken(false); 811 if (fieldNameToken instanceof JSONString) 812 { 813 fieldName = ((JSONString) fieldNameToken).stringValue(); 814 if (fields.containsKey(fieldName)) 815 { 816 throw new JSONException(ERR_OBJECT_READER_DUPLICATE_FIELD.get( 817 currentObjectBytes.length(), fieldName)); 818 } 819 } 820 else if (firstField && fieldNameToken.equals('}')) 821 { 822 return new JSONObject(fields); 823 } 824 else 825 { 826 throw new JSONException(ERR_OBJECT_READER_INVALID_TOKEN_IN_OBJECT.get( 827 currentObjectBytes.length(), String.valueOf(fieldNameToken))); 828 } 829 firstField = false; 830 831 // Read the next token. It must be a colon. 832 final Object colonToken = readToken(false); 833 if (! colonToken.equals(':')) 834 { 835 throw new JSONException(ERR_OBJECT_READER_TOKEN_NOT_COLON.get( 836 currentObjectBytes.length(), String.valueOf(colonToken), 837 String.valueOf(fieldNameToken))); 838 } 839 840 // Read the next token. It must be one of the following: 841 // - A JSONValue 842 // - An opening square bracket, designating the start of an array. 843 // - An opening curly brace, designating the start of an object. 844 final Object valueToken = readToken(false); 845 if (valueToken instanceof JSONValue) 846 { 847 fields.put(fieldName, (JSONValue) valueToken); 848 } 849 else if (valueToken.equals('[')) 850 { 851 final JSONArray a = readArray(); 852 fields.put(fieldName, a); 853 } 854 else if (valueToken.equals('{')) 855 { 856 final LinkedHashMap<String,JSONValue> m = 857 new LinkedHashMap<>(StaticUtils.computeMapCapacity(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}