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