Mercurial Hosting > luan
comparison src/org/eclipse/jetty/util/ajax/JSON.java @ 802:3428c60d7cfc
replace jetty jars with source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 07 Sep 2016 21:15:48 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
801:6a21393191c1 | 802:3428c60d7cfc |
---|---|
1 // | |
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 | |
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 * 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 */ | |
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 * @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 } |