1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.util.ajax;
20
21 import java.io.Externalizable;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.Reader;
25 import java.lang.reflect.Array;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.concurrent.ConcurrentHashMap;
32
33 import org.eclipse.jetty.util.IO;
34 import org.eclipse.jetty.util.Loader;
35 import org.eclipse.jetty.util.QuotedStringTokenizer;
36 import org.eclipse.jetty.util.TypeUtil;
37 import org.eclipse.jetty.util.log.Log;
38 import org.eclipse.jetty.util.log.Logger;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 public class JSON
91 {
92 static final Logger LOG = Log.getLogger(JSON.class);
93 public final static JSON DEFAULT = new JSON();
94
95 private Map<String, Convertor> _convertors = new ConcurrentHashMap<String, Convertor>();
96 private int _stringBufferSize = 1024;
97
98 public JSON()
99 {
100 }
101
102
103
104
105
106 public int getStringBufferSize()
107 {
108 return _stringBufferSize;
109 }
110
111
112
113
114
115
116 public void setStringBufferSize(int stringBufferSize)
117 {
118 _stringBufferSize = stringBufferSize;
119 }
120
121
122
123
124
125
126
127
128
129 public static void registerConvertor(Class forClass, Convertor convertor)
130 {
131 DEFAULT.addConvertor(forClass,convertor);
132 }
133
134 public static JSON getDefault()
135 {
136 return DEFAULT;
137 }
138
139 @Deprecated
140 public static void setDefault(JSON json)
141 {
142 }
143
144 public static String toString(Object object)
145 {
146 StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
147 DEFAULT.append(buffer,object);
148 return buffer.toString();
149 }
150
151 public static String toString(Map object)
152 {
153 StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
154 DEFAULT.appendMap(buffer,object);
155 return buffer.toString();
156 }
157
158 public static String toString(Object[] array)
159 {
160 StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
161 DEFAULT.appendArray(buffer,array);
162 return buffer.toString();
163 }
164
165
166
167
168
169
170 public static Object parse(String s)
171 {
172 return DEFAULT.parse(new StringSource(s),false);
173 }
174
175
176
177
178
179
180
181
182 public static Object parse(String s, boolean stripOuterComment)
183 {
184 return DEFAULT.parse(new StringSource(s),stripOuterComment);
185 }
186
187
188
189
190
191
192
193 public static Object parse(Reader in) throws IOException
194 {
195 return DEFAULT.parse(new ReaderSource(in),false);
196 }
197
198
199
200
201
202
203
204
205
206 public static Object parse(Reader in, boolean stripOuterComment) throws IOException
207 {
208 return DEFAULT.parse(new ReaderSource(in),stripOuterComment);
209 }
210
211
212
213
214
215
216
217
218 @Deprecated
219 public static Object parse(InputStream in) throws IOException
220 {
221 return DEFAULT.parse(new StringSource(IO.toString(in)),false);
222 }
223
224
225
226
227
228
229
230
231
232
233 @Deprecated
234 public static Object parse(InputStream in, boolean stripOuterComment) throws IOException
235 {
236 return DEFAULT.parse(new StringSource(IO.toString(in)),stripOuterComment);
237 }
238
239
240
241
242
243
244
245
246 public String toJSON(Object object)
247 {
248 StringBuilder buffer = new StringBuilder(getStringBufferSize());
249 append(buffer,object);
250 return buffer.toString();
251 }
252
253
254
255
256
257
258
259
260 public Object fromJSON(String json)
261 {
262 Source source = new StringSource(json);
263 return parse(source);
264 }
265
266 @Deprecated
267 public void append(StringBuffer buffer, Object object)
268 {
269 append((Appendable)buffer,object);
270 }
271
272
273
274
275
276
277
278
279
280 public void append(Appendable buffer, Object object)
281 {
282 try
283 {
284 if (object == null)
285 {
286 buffer.append("null");
287 }
288
289 else if (object instanceof Map)
290 {
291 appendMap(buffer,(Map)object);
292 }
293 else if (object instanceof String)
294 {
295 appendString(buffer,(String)object);
296 }
297 else if (object instanceof Number)
298 {
299 appendNumber(buffer,(Number)object);
300 }
301 else if (object instanceof Boolean)
302 {
303 appendBoolean(buffer,(Boolean)object);
304 }
305 else if (object.getClass().isArray())
306 {
307 appendArray(buffer,object);
308 }
309 else if (object instanceof Character)
310 {
311 appendString(buffer,object.toString());
312 }
313 else if (object instanceof Convertible)
314 {
315 appendJSON(buffer,(Convertible)object);
316 }
317 else if (object instanceof Generator)
318 {
319 appendJSON(buffer,(Generator)object);
320 }
321 else
322 {
323
324 Convertor convertor = getConvertor(object.getClass());
325 if (convertor != null)
326 {
327 appendJSON(buffer,convertor,object);
328 }
329 else if (object instanceof Collection)
330 {
331 appendArray(buffer,(Collection)object);
332 }
333 else
334 {
335 appendString(buffer,object.toString());
336 }
337 }
338 }
339 catch (IOException e)
340 {
341 throw new RuntimeException(e);
342 }
343 }
344
345 @Deprecated
346 public void appendNull(StringBuffer buffer)
347 {
348 appendNull((Appendable)buffer);
349 }
350
351 public void appendNull(Appendable buffer)
352 {
353 try
354 {
355 buffer.append("null");
356 }
357 catch (IOException e)
358 {
359 throw new RuntimeException(e);
360 }
361 }
362
363 @Deprecated
364 public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object)
365 {
366 appendJSON((Appendable)buffer,convertor,object);
367 }
368
369 public void appendJSON(final Appendable buffer, final Convertor convertor, final Object object)
370 {
371 appendJSON(buffer,new Convertible()
372 {
373 public void fromJSON(Map object)
374 {
375 }
376
377 public void toJSON(Output out)
378 {
379 convertor.toJSON(object,out);
380 }
381 });
382 }
383
384 @Deprecated
385 public void appendJSON(final StringBuffer buffer, Convertible converter)
386 {
387 appendJSON((Appendable)buffer,converter);
388 }
389
390 public void appendJSON(final Appendable buffer, Convertible converter)
391 {
392 ConvertableOutput out=new ConvertableOutput(buffer);
393 converter.toJSON(out);
394 out.complete();
395 }
396
397 @Deprecated
398 public void appendJSON(StringBuffer buffer, Generator generator)
399 {
400 generator.addJSON(buffer);
401 }
402
403 public void appendJSON(Appendable buffer, Generator generator)
404 {
405 generator.addJSON(buffer);
406 }
407
408 @Deprecated
409 public void appendMap(StringBuffer buffer, Map<?,?> map)
410 {
411 appendMap((Appendable)buffer,map);
412 }
413
414 public void appendMap(Appendable buffer, Map<?,?> map)
415 {
416 try
417 {
418 if (map == null)
419 {
420 appendNull(buffer);
421 return;
422 }
423
424 buffer.append('{');
425 Iterator<?> iter = map.entrySet().iterator();
426 while (iter.hasNext())
427 {
428 Map.Entry<?,?> entry = (Map.Entry<?,?>)iter.next();
429 QuotedStringTokenizer.quote(buffer,entry.getKey().toString());
430 buffer.append(':');
431 append(buffer,entry.getValue());
432 if (iter.hasNext())
433 buffer.append(',');
434 }
435
436 buffer.append('}');
437 }
438 catch (IOException e)
439 {
440 throw new RuntimeException(e);
441 }
442 }
443
444 @Deprecated
445 public void appendArray(StringBuffer buffer, Collection collection)
446 {
447 appendArray((Appendable)buffer,collection);
448 }
449
450 public void appendArray(Appendable buffer, Collection collection)
451 {
452 try
453 {
454 if (collection == null)
455 {
456 appendNull(buffer);
457 return;
458 }
459
460 buffer.append('[');
461 Iterator iter = collection.iterator();
462 boolean first = true;
463 while (iter.hasNext())
464 {
465 if (!first)
466 buffer.append(',');
467
468 first = false;
469 append(buffer,iter.next());
470 }
471
472 buffer.append(']');
473 }
474 catch (IOException e)
475 {
476 throw new RuntimeException(e);
477 }
478 }
479
480 @Deprecated
481 public void appendArray(StringBuffer buffer, Object array)
482 {
483 appendArray((Appendable)buffer,array);
484 }
485
486 public void appendArray(Appendable buffer, Object array)
487 {
488 try
489 {
490 if (array == null)
491 {
492 appendNull(buffer);
493 return;
494 }
495
496 buffer.append('[');
497 int length = Array.getLength(array);
498
499 for (int i = 0; i < length; i++)
500 {
501 if (i != 0)
502 buffer.append(',');
503 append(buffer,Array.get(array,i));
504 }
505
506 buffer.append(']');
507 }
508 catch (IOException e)
509 {
510 throw new RuntimeException(e);
511 }
512 }
513
514 @Deprecated
515 public void appendBoolean(StringBuffer buffer, Boolean b)
516 {
517 appendBoolean((Appendable)buffer,b);
518 }
519
520 public void appendBoolean(Appendable buffer, Boolean b)
521 {
522 try
523 {
524 if (b == null)
525 {
526 appendNull(buffer);
527 return;
528 }
529 buffer.append(b?"true":"false");
530 }
531 catch (IOException e)
532 {
533 throw new RuntimeException(e);
534 }
535 }
536
537 @Deprecated
538 public void appendNumber(StringBuffer buffer, Number number)
539 {
540 appendNumber((Appendable)buffer,number);
541 }
542
543 public void appendNumber(Appendable buffer, Number number)
544 {
545 try
546 {
547 if (number == null)
548 {
549 appendNull(buffer);
550 return;
551 }
552 buffer.append(String.valueOf(number));
553 }
554 catch (IOException e)
555 {
556 throw new RuntimeException(e);
557 }
558 }
559
560 @Deprecated
561 public void appendString(StringBuffer buffer, String string)
562 {
563 appendString((Appendable)buffer,string);
564 }
565
566 public void appendString(Appendable buffer, String string)
567 {
568 if (string == null)
569 {
570 appendNull(buffer);
571 return;
572 }
573
574 QuotedStringTokenizer.quote(buffer,string);
575 }
576
577
578
579 protected String toString(char[] buffer, int offset, int length)
580 {
581 return new String(buffer,offset,length);
582 }
583
584 protected Map<String, Object> newMap()
585 {
586 return new HashMap<String, Object>();
587 }
588
589 protected Object[] newArray(int size)
590 {
591 return new Object[size];
592 }
593
594 protected JSON contextForArray()
595 {
596 return this;
597 }
598
599 protected JSON contextFor(String field)
600 {
601 return this;
602 }
603
604 protected Object convertTo(Class type, Map map)
605 {
606 if (type != null && Convertible.class.isAssignableFrom(type))
607 {
608 try
609 {
610 Convertible conv = (Convertible)type.newInstance();
611 conv.fromJSON(map);
612 return conv;
613 }
614 catch (Exception e)
615 {
616 throw new RuntimeException(e);
617 }
618 }
619
620 Convertor convertor = getConvertor(type);
621 if (convertor != null)
622 {
623 return convertor.fromJSON(map);
624 }
625 return map;
626 }
627
628
629
630
631
632
633
634
635
636 public void addConvertor(Class forClass, Convertor convertor)
637 {
638 _convertors.put(forClass.getName(),convertor);
639 }
640
641
642
643
644
645
646
647
648
649
650
651
652 protected Convertor getConvertor(Class forClass)
653 {
654 Class cls = forClass;
655 Convertor convertor = _convertors.get(cls.getName());
656 if (convertor == null && this != DEFAULT)
657 convertor = DEFAULT.getConvertor(cls);
658
659 while (convertor == null && cls != Object.class)
660 {
661 Class[] ifs = cls.getInterfaces();
662 int i = 0;
663 while (convertor == null && ifs != null && i < ifs.length)
664 convertor = _convertors.get(ifs[i++].getName());
665 if (convertor == null)
666 {
667 cls = cls.getSuperclass();
668 convertor = _convertors.get(cls.getName());
669 }
670 }
671 return convertor;
672 }
673
674
675
676
677
678
679
680
681
682 public void addConvertorFor(String name, Convertor convertor)
683 {
684 _convertors.put(name,convertor);
685 }
686
687
688
689
690
691
692
693
694 public Convertor getConvertorFor(String name)
695 {
696 Convertor convertor = _convertors.get(name);
697 if (convertor == null && this != DEFAULT)
698 convertor = DEFAULT.getConvertorFor(name);
699 return convertor;
700 }
701
702 public Object parse(Source source, boolean stripOuterComment)
703 {
704 int comment_state = 0;
705 if (!stripOuterComment)
706 return parse(source);
707
708 int strip_state = 1;
709
710 Object o = null;
711 while (source.hasNext())
712 {
713 char c = source.peek();
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732 comment
733 else if (comment_state > 1)
734 {
735 switch (c)
736 {
737 case '*':
738 comment_state = 3;
739 break;
740 case '/':
741 if (comment_state == 3)
742 {
743 comment_state = 0;
744 if (strip_state == 2)
745 return o;
746 }
747 else
748 comment_state = 2;
749 break;
750 default:
751 comment_state = 2;
752 }
753 }
754
755 else if (comment_state < 0)
756 {
757 switch (c)
758 {
759 case '\r':
760 case '\n':
761 comment_state = 0;
762 default:
763 break;
764 }
765 }
766
767 else
768 {
769 if (!Character.isWhitespace(c))
770 {
771 if (c == '/')
772 comment_state = 1;
773 else if (c == '*')
774 comment_state = 3;
775 else if (o == null)
776 {
777 o = parse(source);
778 continue;
779 }
780 }
781 }
782
783 source.next();
784 }
785
786 return o;
787 }
788
789 public Object parse(Source source)
790 {
791 int comment_state = 0;
792
793 while (source.hasNext())
794 {
795 char c = source.peek();
796
797
798
799
800
801
802
803
804
805
806
807
808
809 comment
810 else if (comment_state > 1)
811 {
812 switch (c)
813 {
814 case '*':
815 comment_state = 3;
816 break;
817 case '/':
818 if (comment_state == 3)
819 comment_state = 0;
820 else
821 comment_state = 2;
822 break;
823 default:
824 comment_state = 2;
825 }
826 }
827
828 else if (comment_state < 0)
829 {
830 switch (c)
831 {
832 case '\r':
833 case '\n':
834 comment_state = 0;
835 break;
836 default:
837 break;
838 }
839 }
840
841 else
842 {
843 switch (c)
844 {
845 case '{':
846 return parseObject(source);
847 case '[':
848 return parseArray(source);
849 case '"':
850 return parseString(source);
851 case '-':
852 return parseNumber(source);
853
854 case 'n':
855 complete("null",source);
856 return null;
857 case 't':
858 complete("true",source);
859 return Boolean.TRUE;
860 case 'f':
861 complete("false",source);
862 return Boolean.FALSE;
863 case 'u':
864 complete("undefined",source);
865 return null;
866 case 'N':
867 complete("NaN",source);
868 return null;
869
870 case '/':
871 comment_state = 1;
872 break;
873
874 default:
875 if (Character.isDigit(c))
876 return parseNumber(source);
877 else if (Character.isWhitespace(c))
878 break;
879 return handleUnknown(source,c);
880 }
881 }
882 source.next();
883 }
884
885 return null;
886 }
887
888 protected Object handleUnknown(Source source, char c)
889 {
890 throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source);
891 }
892
893 protected Object parseObject(Source source)
894 {
895 if (source.next() != '{')
896 throw new IllegalStateException();
897 Map<String, Object> map = newMap();
898
899 char next = seekTo("\"}",source);
900
901 while (source.hasNext())
902 {
903 if (next == '}')
904 {
905 source.next();
906 break;
907 }
908
909 String name = parseString(source);
910 seekTo(':',source);
911 source.next();
912
913 Object value = contextFor(name).parse(source);
914 map.put(name,value);
915
916 seekTo(",}",source);
917 next = source.next();
918 if (next == '}')
919 break;
920 else
921 next = seekTo("\"}",source);
922 }
923
924 String xclassname = (String)map.get("x-class");
925 if (xclassname != null)
926 {
927 Convertor c = getConvertorFor(xclassname);
928 if (c != null)
929 return c.fromJSON(map);
930 LOG.warn("No Convertor for x-class '{}'", xclassname);
931 }
932
933 String classname = (String)map.get("class");
934 if (classname != null)
935 {
936 try
937 {
938 Class c = Loader.loadClass(JSON.class,classname);
939 return convertTo(c,map);
940 }
941 catch (ClassNotFoundException e)
942 {
943 LOG.warn("No Class for '{}'", classname);
944 }
945 }
946
947 return map;
948 }
949
950 protected Object parseArray(Source source)
951 {
952 if (source.next() != '[')
953 throw new IllegalStateException();
954
955 int size = 0;
956 ArrayList list = null;
957 Object item = null;
958 boolean coma = true;
959
960 while (source.hasNext())
961 {
962 char c = source.peek();
963 switch (c)
964 {
965 case ']':
966 source.next();
967 switch (size)
968 {
969 case 0:
970 return newArray(0);
971 case 1:
972 Object array = newArray(1);
973 Array.set(array,0,item);
974 return array;
975 default:
976 return list.toArray(newArray(list.size()));
977 }
978
979 case ',':
980 if (coma)
981 throw new IllegalStateException();
982 coma = true;
983 source.next();
984 break;
985
986 default:
987 if (Character.isWhitespace(c))
988 source.next();
989 else
990 {
991 coma = false;
992 if (size++ == 0)
993 item = contextForArray().parse(source);
994 else if (list == null)
995 {
996 list = new ArrayList();
997 list.add(item);
998 item = contextForArray().parse(source);
999 list.add(item);
1000 item = null;
1001 }
1002 else
1003 {
1004 item = contextForArray().parse(source);
1005 list.add(item);
1006 item = null;
1007 }
1008 }
1009 }
1010
1011 }
1012
1013 throw new IllegalStateException("unexpected end of array");
1014 }
1015
1016 protected String parseString(Source source)
1017 {
1018 if (source.next() != '"')
1019 throw new IllegalStateException();
1020
1021 boolean escape = false;
1022
1023 StringBuilder b = null;
1024 final char[] scratch = source.scratchBuffer();
1025
1026 if (scratch != null)
1027 {
1028 int i = 0;
1029 while (source.hasNext())
1030 {
1031 if (i >= scratch.length)
1032 {
1033
1034
1035 b = new StringBuilder(scratch.length * 2);
1036 b.append(scratch,0,i);
1037 break;
1038 }
1039
1040 char c = source.next();
1041
1042 if (escape)
1043 {
1044 escape = false;
1045 switch (c)
1046 {
1047 case '"':
1048 scratch[i++] = '"';
1049 break;
1050 case '\\':
1051 scratch[i++] = '\\';
1052 break;
1053 case '/':
1054 scratch[i++] = '/';
1055 break;
1056 case 'b':
1057 scratch[i++] = '\b';
1058 break;
1059 case 'f':
1060 scratch[i++] = '\f';
1061 break;
1062 case 'n':
1063 scratch[i++] = '\n';
1064 break;
1065 case 'r':
1066 scratch[i++] = '\r';
1067 break;
1068 case 't':
1069 scratch[i++] = '\t';
1070 break;
1071 case 'u':
1072 char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
1073 + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
1074 scratch[i++] = uc;
1075 break;
1076 default:
1077 scratch[i++] = c;
1078 }
1079 }
1080 else if (c == '\\')
1081 {
1082 escape = true;
1083 }
1084 else if (c == '\"')
1085 {
1086
1087 return toString(scratch,0,i);
1088 }
1089 else
1090 {
1091 scratch[i++] = c;
1092 }
1093 }
1094
1095
1096 if (b == null)
1097 return toString(scratch,0,i);
1098 }
1099 else
1100 b = new StringBuilder(getStringBufferSize());
1101
1102
1103 final StringBuilder builder=b;
1104 while (source.hasNext())
1105 {
1106 char c = source.next();
1107
1108 if (escape)
1109 {
1110 escape = false;
1111 switch (c)
1112 {
1113 case '"':
1114 builder.append('"');
1115 break;
1116 case '\\':
1117 builder.append('\\');
1118 break;
1119 case '/':
1120 builder.append('/');
1121 break;
1122 case 'b':
1123 builder.append('\b');
1124 break;
1125 case 'f':
1126 builder.append('\f');
1127 break;
1128 case 'n':
1129 builder.append('\n');
1130 break;
1131 case 'r':
1132 builder.append('\r');
1133 break;
1134 case 't':
1135 builder.append('\t');
1136 break;
1137 case 'u':
1138 char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
1139 + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
1140 builder.append(uc);
1141 break;
1142 default:
1143 builder.append(c);
1144 }
1145 }
1146 else if (c == '\\')
1147 {
1148 escape = true;
1149 }
1150 else if (c == '\"')
1151 {
1152 break;
1153 }
1154 else
1155 {
1156 builder.append(c);
1157 }
1158 }
1159 return builder.toString();
1160 }
1161
1162 public Number parseNumber(Source source)
1163 {
1164 boolean minus = false;
1165 long number = 0;
1166 StringBuilder buffer = null;
1167
1168 longLoop: while (source.hasNext())
1169 {
1170 char c = source.peek();
1171 switch (c)
1172 {
1173 case '0':
1174 case '1':
1175 case '2':
1176 case '3':
1177 case '4':
1178 case '5':
1179 case '6':
1180 case '7':
1181 case '8':
1182 case '9':
1183 number = number * 10 + (c - '0');
1184 source.next();
1185 break;
1186
1187 case '-':
1188 case '+':
1189 if (number != 0)
1190 throw new IllegalStateException("bad number");
1191 minus = true;
1192 source.next();
1193 break;
1194
1195 case '.':
1196 case 'e':
1197 case 'E':
1198 buffer = new StringBuilder(16);
1199 if (minus)
1200 buffer.append('-');
1201 buffer.append(number);
1202 buffer.append(c);
1203 source.next();
1204 break longLoop;
1205
1206 default:
1207 break longLoop;
1208 }
1209 }
1210
1211 if (buffer == null)
1212 return minus ? -1 * number : number;
1213
1214 doubleLoop: while (source.hasNext())
1215 {
1216 char c = source.peek();
1217 switch (c)
1218 {
1219 case '0':
1220 case '1':
1221 case '2':
1222 case '3':
1223 case '4':
1224 case '5':
1225 case '6':
1226 case '7':
1227 case '8':
1228 case '9':
1229 case '-':
1230 case '.':
1231 case '+':
1232 case 'e':
1233 case 'E':
1234 buffer.append(c);
1235 source.next();
1236 break;
1237
1238 default:
1239 break doubleLoop;
1240 }
1241 }
1242 return new Double(buffer.toString());
1243
1244 }
1245
1246 protected void seekTo(char seek, Source source)
1247 {
1248 while (source.hasNext())
1249 {
1250 char c = source.peek();
1251 if (c == seek)
1252 return;
1253
1254 if (!Character.isWhitespace(c))
1255 throw new IllegalStateException("Unexpected '" + c + " while seeking '" + seek + "'");
1256 source.next();
1257 }
1258
1259 throw new IllegalStateException("Expected '" + seek + "'");
1260 }
1261
1262 protected char seekTo(String seek, Source source)
1263 {
1264 while (source.hasNext())
1265 {
1266 char c = source.peek();
1267 if (seek.indexOf(c) >= 0)
1268 {
1269 return c;
1270 }
1271
1272 if (!Character.isWhitespace(c))
1273 throw new IllegalStateException("Unexpected '" + c + "' while seeking one of '" + seek + "'");
1274 source.next();
1275 }
1276
1277 throw new IllegalStateException("Expected one of '" + seek + "'");
1278 }
1279
1280 protected static void complete(String seek, Source source)
1281 {
1282 int i = 0;
1283 while (source.hasNext() && i < seek.length())
1284 {
1285 char c = source.next();
1286 if (c != seek.charAt(i++))
1287 throw new IllegalStateException("Unexpected '" + c + " while seeking \"" + seek + "\"");
1288 }
1289
1290 if (i < seek.length())
1291 throw new IllegalStateException("Expected \"" + seek + "\"");
1292 }
1293
1294 private final class ConvertableOutput implements Output
1295 {
1296 private final Appendable _buffer;
1297 char c = '{';
1298
1299 private ConvertableOutput(Appendable buffer)
1300 {
1301 _buffer = buffer;
1302 }
1303
1304 public void complete()
1305 {
1306 try
1307 {
1308 if (c == '{')
1309 _buffer.append("{}");
1310 else if (c != 0)
1311 _buffer.append("}");
1312 }
1313 catch (IOException e)
1314 {
1315 throw new RuntimeException(e);
1316 }
1317 }
1318
1319 public void add(Object obj)
1320 {
1321 if (c == 0)
1322 throw new IllegalStateException();
1323 append(_buffer,obj);
1324 c = 0;
1325 }
1326
1327 public void addClass(Class type)
1328 {
1329 try
1330 {
1331 if (c == 0)
1332 throw new IllegalStateException();
1333 _buffer.append(c);
1334 _buffer.append("\"class\":");
1335 append(_buffer,type.getName());
1336 c = ',';
1337 }
1338 catch (IOException e)
1339 {
1340 throw new RuntimeException(e);
1341 }
1342 }
1343
1344 public void add(String name, Object value)
1345 {
1346 try
1347 {
1348 if (c == 0)
1349 throw new IllegalStateException();
1350 _buffer.append(c);
1351 QuotedStringTokenizer.quote(_buffer,name);
1352 _buffer.append(':');
1353 append(_buffer,value);
1354 c = ',';
1355 }
1356 catch (IOException e)
1357 {
1358 throw new RuntimeException(e);
1359 }
1360 }
1361
1362 public void add(String name, double value)
1363 {
1364 try
1365 {
1366 if (c == 0)
1367 throw new IllegalStateException();
1368 _buffer.append(c);
1369 QuotedStringTokenizer.quote(_buffer,name);
1370 _buffer.append(':');
1371 appendNumber(_buffer, value);
1372 c = ',';
1373 }
1374 catch (IOException e)
1375 {
1376 throw new RuntimeException(e);
1377 }
1378 }
1379
1380 public void add(String name, long value)
1381 {
1382 try
1383 {
1384 if (c == 0)
1385 throw new IllegalStateException();
1386 _buffer.append(c);
1387 QuotedStringTokenizer.quote(_buffer,name);
1388 _buffer.append(':');
1389 appendNumber(_buffer, value);
1390 c = ',';
1391 }
1392 catch (IOException e)
1393 {
1394 throw new RuntimeException(e);
1395 }
1396 }
1397
1398 public void add(String name, boolean value)
1399 {
1400 try
1401 {
1402 if (c == 0)
1403 throw new IllegalStateException();
1404 _buffer.append(c);
1405 QuotedStringTokenizer.quote(_buffer,name);
1406 _buffer.append(':');
1407 appendBoolean(_buffer,value?Boolean.TRUE:Boolean.FALSE);
1408 c = ',';
1409 }
1410 catch (IOException e)
1411 {
1412 throw new RuntimeException(e);
1413 }
1414 }
1415 }
1416
1417 public interface Source
1418 {
1419 boolean hasNext();
1420
1421 char next();
1422
1423 char peek();
1424
1425 char[] scratchBuffer();
1426 }
1427
1428 public static class StringSource implements Source
1429 {
1430 private final String string;
1431 private int index;
1432 private char[] scratch;
1433
1434 public StringSource(String s)
1435 {
1436 string = s;
1437 }
1438
1439 public boolean hasNext()
1440 {
1441 if (index < string.length())
1442 return true;
1443 scratch = null;
1444 return false;
1445 }
1446
1447 public char next()
1448 {
1449 return string.charAt(index++);
1450 }
1451
1452 public char peek()
1453 {
1454 return string.charAt(index);
1455 }
1456
1457 @Override
1458 public String toString()
1459 {
1460 return string.substring(0,index) + "|||" + string.substring(index);
1461 }
1462
1463 public char[] scratchBuffer()
1464 {
1465 if (scratch == null)
1466 scratch = new char[string.length()];
1467 return scratch;
1468 }
1469 }
1470
1471 public static class ReaderSource implements Source
1472 {
1473 private Reader _reader;
1474 private int _next = -1;
1475 private char[] scratch;
1476
1477 public ReaderSource(Reader r)
1478 {
1479 _reader = r;
1480 }
1481
1482 public void setReader(Reader reader)
1483 {
1484 _reader = reader;
1485 _next = -1;
1486 }
1487
1488 public boolean hasNext()
1489 {
1490 getNext();
1491 if (_next < 0)
1492 {
1493 scratch = null;
1494 return false;
1495 }
1496 return true;
1497 }
1498
1499 public char next()
1500 {
1501 getNext();
1502 char c = (char)_next;
1503 _next = -1;
1504 return c;
1505 }
1506
1507 public char peek()
1508 {
1509 getNext();
1510 return (char)_next;
1511 }
1512
1513 private void getNext()
1514 {
1515 if (_next < 0)
1516 {
1517 try
1518 {
1519 _next = _reader.read();
1520 }
1521 catch (IOException e)
1522 {
1523 throw new RuntimeException(e);
1524 }
1525 }
1526 }
1527
1528 public char[] scratchBuffer()
1529 {
1530 if (scratch == null)
1531 scratch = new char[1024];
1532 return scratch;
1533 }
1534
1535 }
1536
1537
1538
1539
1540 public interface Output
1541 {
1542 public void addClass(Class c);
1543
1544 public void add(Object obj);
1545
1546 public void add(String name, Object value);
1547
1548 public void add(String name, double value);
1549
1550 public void add(String name, long value);
1551
1552 public void add(String name, boolean value);
1553 }
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568 public interface Convertible
1569 {
1570 public void toJSON(Output out);
1571
1572 public void fromJSON(Map object);
1573 }
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586 public interface Convertor
1587 {
1588 public void toJSON(Object obj, Output out);
1589
1590 public Object fromJSON(Map object);
1591 }
1592
1593
1594
1595
1596
1597
1598 public interface Generator
1599 {
1600 public void addJSON(Appendable buffer);
1601 }
1602
1603
1604
1605
1606
1607 public static class Literal implements Generator
1608 {
1609 private String _json;
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619 public Literal(String json)
1620 {
1621 if (LOG.isDebugEnabled())
1622 parse(json);
1623 _json = json;
1624 }
1625
1626 @Override
1627 public String toString()
1628 {
1629 return _json;
1630 }
1631
1632 public void addJSON(Appendable buffer)
1633 {
1634 try
1635 {
1636 buffer.append(_json);
1637 }
1638 catch(IOException e)
1639 {
1640 throw new RuntimeException(e);
1641 }
1642 }
1643 }
1644 }