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