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