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
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}
1641