1 package com.softmotions.ejdb2; 2 3 import java.io.IOException; 4 import java.io.StringWriter; 5 import java.io.UnsupportedEncodingException; 6 import java.io.Writer; 7 import java.net.URLDecoder; 8 import java.util.ArrayList; 9 import java.util.Collections; 10 import java.util.Iterator; 11 import java.util.LinkedHashMap; 12 import java.util.List; 13 import java.util.Map; 14 import java.util.Map.Entry; 15 import java.util.Objects; 16 17 /** 18 * JSON parser/container. 19 * 20 * Based on: 21 * 22 * - https://github.com/json-iterator (MIT) 23 * 24 * - https://github.com/ralfstx/minimal-json (MIT) 25 */ 26 public final class JSON { 27 buildObject()28 public static ObjectBuilder buildObject() { 29 return new ObjectBuilder(); 30 } 31 buildArray()32 public static ArrayBuilder buildArray() { 33 return new ArrayBuilder(); 34 } 35 fromString(String val)36 public static JSON fromString(String val) { 37 return new JSON(val); 38 } 39 fromBytes(byte[] bytes)40 public static JSON fromBytes(byte[] bytes) { 41 return new JSON(bytes); 42 } 43 fromMap(Map<String, Object> map)44 public static JSON fromMap(Map<String, Object> map) { 45 return new JSON(ValueType.OBJECT, map); 46 } 47 fromList(List<Object> list)48 public static JSON fromList(List<Object> list) { 49 return new JSON(ValueType.ARRAY, list); 50 } 51 52 private static final ValueType[] valueTypes = new ValueType[256]; 53 private static final int[] hexDigits = new int['f' + 1]; 54 private static JSON UNKNOWN = new JSON(ValueType.UNKNOWN, null); 55 56 static { 57 for (int i = 0; i < valueTypes.length; ++i) { 58 valueTypes[i] = ValueType.UNKNOWN; 59 } 60 valueTypes['"'] = ValueType.STRING; 61 valueTypes['-'] = ValueType.NUMBER; 62 valueTypes['0'] = ValueType.NUMBER; 63 valueTypes['1'] = ValueType.NUMBER; 64 valueTypes['2'] = ValueType.NUMBER; 65 valueTypes['3'] = ValueType.NUMBER; 66 valueTypes['4'] = ValueType.NUMBER; 67 valueTypes['5'] = ValueType.NUMBER; 68 valueTypes['6'] = ValueType.NUMBER; 69 valueTypes['7'] = ValueType.NUMBER; 70 valueTypes['8'] = ValueType.NUMBER; 71 valueTypes['9'] = ValueType.NUMBER; 72 valueTypes['t'] = ValueType.BOOLEAN; 73 valueTypes['f'] = ValueType.BOOLEAN; 74 valueTypes['n'] = ValueType.NULL; 75 valueTypes['['] = ValueType.ARRAY; 76 valueTypes['{'] = ValueType.OBJECT; 77 } 78 79 static { 80 for (int i = 0; i < hexDigits.length; ++i) { 81 hexDigits[i] = -1; 82 } 83 for (int i = '0'; i <= '9'; ++i) { 84 hexDigits[i] = (i - '0'); 85 } 86 for (int i = 'a'; i <= 'f'; ++i) { 87 hexDigits[i] = ((i - 'a') + 10); 88 } 89 for (int i = 'A'; i <= 'F'; ++i) { 90 hexDigits[i] = ((i - 'A') + 10); 91 } 92 } 93 94 public final Object value; 95 public final ValueType type; 96 97 private char[] reusableChars = new char[32]; 98 private byte[] buf = new byte[0]; 99 private int head; 100 private int tail; 101 JSON(byte[] buf)102 JSON(byte[] buf) { 103 this.buf = buf; 104 tail = buf.length; 105 type = whatIsNext(); 106 value = read(type); 107 reset(); 108 } 109 JSON(String data)110 JSON(String data) { 111 this(data.getBytes()); 112 } 113 JSON(ValueType type, Object value)114 private JSON(ValueType type, Object value) { 115 this.type = type; 116 this.value = value; 117 } 118 write(Writer w)119 public void write(Writer w) { 120 if (type != ValueType.UNKNOWN) { 121 writeTo(w, value); 122 } 123 } 124 125 @Override toString()126 public String toString() { 127 StringWriter sw = new StringWriter(); 128 write(sw); 129 return sw.toString(); 130 } 131 isUnknown()132 public boolean isUnknown() { 133 return type == ValueType.UNKNOWN; 134 } 135 isNull()136 public boolean isNull() { 137 return type == ValueType.NULL; 138 } 139 isNumber()140 public boolean isNumber() { 141 return type == ValueType.NUMBER; 142 } 143 isBoolean()144 public boolean isBoolean() { 145 return type == ValueType.BOOLEAN; 146 } 147 isString()148 public boolean isString() { 149 return type == ValueType.STRING; 150 } 151 isObject()152 public boolean isObject() { 153 return type == ValueType.OBJECT; 154 } 155 isArray()156 public boolean isArray() { 157 return type == ValueType.ARRAY; 158 } 159 modify()160 public Builder modify() { 161 return new Builder(this); 162 } 163 get(String key)164 public JSON get(String key) { 165 if (type == ValueType.ARRAY) { 166 try { 167 return get(Integer.parseInt(key)); 168 } catch (NumberFormatException ignored) { 169 return UNKNOWN; 170 } 171 } 172 if (type != ValueType.OBJECT) { 173 return UNKNOWN; 174 } 175 Object v = ((Map<String, Object>) value).get(key); 176 return new JSON(ValueType.getTypeOf(v), v); 177 } 178 get(int index)179 public JSON get(int index) { 180 if (type == ValueType.OBJECT) { 181 return get(String.valueOf(index)); 182 } 183 if (type != ValueType.ARRAY) { 184 return UNKNOWN; 185 } 186 List<Object> list = (List<Object>) value; 187 if (index < 0 || index >= list.size()) { 188 return UNKNOWN; 189 } 190 Object v = list.get(index); 191 return new JSON(ValueType.getTypeOf(v), v); 192 } 193 cast()194 public <T> T cast() { 195 return (T) value; 196 } 197 orDefault(T defaultValue)198 public <T> T orDefault(T defaultValue) { 199 return type != ValueType.UNKNOWN ? (T) value : defaultValue; 200 } 201 orDefaultNotNull(T defaultValue)202 public <T> T orDefaultNotNull(T defaultValue) { 203 return type != ValueType.UNKNOWN && type != ValueType.NULL ? (T) value : defaultValue; 204 } 205 asStringOr(String fallbackValue)206 public String asStringOr(String fallbackValue) { 207 return type == ValueType.STRING ? (String) value : fallbackValue; 208 } 209 asString()210 public String asString() { 211 return asStringOr(null); 212 } 213 asTextOr(String fallbackValue)214 public String asTextOr(String fallbackValue) { 215 if (type == ValueType.UNKNOWN) { 216 return fallbackValue; 217 } else { 218 return String.valueOf(value); 219 } 220 } 221 asText()222 public String asText() { 223 return asTextOr(null); 224 } 225 asBooleanOr(Boolean fallbackValue)226 public Boolean asBooleanOr(Boolean fallbackValue) { 227 return type == ValueType.BOOLEAN ? (Boolean) value : fallbackValue; 228 } 229 asBoolean()230 public Boolean asBoolean() { 231 return asBooleanOr(null); 232 } 233 asNumberOr(Number fallbackValue)234 public Number asNumberOr(Number fallbackValue) { 235 return type == ValueType.NUMBER ? (Number) value : fallbackValue; 236 } 237 asNumber()238 public Number asNumber() { 239 return asNumberOr(null); 240 } 241 asIntegerOr(Integer fallbackValue)242 public Integer asIntegerOr(Integer fallbackValue) { 243 Number n = asNumberOr(fallbackValue); 244 return n != null ? n.intValue() : null; 245 } 246 asInteger()247 public Integer asInteger() { 248 return asIntegerOr(null); 249 } 250 asMapOr(Map<String, Object> fallbackValue)251 public Map<String, Object> asMapOr(Map<String, Object> fallbackValue) { 252 return type == ValueType.OBJECT ? (Map<String, Object>) value : fallbackValue; 253 } 254 asMapOrEmpty()255 public Map<String, Object> asMapOrEmpty() { 256 return asMapOr(Collections.emptyMap()); 257 } 258 asListOr(List<Object> fallbackValue)259 public List<Object> asListOr(List<Object> fallbackValue) { 260 return type == ValueType.ARRAY ? (List<Object>) value : fallbackValue; 261 } 262 asListOrEmpty()263 public List<Object> asListOrEmpty() { 264 return asListOr(Collections.emptyList()); 265 } 266 asKnownJsonOr(JSON fallbackValue)267 public JSON asKnownJsonOr(JSON fallbackValue) { 268 return type == ValueType.UNKNOWN ? fallbackValue : this; 269 } 270 asKnownJson()271 public JSON asKnownJson() { 272 return asKnownJsonOr(null); 273 } 274 at(String pointer)275 public JSON at(String pointer) { 276 if (type != ValueType.OBJECT) { 277 return UNKNOWN; 278 } 279 if (pointer == null || "/".equals(pointer) || pointer.isEmpty()) { 280 return this; 281 } 282 return traverse(this, createPointer(pointer)); 283 } 284 traverse(JSON obj, List<String> pp)285 private JSON traverse(JSON obj, List<String> pp) { 286 if (pp.isEmpty() || obj.isUnknown()) { 287 return obj; 288 } 289 String key = pp.remove(0); 290 if (obj.isObject() || obj.isArray()) { 291 return traverse(obj.get(key), pp); 292 } else { 293 return UNKNOWN; 294 } 295 } 296 createPointer(String pointer)297 private List<String> createPointer(String pointer) { 298 if (pointer.charAt(0) == '#') { 299 try { 300 pointer = URLDecoder.decode(pointer, "UTF_8"); 301 } catch (UnsupportedEncodingException e) { 302 throw new JSONException(e); 303 } 304 } 305 if (pointer.isEmpty() || pointer.charAt(0) != '/') { 306 throw new JSONException("Invalid JSON pointer: " + pointer); 307 } 308 String[] parts = pointer.substring(1).split("/"); 309 ArrayList<String> res = new ArrayList<>(parts.length); 310 311 for (int i = 0, l = parts.length; i < l; ++i) { 312 while (parts[i].contains("~1")) { 313 parts[i] = parts[i].replace("~1", "/"); 314 } 315 while (parts[i].contains("~0")) { 316 parts[i] = parts[i].replace("~0", "~"); 317 } 318 res.add(parts[i]); 319 } 320 return res; 321 } 322 writeTo(Writer w, Object val)323 private static void writeTo(Writer w, Object val) { 324 try { 325 write(new JsonWriter(w), val); 326 } catch (IOException e) { 327 throw new JSONException(e); 328 } 329 } 330 write(JsonWriter w, Object val)331 private static void write(JsonWriter w, Object val) throws IOException { 332 if (val == null) { 333 w.writeLiteral("null"); 334 return; 335 } 336 final Class clazz = val.getClass(); 337 if (clazz == String.class) { 338 w.writeString((String) val); 339 return; 340 } else if (clazz == Boolean.class) { 341 Boolean bv = (Boolean) val; 342 w.writeLiteral(bv ? "true" : "false"); 343 return; 344 } 345 if (val instanceof Map) { 346 final Map<String, Object> map = (Map<String, Object>) val; 347 Iterator<Entry<String, Object>> iter = map.entrySet().iterator(); 348 w.writeObjectOpen(); 349 if (iter.hasNext()) { 350 Entry<String, Object> v = iter.next(); 351 w.writeMemberName(v.getKey()); 352 w.writeMemberSeparator(); 353 write(w, v.getValue()); 354 while (iter.hasNext()) { 355 v = iter.next(); 356 w.writeObjectSeparator(); 357 w.writeMemberName(v.getKey()); 358 w.writeMemberSeparator(); 359 write(w, v.getValue()); 360 } 361 } 362 w.writeObjectClose(); 363 } else if (val instanceof List) { 364 final List<Object> list = (List<Object>) val; 365 w.writeArrayOpen(); 366 Iterator<Object> iter = list.iterator(); 367 if (iter.hasNext()) { 368 write(w, iter.next()); 369 while (iter.hasNext()) { 370 w.writeArraySeparator(); 371 write(w, iter.next()); 372 } 373 } 374 w.writeArrayClose(); 375 } else if (val instanceof Number) { 376 w.writeNumber((Number) val); 377 } else { 378 w.writeString(val.toString()); 379 } 380 } 381 reset()382 private void reset() { 383 buf = null; 384 head = 0; 385 tail = 0; 386 } 387 read(ValueType valueType)388 private Object read(ValueType valueType) { 389 try { 390 switch (valueType) { 391 case STRING: 392 return readString(); 393 case NUMBER: 394 return readNumber(); 395 case NULL: 396 head += 4; 397 return null; 398 case BOOLEAN: 399 return readBoolean(); 400 case ARRAY: 401 return readArray(new ArrayList<Object>(4)); 402 case OBJECT: 403 return readObject(new LinkedHashMap<String, Object>(4)); 404 default: 405 throw reportError("read", "unexpected value type: " + valueType); 406 } 407 } catch (ArrayIndexOutOfBoundsException e) { 408 throw reportError("read", "premature end"); 409 } 410 } 411 read()412 private Object read() { 413 return read(whatIsNext()); 414 } 415 readObject(Map<String, Object> map)416 private Map<String, Object> readObject(Map<String, Object> map) { 417 byte c = nextToken(); 418 if ('{' == c) { 419 c = nextToken(); 420 if ('"' == c) { 421 unreadByte(); 422 String field = readString(); 423 if (nextToken() != ':') { 424 throw reportError("readObject", "expect :"); 425 } 426 map.put(field, read()); 427 while (nextToken() == ',') { 428 field = readString(); 429 if (nextToken() != ':') { 430 throw reportError("readObject", "expect :"); 431 } 432 map.put(field, read()); 433 } 434 return map; 435 } 436 if ('}' == c) { 437 return map; 438 } 439 throw reportError("readObject", "expect \" after {"); 440 } 441 if ('n' == c) { 442 head += 3; 443 return map; 444 } 445 throw reportError("readObject", "expect { or n"); 446 } 447 readArray(ArrayList<Object> list)448 private ArrayList<Object> readArray(ArrayList<Object> list) { 449 byte c = nextToken(); 450 if (c == '[') { 451 c = nextToken(); 452 if (c != ']') { 453 unreadByte(); 454 list.add(read()); 455 while (nextToken() == ',') { 456 list.add(read()); 457 } 458 return list; 459 } 460 return list; 461 } 462 if (c == 'n') { 463 return list; 464 } 465 throw reportError("readArray", "expect [ or n, but found: " + (char) c); 466 } 467 readBoolean()468 private boolean readBoolean() { 469 byte c = nextToken(); 470 if ('t' == c) { 471 head += 3; // true 472 return true; 473 } 474 if ('f' == c) { 475 head += 4; // false 476 return false; 477 } 478 throw reportError("readBoolean", "expect t or f, found: " + c); 479 } 480 readNumber()481 private Object readNumber() { 482 NumberChars numberChars = readNumberImpl(); 483 String numberStr = new String(numberChars.chars, 0, numberChars.charsLength); 484 Double number = Double.valueOf(numberStr); 485 if (numberChars.dotFound) { 486 return number; 487 } 488 double doubleNumber = number; 489 if (doubleNumber == Math.floor(doubleNumber) && !Double.isInfinite(doubleNumber)) { 490 long longNumber = Long.valueOf(numberStr); 491 if (longNumber <= Integer.MAX_VALUE && longNumber >= Integer.MIN_VALUE) { 492 return (int) longNumber; 493 } 494 return longNumber; 495 } 496 return number; 497 } 498 readNumberImpl()499 private NumberChars readNumberImpl() { 500 int j = 0; 501 boolean dotFound = false; 502 for (;;) { 503 for (int i = head; i < tail; ++i) { 504 if (j == reusableChars.length) { 505 char[] newBuf = new char[reusableChars.length * 2]; 506 System.arraycopy(reusableChars, 0, newBuf, 0, reusableChars.length); 507 reusableChars = newBuf; 508 } 509 byte c = buf[i]; 510 switch (c) { 511 case '.': 512 case 'e': 513 case 'E': 514 dotFound = true; 515 // fallthrough 516 case '-': 517 case '+': 518 case '0': 519 case '1': 520 case '2': 521 case '3': 522 case '4': 523 case '5': 524 case '6': 525 case '7': 526 case '8': 527 case '9': 528 reusableChars[j++] = (char) c; 529 break; 530 default: 531 head = i; 532 NumberChars numberChars = new NumberChars(); 533 numberChars.chars = reusableChars; 534 numberChars.charsLength = j; 535 numberChars.dotFound = dotFound; 536 return numberChars; 537 } 538 } 539 540 head = tail; 541 NumberChars numberChars = new NumberChars(); 542 numberChars.chars = reusableChars; 543 numberChars.charsLength = j; 544 numberChars.dotFound = dotFound; 545 return numberChars; 546 } 547 } 548 readString()549 private String readString() { 550 byte c = nextToken(); 551 if (c != '"') { 552 if (c == 'n') { 553 head += 3; 554 return null; 555 } 556 throw reportError("readString", "expect string or null, but " + (char) c); 557 } 558 int j = parseString(); 559 return new String(reusableChars, 0, j); 560 } 561 parseString()562 private int parseString() { 563 byte c; 564 int i = head; 565 int bound = reusableChars.length; 566 for (int j = 0; j < bound; ++j) { 567 c = buf[i++]; 568 if (c == '"') { 569 head = i; 570 return j; 571 } 572 if ((c ^ '\\') < 1) { 573 break; 574 } 575 reusableChars[j] = (char) c; 576 } 577 int alreadyCopied = 0; 578 if (i > head) { 579 alreadyCopied = i - head - 1; 580 head = i - 1; 581 } 582 return readStringSlowPath(alreadyCopied); 583 } 584 translateHex(byte b)585 private int translateHex(byte b) { 586 int val = hexDigits[b]; 587 if (val == -1) { 588 throw new IndexOutOfBoundsException(b + " is not valid hex digit"); 589 } 590 return val; 591 } 592 readStringSlowPath(int j)593 private int readStringSlowPath(int j) { 594 try { 595 boolean isExpectingLowSurrogate = false; 596 for (int i = head; i < tail;) { 597 int bc = buf[i++]; 598 if (bc == '"') { 599 head = i; 600 return j; 601 } 602 if (bc == '\\') { 603 bc = buf[i++]; 604 switch (bc) { 605 case 'b': 606 bc = '\b'; 607 break; 608 case 't': 609 bc = '\t'; 610 break; 611 case 'n': 612 bc = '\n'; 613 break; 614 case 'f': 615 bc = '\f'; 616 break; 617 case 'r': 618 bc = '\r'; 619 break; 620 case '"': 621 case '/': 622 case '\\': 623 break; 624 case 'u': 625 bc = (translateHex(buf[i++]) << 12) + (translateHex(buf[i++]) << 8) + (translateHex(buf[i++]) << 4) 626 + translateHex(buf[i++]); 627 if (Character.isHighSurrogate((char) bc)) { 628 if (isExpectingLowSurrogate) { 629 throw new JSONException("invalid surrogate"); 630 } else { 631 isExpectingLowSurrogate = true; 632 } 633 } else if (Character.isLowSurrogate((char) bc)) { 634 if (isExpectingLowSurrogate) { 635 isExpectingLowSurrogate = false; 636 } else { 637 throw new JSONException("invalid surrogate"); 638 } 639 } else { 640 if (isExpectingLowSurrogate) { 641 throw new JSONException("invalid surrogate"); 642 } 643 } 644 break; 645 646 default: 647 throw reportError("readStringSlowPath", "invalid escape character: " + bc); 648 } 649 } else if ((bc & 0x80) != 0) { 650 final int u2 = buf[i++]; 651 if ((bc & 0xE0) == 0xC0) { 652 bc = ((bc & 0x1F) << 6) + (u2 & 0x3F); 653 } else { 654 final int u3 = buf[i++]; 655 if ((bc & 0xF0) == 0xE0) { 656 bc = ((bc & 0x0F) << 12) + ((u2 & 0x3F) << 6) + (u3 & 0x3F); 657 } else { 658 final int u4 = buf[i++]; 659 if ((bc & 0xF8) == 0xF0) { 660 bc = ((bc & 0x07) << 18) + ((u2 & 0x3F) << 12) + ((u3 & 0x3F) << 6) + (u4 & 0x3F); 661 } else { 662 throw reportError("readStringSlowPath", "invalid unicode character"); 663 } 664 if (bc >= 0x10000) { 665 // check if valid unicode 666 if (bc >= 0x110000) { 667 throw reportError("readStringSlowPath", "invalid unicode character"); 668 } 669 // split surrogates 670 final int sup = bc - 0x10000; 671 if (reusableChars.length == j) { 672 char[] newBuf = new char[reusableChars.length * 2]; 673 System.arraycopy(reusableChars, 0, newBuf, 0, reusableChars.length); 674 reusableChars = newBuf; 675 } 676 reusableChars[j++] = (char) ((sup >>> 10) + 0xd800); 677 if (reusableChars.length == j) { 678 char[] newBuf = new char[reusableChars.length * 2]; 679 System.arraycopy(reusableChars, 0, newBuf, 0, reusableChars.length); 680 reusableChars = newBuf; 681 } 682 reusableChars[j++] = (char) ((sup & 0x3ff) + 0xdc00); 683 continue; 684 } 685 } 686 } 687 } 688 if (reusableChars.length == j) { 689 char[] newBuf = new char[reusableChars.length * 2]; 690 System.arraycopy(reusableChars, 0, newBuf, 0, reusableChars.length); 691 reusableChars = newBuf; 692 } 693 reusableChars[j++] = (char) bc; 694 } 695 throw reportError("readStringSlowPath", "incomplete string"); 696 } catch (IndexOutOfBoundsException e) { 697 throw reportError("readString", "incomplete string"); 698 } 699 } 700 whatIsNext()701 private ValueType whatIsNext() { 702 ValueType valueType = valueTypes[nextToken()]; 703 unreadByte(); 704 return valueType; 705 } 706 unreadByte()707 private void unreadByte() { 708 if (head == 0) { 709 throw reportError("unreadByte", "unread too many bytes"); 710 } 711 head--; 712 } 713 nextToken()714 private byte nextToken() { 715 int i = head; 716 for (;;) { 717 byte c = buf[i++]; 718 switch (c) { 719 case ' ': 720 case '\n': 721 case '\r': 722 case '\t': 723 continue; 724 default: 725 head = i; 726 return c; 727 } 728 } 729 } 730 reportError(String op, String msg)731 private JSONException reportError(String op, String msg) { 732 int peekStart = head - 10; 733 if (peekStart < 0) { 734 peekStart = 0; 735 } 736 int peekSize = head - peekStart; 737 if (head > tail) { 738 peekSize = tail - peekStart; 739 } 740 String peek = new String(buf, peekStart, peekSize); 741 throw new JSONException(op + ": " + msg + ", head: " + head + ", peek: " + peek + ", buf: " + new String(buf)); 742 } 743 744 @Override hashCode()745 public int hashCode() { 746 return Objects.hash(type, value); 747 } 748 749 @Override equals(Object obj)750 public boolean equals(Object obj) { 751 if (this == obj) { 752 return true; 753 } 754 if (obj == null) { 755 return false; 756 } 757 if (getClass() != obj.getClass()) { 758 return false; 759 } 760 JSON other = (JSON) obj; 761 return type == other.type && Objects.equals(value, other.value); 762 } 763 764 public static enum ValueType { 765 UNKNOWN, STRING, NUMBER, NULL, BOOLEAN, ARRAY, OBJECT; 766 getTypeOf(Object v)767 static ValueType getTypeOf(Object v) { 768 if (v == null) { 769 return NULL; 770 } 771 Class clazz = v.getClass(); 772 if (clazz == String.class) { 773 return STRING; 774 } 775 if (clazz == Boolean.class) { 776 return BOOLEAN; 777 } 778 if (v instanceof Number) { 779 return NUMBER; 780 } 781 if (v instanceof Map) { 782 return OBJECT; 783 } 784 if (v instanceof List) { 785 return ARRAY; 786 } 787 return UNKNOWN; 788 } 789 } 790 791 private static final class NumberChars { 792 char[] chars; 793 int charsLength; 794 boolean dotFound; 795 } 796 797 private static final class JsonWriter { 798 private static final char[] QUOT_CHARS = { '\\', '"' }; 799 private static final char[] BS_CHARS = { '\\', '\\' }; 800 private static final char[] LF_CHARS = { '\\', 'n' }; 801 private static final char[] CR_CHARS = { '\\', 'r' }; 802 private static final char[] TAB_CHARS = { '\\', 't' }; 803 804 // In JavaScript, U+2028 and U+2029 characters count as line endings and must be 805 // encoded. 806 // http://stackoverflow.com/questions/2965293/javascript-parse-error-on-u2028-unicode-character 807 private static final char[] UNICODE_2028_CHARS = { '\\', 'u', '2', '0', '2', '8' }; 808 private static final char[] UNICODE_2029_CHARS = { '\\', 'u', '2', '0', '2', '9' }; 809 private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 810 'e', 'f' }; 811 812 private final Writer writer; 813 JsonWriter(Writer writer)814 JsonWriter(Writer writer) { 815 this.writer = writer; 816 } 817 writeLiteral(String value)818 void writeLiteral(String value) throws IOException { 819 writer.write(value); 820 } 821 writeNumber(Number value)822 void writeNumber(Number value) throws IOException { 823 String s = value.toString(); 824 if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) { 825 while (s.endsWith("0")) { 826 s = s.substring(0, s.length() - 1); 827 } 828 if (s.endsWith(".")) { 829 s = s.substring(0, s.length() - 1); 830 } 831 } 832 writer.write(s); 833 } 834 writeString(String string)835 void writeString(String string) throws IOException { 836 writer.write('"'); 837 writeJsonString(string); 838 writer.write('"'); 839 } 840 writeArrayOpen()841 void writeArrayOpen() throws IOException { 842 writer.write('['); 843 } 844 writeArrayClose()845 void writeArrayClose() throws IOException { 846 writer.write(']'); 847 } 848 writeArraySeparator()849 void writeArraySeparator() throws IOException { 850 writer.write(','); 851 } 852 writeObjectOpen()853 void writeObjectOpen() throws IOException { 854 writer.write('{'); 855 } 856 writeObjectClose()857 void writeObjectClose() throws IOException { 858 writer.write('}'); 859 } 860 writeMemberName(String name)861 void writeMemberName(String name) throws IOException { 862 writer.write('"'); 863 writeJsonString(name); 864 writer.write('"'); 865 } 866 writeMemberSeparator()867 void writeMemberSeparator() throws IOException { 868 writer.write(':'); 869 } 870 writeObjectSeparator()871 void writeObjectSeparator() throws IOException { 872 writer.write(','); 873 } 874 writeJsonString(String string)875 void writeJsonString(String string) throws IOException { 876 int length = string.length(); 877 int start = 0; 878 for (int index = 0; index < length; ++index) { 879 char[] replacement = getReplacementChars(string.charAt(index)); 880 if (replacement != null) { 881 writer.write(string, start, index - start); 882 writer.write(replacement); 883 start = index + 1; 884 } 885 } 886 writer.write(string, start, length - start); 887 } 888 getReplacementChars(char ch)889 static char[] getReplacementChars(char ch) { 890 if (ch > '\\') { 891 if (ch < '\u2028' || ch > '\u2029') { 892 // The lower range contains 'a' .. 'z'. Only 2 checks required. 893 return null; 894 } 895 return ch == '\u2028' ? UNICODE_2028_CHARS : UNICODE_2029_CHARS; 896 } 897 if (ch == '\\') { 898 return BS_CHARS; 899 } 900 if (ch > '"') { 901 // This range contains '0' .. '9' and 'A' .. 'Z'. Need 3 checks to get here. 902 return null; 903 } 904 if (ch == '"') { 905 return QUOT_CHARS; 906 } 907 if (ch > 0x001f) { // CONTROL_CHARACTERS_END 908 return null; 909 } 910 if (ch == '\n') { 911 return LF_CHARS; 912 } 913 if (ch == '\r') { 914 return CR_CHARS; 915 } 916 if (ch == '\t') { 917 return TAB_CHARS; 918 } 919 return new char[] { '\\', 'u', '0', '0', HEX_DIGITS[ch >> 4 & 0x000f], HEX_DIGITS[ch & 0x000f] }; 920 } 921 } 922 923 public static final class Builder { 924 final JSON json; 925 final ObjectBuilder o; 926 final ArrayBuilder a; 927 Builder(JSON json)928 Builder(JSON json) { 929 this.json = json; 930 if (json.type == ValueType.OBJECT) { 931 o = new ObjectBuilder((Map<String, Object>) json.value); 932 a = null; 933 } else if (json.type == ValueType.ARRAY) { 934 a = new ArrayBuilder((List<Object>) json.value); 935 o = null; 936 } else { 937 throw new JSONException("JSON value must be either Object or Array"); 938 } 939 } 940 addObject()941 public ObjectBuilder addObject() { 942 return getA().addObject(); 943 } 944 addObject(JSON val)945 public ArrayBuilder addObject(JSON val) { 946 return getA().addObject(val); 947 } 948 addArray()949 public ArrayBuilder addArray() { 950 return getA().addArray(); 951 } 952 addArray(JSON val)953 public ArrayBuilder addArray(JSON val) { 954 return getA().addArray(val); 955 } 956 add(String val)957 public ArrayBuilder add(String val) { 958 return getA().add(val); 959 } 960 add(Number val)961 public ArrayBuilder add(Number val) { 962 return getA().add(val); 963 } 964 add(Boolean val)965 public ArrayBuilder add(Boolean val) { 966 return getA().add(val); 967 } 968 addNull()969 public ArrayBuilder addNull() { 970 return getA().addNull(); 971 } 972 length()973 public int length() { 974 return getA().length(); 975 } 976 get(int index)977 public Object get(int index) { 978 return getA().get(index); 979 } 980 delete(int index)981 public ArrayBuilder delete(int index) { 982 return getA().delete(index); 983 } 984 putObject(String key)985 public ObjectBuilder putObject(String key) { 986 return getO().putObject(key); 987 } 988 putObject(String key, JSON val)989 public ObjectBuilder putObject(String key, JSON val) { 990 return getO().putObject(key, val); 991 } 992 putArray(String key)993 public ArrayBuilder putArray(String key) { 994 return getO().putArray(key); 995 } 996 putArray(String key, JSON val)997 public ObjectBuilder putArray(String key, JSON val) { 998 return getO().putArray(key, val); 999 } 1000 put(String key, String val)1001 public ObjectBuilder put(String key, String val) { 1002 return getO().put(key, val); 1003 } 1004 put(String key, Number val)1005 public ObjectBuilder put(String key, Number val) { 1006 return getO().put(key, val); 1007 } 1008 put(String key, Boolean val)1009 public ObjectBuilder put(String key, Boolean val) { 1010 return getO().put(key, val); 1011 } 1012 putNull(String key)1013 public ObjectBuilder putNull(String key) { 1014 return getO().putNull(key); 1015 } 1016 delete(String key)1017 public ObjectBuilder delete(String key) { 1018 return getO().delete(key); 1019 } 1020 keys()1021 public Iterable<String> keys() { 1022 return getO().keys(); 1023 } 1024 toJSON()1025 public JSON toJSON() { 1026 return json; 1027 } 1028 1029 @Override toString()1030 public String toString() { 1031 return json.toString(); 1032 } 1033 getA()1034 private ArrayBuilder getA() { 1035 if (a == null) { 1036 throw new JSONException("JSON value is not an Array"); 1037 } 1038 return a; 1039 } 1040 getO()1041 private ObjectBuilder getO() { 1042 if (o == null) { 1043 throw new JSONException("JSON value is not an Object"); 1044 } 1045 return o; 1046 } 1047 } 1048 1049 public static final class ArrayBuilder { 1050 1051 final List<Object> value; 1052 1053 private JSON json; 1054 ArrayBuilder(List<Object> value)1055 ArrayBuilder(List<Object> value) { 1056 this.value = value; 1057 } 1058 ArrayBuilder()1059 ArrayBuilder() { 1060 this(new ArrayList<>()); 1061 } 1062 addObject()1063 public ObjectBuilder addObject() { 1064 ObjectBuilder b = new ObjectBuilder(); 1065 value.add(b.value); 1066 return b; 1067 } 1068 addObject(JSON val)1069 public ArrayBuilder addObject(JSON val) { 1070 if (val.type == ValueType.NULL) { 1071 return addNull(); 1072 } else if (val.type != ValueType.OBJECT) { 1073 throw new JSONException("Value must be an Object"); 1074 } 1075 value.add(val.value); 1076 return this; 1077 } 1078 addArray()1079 public ArrayBuilder addArray() { 1080 ArrayBuilder b = new ArrayBuilder(); 1081 value.add(b.value); 1082 return b; 1083 } 1084 addArray(JSON val)1085 public ArrayBuilder addArray(JSON val) { 1086 if (val.type == ValueType.NULL) { 1087 return addNull(); 1088 } else if (val.type != ValueType.ARRAY) { 1089 throw new JSONException("Value must be an Array"); 1090 } 1091 value.add(val.value); 1092 return this; 1093 } 1094 add(String val)1095 public ArrayBuilder add(String val) { 1096 value.add(val); 1097 return this; 1098 } 1099 add(Number val)1100 public ArrayBuilder add(Number val) { 1101 value.add(val); 1102 return this; 1103 } 1104 add(Boolean val)1105 public ArrayBuilder add(Boolean val) { 1106 value.add(val); 1107 return this; 1108 } 1109 addNull()1110 public ArrayBuilder addNull() { 1111 value.add(null); 1112 return this; 1113 } 1114 length()1115 public int length() { 1116 return value.size(); 1117 } 1118 get(int index)1119 public Object get(int index) { 1120 return index >= 0 && value.size() > index ? value.get(index) : null; 1121 } 1122 delete(int index)1123 public ArrayBuilder delete(int index) { 1124 value.remove(index); 1125 return this; 1126 } 1127 toJSON()1128 public JSON toJSON() { 1129 if (json == null) { 1130 json = new JSON(ValueType.ARRAY, value); 1131 } 1132 return json; 1133 } 1134 1135 @Override toString()1136 public String toString() { 1137 StringWriter sw = new StringWriter(); 1138 writeTo(sw, value); 1139 return sw.toString(); 1140 } 1141 } 1142 1143 public static final class ObjectBuilder { 1144 1145 final Map<String, Object> value; 1146 1147 private JSON json; 1148 ObjectBuilder(Map<String, Object> value)1149 ObjectBuilder(Map<String, Object> value) { 1150 this.value = value; 1151 } 1152 ObjectBuilder()1153 ObjectBuilder() { 1154 this(new LinkedHashMap<>()); 1155 } 1156 putObject(String key)1157 public ObjectBuilder putObject(String key) { 1158 ObjectBuilder b = new ObjectBuilder(); 1159 value.put(key, b.value); 1160 return b; 1161 } 1162 putObject(String key, JSON val)1163 public ObjectBuilder putObject(String key, JSON val) { 1164 if (val.type == ValueType.NULL) { 1165 return putNull(key); 1166 } 1167 if (val.type != ValueType.OBJECT) { 1168 throw new JSONException("Value must be an Object"); 1169 } 1170 value.put(key, val.value); 1171 return this; 1172 } 1173 putArray(String key)1174 public ArrayBuilder putArray(String key) { 1175 ArrayBuilder b = new ArrayBuilder(); 1176 value.put(key, b.value); 1177 return b; 1178 } 1179 putArray(String key, JSON val)1180 public ObjectBuilder putArray(String key, JSON val) { 1181 if (val.type == ValueType.NULL) { 1182 return putNull(key); 1183 } 1184 if (val.type != ValueType.ARRAY) { 1185 throw new JSONException("Value must be an Array"); 1186 } 1187 value.put(key, val.value); 1188 return this; 1189 } 1190 put(String key, String val)1191 public ObjectBuilder put(String key, String val) { 1192 value.put(key, val); 1193 return this; 1194 } 1195 put(String key, Number val)1196 public ObjectBuilder put(String key, Number val) { 1197 value.put(key, val); 1198 return this; 1199 } 1200 put(String key, Boolean val)1201 public ObjectBuilder put(String key, Boolean val) { 1202 value.put(key, val); 1203 return this; 1204 } 1205 putNull(String key)1206 public ObjectBuilder putNull(String key) { 1207 value.put(key, null); 1208 return this; 1209 } 1210 delete(String key)1211 public ObjectBuilder delete(String key) { 1212 value.remove(key); 1213 return this; 1214 } 1215 keys()1216 public Iterable<String> keys() { 1217 return value.keySet(); 1218 } 1219 toJSON()1220 public JSON toJSON() { 1221 if (json == null) { 1222 json = new JSON(ValueType.OBJECT, value); 1223 } 1224 return json; 1225 } 1226 1227 @Override toString()1228 public String toString() { 1229 StringWriter sw = new StringWriter(); 1230 writeTo(sw, value); 1231 return sw.toString(); 1232 } 1233 } 1234 } 1235