• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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