001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 *
027 * This file is based on:
028 * org.json.JSONObject
029 * from the JSON in Java implementation.
030 *
031 * Copyright (c) 2002 JSON.org
032 *
033 * Permission is hereby granted, free of charge, to any person obtaining a copy
034 * of this software and associated documentation files (the "Software"), to deal
035 * in the Software without restriction, including without limitation the rights
036 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
037 * copies of the Software, and to permit persons to whom the Software is
038 * furnished to do so, subject to the following conditions:
039 *
040 * The above copyright notice and this permission notice shall be included in all
041 * copies or substantial portions of the Software.
042 *
043 * The Software shall be used for Good, not Evil.
044 *
045 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
046 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
047 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
048 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
049 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
050 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
051 * SOFTWARE.
052 */
053
054package org.opencms.json;
055
056import java.io.IOException;
057import java.io.Writer;
058import java.lang.reflect.Field;
059import java.lang.reflect.Method;
060import java.util.Collection;
061import java.util.HashMap;
062import java.util.Iterator;
063import java.util.LinkedHashMap;
064import java.util.Map;
065import java.util.Set;
066import java.util.TreeSet;
067
068/**
069 * A JSONObject is an unordered collection of name/value pairs. Its
070 * external form is a string wrapped in curly braces with colons between the
071 * names and values, and commas between the values and names. The internal form
072 * is an object having <code>get</code> and <code>opt</code> methods for
073 * accessing the values by name, and <code>put</code> methods for adding or
074 * replacing values by name. The values can be any of these types:
075 * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>,
076 * <code>Number</code>, <code>String</code>, or the <code>JSONObject.NULL</code>
077 * object. A JSONObject constructor can be used to convert an external form
078 * JSON text into an internal form whose values can be retrieved with the
079 * <code>get</code> and <code>opt</code> methods, or to convert values into a
080 * JSON text using the <code>put</code> and <code>toString</code> methods.
081 * A <code>get</code> method returns a value if one can be found, and throws an
082 * exception if one cannot be found. An <code>opt</code> method returns a
083 * default value instead of throwing an exception, and so is useful for
084 * obtaining optional values.
085 * <p>
086 * The generic <code>get()</code> and <code>opt()</code> methods return an
087 * object, which you can cast or query for type. There are also typed
088 * <code>get</code> and <code>opt</code> methods that do type checking and type
089 * conversion for you.
090 * <p>
091 * The <code>put</code> methods adds values to an object. For example, <pre>
092 *     myString = new JSONObject().put("JSON", "Hello, World!").toString();</pre>
093 * produces the string <code>{"JSON": "Hello, World"}</code>.
094 * <p>
095 * The texts produced by the <code>toString</code> methods strictly conform to
096 * the JSON syntax rules.
097 * The constructors are more forgiving in the texts they will accept:
098 * <ul>
099 * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
100 *     before the closing brace.</li>
101 * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
102 *     quote)</small>.</li>
103 * <li>Strings do not need to be quoted at all if they do not begin with a quote
104 *     or single quote, and if they do not contain leading or trailing spaces,
105 *     and if they do not contain any of these characters:
106 *     <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers
107 *     and if they are not the reserved words <code>true</code>,
108 *     <code>false</code>, or <code>null</code>.</li>
109 * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as
110 *     by <code>:</code>.</li>
111 * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as
112 *     well as by <code>,</code> <small>(comma)</small>.</li>
113 * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or
114 *     <code>0x-</code> <small>(hex)</small> prefix.</li>
115 * <li>Comments written in the slashshlash, slashstar, and hash conventions
116 *     will be ignored.</li>
117 * </ul>
118 *
119 */
120public class JSONObject {
121
122    /**
123     * JSONObject.NULL is equivalent to the value that JavaScript calls null,
124     * whilst Java's null is equivalent to the value that JavaScript calls
125     * undefined.<p>
126     */
127    protected static final class Null {
128
129        /**
130         * A Null object is equal to the null value and to itself.<p>
131         *
132         * @param object an object to test for nullness
133         * @return true if the object parameter is the JSONObject.NULL object or null
134         */
135        @Override
136        public boolean equals(Object object) {
137
138            return (object == null) || (object == this);
139        }
140
141        /**
142         * @see Object#hashCode()
143         */
144        @Override
145        public int hashCode() {
146
147            return super.hashCode();
148        }
149
150        /**
151         * Get the "null" string value.<p>
152         *
153         * @return the string "null".
154         */
155        @Override
156        public String toString() {
157
158            return "null";
159        }
160
161        /**
162         * There is only intended to be a single instance of the NULL object,
163         * so the clone method returns itself.<p>
164         *
165         * @return NULL.
166         */
167        @Override
168        protected Object clone() {
169
170            return this;
171        }
172    }
173
174    /**
175     * It is sometimes more convenient and less ambiguous to have a
176     * <code>NULL</code> object than to use Java's <code>null</code> value.
177     * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
178     * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
179     */
180    public static final Object NULL = new Null();
181
182    /**
183     * The map where the JSONObject's properties are kept.
184     */
185    private Map<String, Object> m_map;
186
187    /**
188     * Construct an empty JSONObject.<p>
189     */
190    public JSONObject() {
191
192        this(false);
193    }
194
195    /**
196     * Construct an empty sorted JSONObject.<p>
197     *
198     * @param sorted true for sorted, false for none sorted
199     */
200    public JSONObject(boolean sorted) {
201
202        if (sorted) {
203            m_map = new LinkedHashMap<String, Object>();
204        } else {
205            m_map = new HashMap<String, Object>();
206        }
207    }
208
209    /**
210     * Construct a JSONObject from a subset of another JSONObject.<p>
211     *
212     * An array of strings is used to identify the keys that should be copied.
213     * Missing keys are ignored.<p>
214     *
215     * @param jo a JSONObject
216     * @param names an array of strings
217     * @exception JSONException if a value is a non-finite number
218     */
219    public JSONObject(JSONObject jo, String[] names)
220    throws JSONException {
221
222        this();
223        for (int i = 0; i < names.length; i += 1) {
224            putOpt(names[i], jo.opt(names[i]));
225        }
226    }
227
228    /**
229     * Construct a JSONObject from a JSONTokener.<p>
230     *
231     * @param x a JSONTokener object containing the source string
232     * @throws JSONException if there is a syntax error in the source string
233     */
234    public JSONObject(JSONTokener x)
235    throws JSONException {
236
237        this(x, false);
238    }
239
240    /**
241     * Construct a JSONObject from a JSONTokener, optionally sorted.<p>
242     *
243     * @param x a JSONTokener object containing the source string
244     * @param sorted true for sorted, false for none sorted
245     * @throws JSONException if there is a syntax error in the source string
246     */
247    public JSONObject(JSONTokener x, boolean sorted)
248    throws JSONException {
249
250        this(sorted);
251        char c;
252        String key;
253
254        if (x.nextClean() != '{') {
255            throw x.syntaxError("A JSONObject text must begin with '{'");
256        }
257        for (;;) {
258            c = x.nextClean();
259            switch (c) {
260                case 0:
261                    throw x.syntaxError("A JSONObject text must end with '}'");
262                case '}':
263                    return;
264                default:
265                    x.back();
266                    key = x.nextValue().toString();
267            }
268
269            /*
270             * The key is followed by ':'. We will also tolerate '=' or '=>'.
271             */
272
273            c = x.nextClean();
274            if (c == '=') {
275                if (x.next() != '>') {
276                    x.back();
277                }
278            } else if (c != ':') {
279                throw x.syntaxError("Expected a ':' after a key");
280            }
281            put(key, x.nextValue());
282
283            /*
284             * Pairs are separated by ','. We will also tolerate ';'.
285             */
286
287            switch (x.nextClean()) {
288                case ';':
289                case ',':
290                    if (x.nextClean() == '}') {
291                        return;
292                    }
293                    x.back();
294                    break;
295                case '}':
296                    return;
297                default:
298                    throw x.syntaxError("Expected a ',' or '}'");
299            }
300        }
301    }
302
303    /**
304     * Construct a JSONObject from a Map.<p>
305     *
306     * @param map a map object that can be used to initialize the contents of the JSONObject
307     */
308    public JSONObject(Map<String, Object> map) {
309
310        m_map = (map == null) ? new HashMap<String, Object>() : map;
311    }
312
313    /**
314     * Construct a JSONObject from a Map.<p>
315     *
316     * Note: Use this constructor when the map contains &lt;key,bean&gt;.<p>
317     *
318     * @param map a map with Key-Bean data
319     * @param includeSuperClass tell whether to include the super class properties.
320     */
321    public JSONObject(Map<String, Object> map, boolean includeSuperClass) {
322
323        m_map = new HashMap<String, Object>();
324        if (map != null) {
325            for (Iterator<Map.Entry<String, Object>> i = map.entrySet().iterator(); i.hasNext();) {
326                Map.Entry<String, Object> e = i.next();
327                m_map.put(e.getKey(), new JSONObject(e.getValue(), includeSuperClass));
328            }
329        }
330    }
331
332    /**
333     * Construct a JSONObject from an Object using bean getters<p>
334     * It reflects on all of the public methods of the object.
335     * For each of the methods with no parameters and a name starting
336     * with <code>"get"</code> or <code>"is"</code> followed by an uppercase letter,
337     * the method is invoked, and a key and the value returned from the getter method
338     * are put into the new JSONObject.<p>
339     *
340     * The key is formed by removing the <code>"get"</code> or <code>"is"</code> prefix. If the second remaining
341     * character is not upper case, then the first
342     * character is converted to lower case.<p>
343     *
344     * For example, if an object has a method named <code>"getName"</code>, and
345     * if the result of calling <code>object.getName()</code> is <code>"Larry Fine"</code>,
346     * then the JSONObject will contain <code>"name": "Larry Fine"</code>.<p>
347     *
348     * @param bean an object that has getter methods that should be used to make a JSONObject
349     */
350    public JSONObject(Object bean) {
351
352        this();
353        populateInternalMap(bean, false);
354    }
355
356    /**
357     * Construct JSONObject from the given bean.<p>
358     *
359     * This will also create JSONObject for all internal object (List, Map, Inner Objects) of the provided bean.
360     *
361     * @see #JSONObject(Object bean) also.
362     *
363     * @param bean an object that has getter methods that should be used to make a JSONObject
364     * @param includeSuperClass tell whether to include the super class properties.
365     */
366    public JSONObject(Object bean, boolean includeSuperClass) {
367
368        this();
369        populateInternalMap(bean, includeSuperClass);
370    }
371
372    /**
373    * Construct a JSONObject from an Object, using reflection to find the
374    * public members.<p>
375    *
376    * The resulting JSONObject's keys will be the strings
377    * from the names array, and the values will be the field values associated
378    * with those keys in the object. If a key is not found or not visible,
379    * then it will not be copied into the new JSONObject.<p>
380    *
381    * @param object an object that has fields that should be used to make a JSONObject
382    * @param names an array of strings, the names of the fields to be obtained from the object
383    */
384    public JSONObject(Object object, String[] names) {
385
386        this();
387        Class<?> c = object.getClass();
388        for (int i = 0; i < names.length; i += 1) {
389            String name = names[i];
390            try {
391                Field field = c.getField(name);
392                Object value = field.get(object);
393                this.put(name, value);
394            } catch (Exception e) {
395                /* forget about it */
396            }
397        }
398    }
399
400    /**
401     * Construct a JSONObject from a source JSON text string.<p>
402     *
403     * This is the most commonly used JSONObject constructor.<p>
404     *
405     * @param source a string beginning
406     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
407     *  with <code>}</code>&nbsp;<small>(right brace)</small>
408     * @exception JSONException if there is a syntax error in the source string
409     */
410    public JSONObject(String source)
411    throws JSONException {
412
413        this(source, false);
414    }
415
416    /**
417     * Construct a JSONObject from a source JSON text string, optionally sorted.<p>
418     *
419     * This is the most commonly used JSONObject constructor.<p>
420     *
421     * @param source a string beginning
422     * @param sorted true for sorted, false for none sorted
423     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
424     *  with <code>}</code>&nbsp;<small>(right brace)</small>
425     * @exception JSONException if there is a syntax error in the source string
426     */
427    public JSONObject(String source, boolean sorted)
428    throws JSONException {
429
430        this(new JSONTokener(source), sorted);
431    }
432
433    /**
434     * Produce a string from a double. The string "null" will be returned if
435     * the number is not finite.<p>
436     *
437     * @param d a double
438     * @return a String
439     */
440    public static String doubleToString(double d) {
441
442        if (Double.isInfinite(d) || Double.isNaN(d)) {
443            return "null";
444        }
445
446        // Shave off trailing zeros and decimal point, if possible.
447
448        String s = Double.toString(d);
449        if ((s.indexOf('.') > 0) && (s.indexOf('e') < 0) && (s.indexOf('E') < 0)) {
450            while (s.endsWith("0")) {
451                s = s.substring(0, s.length() - 1);
452            }
453            if (s.endsWith(".")) {
454                s = s.substring(0, s.length() - 1);
455            }
456        }
457        return s;
458    }
459
460    /**
461     * Get an array of field names from a JSONObject.<p>
462     *
463     * @param jo the JSONObject
464     * @return an array of field names, or null if there are no names
465     */
466    public static String[] getNames(JSONObject jo) {
467
468        int length = jo.length();
469        if (length == 0) {
470            return null;
471        }
472        Iterator<String> i = jo.keys();
473        String[] names = new String[length];
474        int j = 0;
475        while (i.hasNext()) {
476            names[j] = i.next();
477            j += 1;
478        }
479        return names;
480    }
481
482    /**
483     * Get an array of field names from an Object.<p>
484     *
485     * @param object the object
486     * @return an array of field names, or null if there are no names
487     */
488    public static String[] getNames(Object object) {
489
490        if (object == null) {
491            return null;
492        }
493        Class<?> klass = object.getClass();
494        Field[] fields = klass.getFields();
495        int length = fields.length;
496        if (length == 0) {
497            return null;
498        }
499        String[] names = new String[length];
500        for (int i = 0; i < length; i += 1) {
501            names[i] = fields[i].getName();
502        }
503        return names;
504    }
505
506    /**
507     * Produce a string from a Number.<p>
508     *
509     * @param  n a Number
510     * @return a String
511     * @throws JSONException if n is a non-finite number
512     */
513    public static String numberToString(Number n) throws JSONException {
514
515        if (n == null) {
516            throw new JSONException("Null pointer");
517        }
518        testValidity(n);
519
520        // Shave off trailing zeros and decimal point, if possible.
521
522        String s = n.toString();
523        if ((s.indexOf('.') > 0) && (s.indexOf('e') < 0) && (s.indexOf('E') < 0)) {
524            while (s.endsWith("0")) {
525                s = s.substring(0, s.length() - 1);
526            }
527            if (s.endsWith(".")) {
528                s = s.substring(0, s.length() - 1);
529            }
530        }
531        return s;
532    }
533
534    /**
535     * Produce a string in double quotes with backslash sequences in all the
536     * right places.<p>
537     *
538     * A backslash will be inserted, allowing JSON
539     * text to be delivered in HTML. In JSON text, a string cannot contain a
540     * control character or an unescaped quote or backslash.<p>
541     *
542     * @param string a String
543     * @return  a String correctly formatted for insertion in a JSON text
544     */
545    public static String quote(String string) {
546
547        if ((string == null) || (string.length() == 0)) {
548            return "\"\"";
549        }
550
551        char b;
552        char c = 0;
553        int i;
554        int len = string.length();
555        StringBuffer sb = new StringBuffer(len + 4);
556        String t;
557
558        sb.append('"');
559        for (i = 0; i < len; i += 1) {
560            b = c;
561            c = string.charAt(i);
562            switch (c) {
563                case '\\':
564                case '"':
565                    sb.append('\\');
566                    sb.append(c);
567                    break;
568                case '/':
569                    if (b == '<') {
570                        sb.append('\\');
571                    }
572                    sb.append(c);
573                    break;
574                case '\b':
575                    sb.append("\\b");
576                    break;
577                case '\t':
578                    sb.append("\\t");
579                    break;
580                case '\n':
581                    sb.append("\\n");
582                    break;
583                case '\f':
584                    sb.append("\\f");
585                    break;
586                case '\r':
587                    sb.append("\\r");
588                    break;
589                default:
590                    if ((c < ' ') || ((c >= '\u0080') && (c < '\u00a0')) || ((c >= '\u2000') && (c < '\u2100'))) {
591                        t = "000" + Integer.toHexString(c);
592                        sb.append("\\u" + t.substring(t.length() - 4));
593                    } else {
594                        sb.append(c);
595                    }
596            }
597        }
598        sb.append('"');
599        return sb.toString();
600    }
601
602    /**
603     * Throws an exception if the object is an NaN or infinite number.<p>
604     *
605     * @param o the object to test
606     * @throws JSONException if o is a non-finite number
607     */
608    static void testValidity(Object o) throws JSONException {
609
610        if (o != null) {
611            if (o instanceof Double) {
612                if (((Double)o).isInfinite() || ((Double)o).isNaN()) {
613                    throw new JSONException("JSON does not allow non-finite numbers.");
614                }
615            } else if (o instanceof Float) {
616                if (((Float)o).isInfinite() || ((Float)o).isNaN()) {
617                    throw new JSONException("JSON does not allow non-finite numbers.");
618                }
619            }
620        }
621    }
622
623    /**
624     * Make a JSON text of an Object value.<p>
625     *
626     * If the object has an value.toJSONString() method, then that method will be used to produce
627     * the JSON text. The method is required to produce a strictly
628     * conforming text. If the object does not contain a toJSONString
629     * method (which is the most common case), then a text will be
630     * produced by other means. If the value is an array or Collection,
631     * then a JSONArray will be made from it and its toJSONString method
632     * will be called. If the value is a MAP, then a JSONObject will be made
633     * from it and its toJSONString method will be called. Otherwise, the
634     * value's toString method will be called, and the result will be quoted.<p>
635     *
636     * Warning: This method assumes that the data structure is acyclical.<p>
637     *
638     * @param value the value to be serialized
639     * @return a printable, displayable, transmittable
640     *  representation of the object, beginning
641     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
642     *  with <code>}</code>&nbsp;<small>(right brace)</small>
643     * @throws JSONException if the value is or contains an invalid number
644     */
645    @SuppressWarnings("unchecked")
646    static String valueToString(Object value) throws JSONException {
647
648        if ((value == null) || value.equals(null)) {
649            return "null";
650        }
651        if (value instanceof I_JSONString) {
652            Object o;
653            try {
654                o = ((I_JSONString)value).toJSONString();
655            } catch (Exception e) {
656                throw new JSONException(e);
657            }
658            if (o instanceof String) {
659                return (String)o;
660            }
661            throw new JSONException("Bad value from toJSONString: " + o);
662        }
663        if (value instanceof Number) {
664            return numberToString((Number)value);
665        }
666        if ((value instanceof Boolean) || (value instanceof JSONObject) || (value instanceof JSONArray)) {
667            return value.toString();
668        }
669        if (value instanceof Map) {
670            return new JSONObject((Map<String, Object>)value).toString();
671        }
672        if (value instanceof Collection) {
673            return new JSONArray((Collection<Object>)value).toString();
674        }
675        if (value.getClass().isArray()) {
676            return new JSONArray(value).toString();
677        }
678        return quote(value.toString());
679    }
680
681    /**
682     * Make a pretty printed JSON text of an object value.<p>
683     *
684     * Warning: This method assumes that the data structure is acyclical.<p>
685     *
686     * @param value the value to be serialized
687     * @param indentFactor the number of spaces to add to each level of
688     *  indentation
689     * @param indent the indentation of the top level
690     * @return a printable, displayable, transmittable
691     *  representation of the object, beginning
692     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
693     *  with <code>}</code>&nbsp;<small>(right brace)</small>
694     * @throws JSONException if the object contains an invalid number
695     */
696    @SuppressWarnings("unchecked")
697    static String valueToString(Object value, int indentFactor, int indent) throws JSONException {
698
699        if ((value == null) || value.equals(null)) {
700            return "null";
701        }
702        try {
703            if (value instanceof I_JSONString) {
704                Object o = ((I_JSONString)value).toJSONString();
705                if (o instanceof String) {
706                    return (String)o;
707                }
708            }
709        } catch (Exception e) {
710            /* forget about it */
711        }
712        if (value instanceof Number) {
713            return numberToString((Number)value);
714        }
715        if (value instanceof Boolean) {
716            return value.toString();
717        }
718        if (value instanceof JSONObject) {
719            return ((JSONObject)value).toString(indentFactor, indent);
720        }
721        if (value instanceof JSONArray) {
722            return ((JSONArray)value).toString(indentFactor, indent);
723        }
724        if (value instanceof Map) {
725            return new JSONObject((Map<String, Object>)value).toString(indentFactor, indent);
726        }
727        if (value instanceof Collection) {
728            return new JSONArray((Collection<Object>)value).toString(indentFactor, indent);
729        }
730        if (value.getClass().isArray()) {
731            return new JSONArray(value).toString(indentFactor, indent);
732        }
733        return quote(value.toString());
734    }
735
736    /**
737     * Accumulate values under a key.<p>
738     *
739     * It is similar to the put method except
740     * that if there is already an object stored under the key then a
741     * JSONArray is stored under the key to hold all of the accumulated values.
742     * If there is already a JSONArray, then the new value is appended to it.
743     * In contrast, the put method replaces the previous value.<p>
744     *
745     * @param key   a key string
746     * @param value an object to be accumulated under the key
747     * @return this
748     * @throws JSONException if the value is an invalid number or if the key is null
749     */
750    public JSONObject accumulate(String key, Object value) throws JSONException {
751
752        testValidity(value);
753        Object o = opt(key);
754        if (o == null) {
755            put(key, value instanceof JSONArray ? new JSONArray().put(value) : value);
756        } else if (o instanceof JSONArray) {
757            ((JSONArray)o).put(value);
758        } else {
759            put(key, new JSONArray().put(o).put(value));
760        }
761        return this;
762    }
763
764    /**
765     * Append values to the array under a key.<p>
766     *
767     * If the key does not exist in the
768     * JSONObject, then the key is put in the JSONObject with its value being a
769     * JSONArray containing the value parameter. If the key was already
770     * associated with a JSONArray, then the value parameter is appended to it.<p>
771     *
772     * @param key   a key string
773     * @param value an object to be accumulated under the key
774     * @return this
775     * @throws JSONException if the key is null or if the current value
776     *  associated with the key is not a JSONArray
777     */
778    public JSONObject append(String key, Object value) throws JSONException {
779
780        testValidity(value);
781        Object o = opt(key);
782        if (o == null) {
783            put(key, new JSONArray().put(value));
784        } else if (o instanceof JSONArray) {
785            put(key, ((JSONArray)o).put(value));
786        } else {
787            throw new JSONException("JSONObject[" + key + "] is not a JSONArray.");
788        }
789        return this;
790    }
791
792    /**
793     * Get the value object associated with a key.<p>
794     *
795     * @param key   a key string
796     * @return      the object associated with the key
797     * @throws   JSONException if the key is not found
798     */
799    public Object get(String key) throws JSONException {
800
801        Object o = opt(key);
802        if (o == null) {
803            throw new JSONException("JSONObject[" + quote(key) + "] not found.");
804        }
805        return o;
806    }
807
808    /**
809     * Get the boolean value associated with a key.<p>
810     *
811     * @param key   A key string
812     * @return      the truth
813     * @throws   JSONException if the value is not a Boolean or the String "true" or "false"
814     */
815    public boolean getBoolean(String key) throws JSONException {
816
817        Object o = get(key);
818        if (o.equals(Boolean.FALSE) || ((o instanceof String) && ((String)o).equalsIgnoreCase("false"))) {
819            return false;
820        } else if (o.equals(Boolean.TRUE) || ((o instanceof String) && ((String)o).equalsIgnoreCase("true"))) {
821            return true;
822        }
823        throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean.");
824    }
825
826    /**
827     * Get the double value associated with a key.<p>
828     *
829     * @param key   a key string
830     * @return      the numeric value
831     * @throws JSONException if the key is not found or
832     *  if the value is not a Number object and cannot be converted to a number
833     */
834    public double getDouble(String key) throws JSONException {
835
836        Object o = get(key);
837        try {
838            return o instanceof Number ? ((Number)o).doubleValue() : Double.valueOf((String)o).doubleValue();
839        } catch (Exception e) {
840            throw new JSONException("JSONObject[" + quote(key) + "] is not a number.");
841        }
842    }
843
844    /**
845     * Get the int value associated with a key.<p>
846     *
847     * If the number value is too  large for an int, it will be clipped.<p>
848     *
849     * @param key   a key string
850     * @return      the integer value
851     * @throws   JSONException if the key is not found or if the value cannot
852     *  be converted to an integer
853     */
854    public int getInt(String key) throws JSONException {
855
856        Object o = get(key);
857        return o instanceof Number ? ((Number)o).intValue() : (int)getDouble(key);
858    }
859
860    /**
861     * Get the JSONArray value associated with a key.<p>
862     *
863     * @param key   a key string
864     * @return      a JSONArray which is the value
865     * @throws   JSONException if the key is not found or
866     *  if the value is not a JSONArray
867     */
868    public JSONArray getJSONArray(String key) throws JSONException {
869
870        Object o = get(key);
871        if (o instanceof JSONArray) {
872            return (JSONArray)o;
873        }
874        throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray.");
875    }
876
877    /**
878     * Get the JSONObject value associated with a key.<p>
879     *
880     * @param key   a key string
881     * @return      a JSONObject which is the value
882     * @throws   JSONException if the key is not found or
883     *  if the value is not a JSONObject
884     */
885    public JSONObject getJSONObject(String key) throws JSONException {
886
887        Object o = get(key);
888        if (o instanceof JSONObject) {
889            return (JSONObject)o;
890        }
891        throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject.");
892    }
893
894    /**
895     * Get the long value associated with a key.<p>
896     *
897     * If the number value is too long for a long, it will be clipped.<p>
898     *
899     * @param key   a key string
900     * @return      the long value.
901     * @throws   JSONException if the key is not found or if the value cannot
902     *  be converted to a long
903     */
904    public long getLong(String key) throws JSONException {
905
906        Object o = get(key);
907        return o instanceof Number ? ((Number)o).longValue() : (long)getDouble(key);
908    }
909
910    /**
911     * Get the string associated with a key.<p>
912     *
913     * @param key   a key string
914     * @return      a string which is the value
915     * @throws   JSONException if the key is not found
916     */
917    public String getString(String key) throws JSONException {
918
919        return get(key).toString();
920    }
921
922    /**
923     * Determine if the JSONObject contains a specific key.<p>
924     *
925     * @param key   a key string
926     * @return      true if the key exists in the JSONObject
927     */
928    public boolean has(String key) {
929
930        return m_map.containsKey(key);
931    }
932
933    /**
934     * Determine if the value associated with the key is null or if there is no value.<p>
935     *
936     * @param key   a key string
937     * @return      true if there is no value associated with the key or if
938     *  the value is the JSONObject.NULL object
939     */
940    public boolean isNull(String key) {
941
942        return JSONObject.NULL.equals(opt(key));
943    }
944
945    /**
946     * Get an enumeration of the keys of the JSONObject.<p>
947     *
948     * @return an iterator of the keys
949     */
950    public Iterator<String> keys() {
951
952        return m_map.keySet().iterator();
953    }
954
955    /**
956     * Gets the set of keys.<p>
957     *
958     * @return the set of keys
959     */
960    public Set<String> keySet() {
961
962        return m_map.keySet();
963    }
964
965    /**
966     * Get the number of keys stored in the JSONObject.<p>
967     *
968     * @return The number of keys in the JSONObject
969     */
970    public int length() {
971
972        return m_map.size();
973    }
974
975    /**
976     * Merges the current JSON object with the given one, modifying the this.<p>
977     *
978     * @param jo the JSON object to merge
979     * @param overwrite if to overwrite values
980     * @param deep if to recurse in object values
981     *
982     * @throws JSONException if a value is a non-finite number
983     *
984     * @since 7.6
985     */
986    public void merge(JSONObject jo, boolean overwrite, boolean deep) throws JSONException {
987
988        Iterator<String> it = jo.keys();
989        while (it.hasNext()) {
990            String key = it.next();
991            if (!has(key)) {
992                put(key, jo.get(key));
993                continue;
994            }
995            boolean recurse = deep && (jo.optJSONObject(key) != null) && (optJSONObject(key) != null);
996            if (overwrite && !recurse) {
997                put(key, jo.get(key));
998                continue;
999            }
1000            if (recurse) {
1001                getJSONObject(key).merge(jo.getJSONObject(key), overwrite, deep);
1002            }
1003        }
1004    }
1005
1006    /**
1007     * Produce a JSONArray containing the names of the elements of this JSONObject.<p>
1008     *
1009     * @return a JSONArray containing the key strings, or null if the JSONObject is empty.
1010     */
1011    public JSONArray names() {
1012
1013        JSONArray ja = new JSONArray();
1014        Iterator<String> keys = keys();
1015        while (keys.hasNext()) {
1016            ja.put(keys.next());
1017        }
1018        return ja.length() == 0 ? null : ja;
1019    }
1020
1021    /**
1022     * Get an optional value associated with a key.<p>
1023     *
1024     * @param key   a key string
1025     * @return      an object which is the value, or null if there is no value
1026     */
1027    public Object opt(String key) {
1028
1029        return key == null ? null : m_map.get(key);
1030    }
1031
1032    /**
1033     * Get an optional boolean associated with a key.<p>
1034     *
1035     * It returns false if there is no such key, or if the value is not
1036     * Boolean.TRUE or the String "true".<p>
1037     *
1038     * @param key   a key string
1039     * @return      the truth
1040     */
1041    public boolean optBoolean(String key) {
1042
1043        return optBoolean(key, false);
1044    }
1045
1046    /**
1047     * Get an optional boolean associated with a key.<p>
1048     *
1049     * It returns the defaultValue if there is no such key, or if it is not
1050     * a Boolean or the String "true" or "false" (case insensitive).<p>
1051     *
1052     * @param key              a key string
1053     * @param defaultValue     the default
1054     * @return      the truth
1055     */
1056    public boolean optBoolean(String key, boolean defaultValue) {
1057
1058        try {
1059            return getBoolean(key);
1060        } catch (Exception e) {
1061            return defaultValue;
1062        }
1063    }
1064
1065    /**
1066     * Get an optional double associated with a key,
1067     * or NaN if there is no such key or if its value is not a number.<p>
1068     *
1069     * If the value is a string, an attempt will be made to evaluate it as
1070     * a number.<p>
1071     *
1072     * @param key   a string which is the key
1073     * @return      an object which is the value
1074     */
1075    public double optDouble(String key) {
1076
1077        return optDouble(key, Double.NaN);
1078    }
1079
1080    /**
1081     * Get an optional double associated with a key, or the
1082     * defaultValue if there is no such key or if its value is not a number.<p>
1083     *
1084     * If the value is a string, an attempt will be made to evaluate it as
1085     * a number.<p>
1086     *
1087     * @param key   a key string
1088     * @param defaultValue     the default
1089     * @return      an object which is the value
1090     */
1091    public double optDouble(String key, double defaultValue) {
1092
1093        try {
1094            Object o = opt(key);
1095            return o instanceof Number ? ((Number)o).doubleValue() : new Double((String)o).doubleValue();
1096        } catch (Exception e) {
1097            return defaultValue;
1098        }
1099    }
1100
1101    /**
1102     * Get an optional int value associated with a key,
1103     * or zero if there is no such key or if the value is not a number.<p>
1104     *
1105     * If the value is a string, an attempt will be made to evaluate it as
1106     * a number.<p>
1107     *
1108     * @param key   a key string
1109     * @return      an object which is the value
1110     */
1111    public int optInt(String key) {
1112
1113        return optInt(key, 0);
1114    }
1115
1116    /**
1117     * Get an optional int value associated with a key,
1118     * or the default if there is no such key or if the value is not a number.<p>
1119     *
1120     * If the value is a string, an attempt will be made to evaluate it as
1121     * a number.<p>
1122     *
1123     * @param key   a key string
1124     * @param defaultValue     the default
1125     * @return      an object which is the value
1126     */
1127    public int optInt(String key, int defaultValue) {
1128
1129        try {
1130            return getInt(key);
1131        } catch (Exception e) {
1132            return defaultValue;
1133        }
1134    }
1135
1136    /**
1137     * Get an optional JSONArray associated with a key.<p>
1138     *
1139     * It returns null if there is no such key, or if its value is not a
1140     * JSONArray.<p>
1141     *
1142     * @param key   a key string
1143     * @return      a JSONArray which is the value
1144     */
1145    public JSONArray optJSONArray(String key) {
1146
1147        Object o = opt(key);
1148        return o instanceof JSONArray ? (JSONArray)o : null;
1149    }
1150
1151    /**
1152     * Get an optional JSONObject associated with a key.<p>
1153     *
1154     * It returns null if there is no such key, or if its value is not a
1155     * JSONObject.<p>
1156     *
1157     * @param key   a key string
1158     * @return      a JSONObject which is the value
1159     */
1160    public JSONObject optJSONObject(String key) {
1161
1162        Object o = opt(key);
1163        return o instanceof JSONObject ? (JSONObject)o : null;
1164    }
1165
1166    /**
1167     * Get an optional long value associated with a key,
1168     * or zero if there is no such key or if the value is not a number.<p>
1169     *
1170     * If the value is a string, an attempt will be made to evaluate it as
1171     * a number.<p>
1172     *
1173     * @param key   a key string
1174     * @return      an object which is the value
1175     */
1176    public long optLong(String key) {
1177
1178        return optLong(key, 0);
1179    }
1180
1181    /**
1182     * Get an optional long value associated with a key,
1183     * or the default if there is no such key or if the value is not a number.<p>
1184     *
1185     * If the value is a string, an attempt will be made to evaluate it as
1186     * a number.<p>
1187     *
1188     * @param key   a key string
1189     * @param defaultValue     the default
1190     * @return      an object which is the value
1191     */
1192    public long optLong(String key, long defaultValue) {
1193
1194        try {
1195            return getLong(key);
1196        } catch (Exception e) {
1197            return defaultValue;
1198        }
1199    }
1200
1201    /**
1202     * Get an optional string associated with a key.<p>
1203     *
1204     * It returns an empty string if there is no such key. If the value is not
1205     * a string and is not null, then it is coverted to a string.<p>
1206     *
1207     * @param key   a key string
1208     * @return      a string which is the value
1209     */
1210    public String optString(String key) {
1211
1212        return optString(key, "");
1213    }
1214
1215    /**
1216     * Get an optional string associated with a key.
1217     * It returns the defaultValue if there is no such key.<p>
1218     *
1219     * @param key   a key string
1220     * @param defaultValue     the default
1221     * @return      a string which is the value
1222     */
1223    public String optString(String key, String defaultValue) {
1224
1225        Object o = opt(key);
1226        return o != null ? o.toString() : defaultValue;
1227    }
1228
1229    /**
1230     * Put a key/boolean pair in the JSONObject.<p>
1231     *
1232     * @param key   a key string
1233     * @param value a boolean which is the value
1234     * @return this
1235     * @throws JSONException if the key is null
1236     */
1237    public JSONObject put(String key, boolean value) throws JSONException {
1238
1239        put(key, value ? Boolean.TRUE : Boolean.FALSE);
1240        return this;
1241    }
1242
1243    /**
1244     * Put a key/value pair in the JSONObject, where the value will be a
1245     * JSONArray which is produced from a Collection.<p>
1246     *
1247     * @param key   a key string
1248     * @param value a Collection value
1249     * @return      this
1250     * @throws JSONException if something goes wrong
1251     */
1252    public JSONObject put(String key, Collection<Object> value) throws JSONException {
1253
1254        put(key, new JSONArray(value));
1255        return this;
1256    }
1257
1258    /**
1259     * Put a key/double pair in the JSONObject.<p>
1260     *
1261     * @param key   a key string
1262     * @param value a double which is the value
1263     * @return this
1264     * @throws JSONException if the key is null or if the number is invalid.
1265     */
1266    public JSONObject put(String key, double value) throws JSONException {
1267
1268        put(key, new Double(value));
1269        return this;
1270    }
1271
1272    /**
1273     * Put a key/int pair in the JSONObject.<p>
1274     *
1275     * @param key   a key string
1276     * @param value an int which is the value
1277     * @return this
1278     * @throws JSONException if the key is null
1279     */
1280    public JSONObject put(String key, int value) throws JSONException {
1281
1282        put(key, new Integer(value));
1283        return this;
1284    }
1285
1286    /**
1287     * Put a key/long pair in the JSONObject.<p>
1288     *
1289     * @param key   a key string
1290     * @param value a long which is the value
1291     * @return this
1292     * @throws JSONException If the key is null
1293     */
1294    public JSONObject put(String key, long value) throws JSONException {
1295
1296        put(key, new Long(value));
1297        return this;
1298    }
1299
1300    /**
1301     * Put a key/value pair in the JSONObject, where the value will be a
1302     * JSONObject which is produced from a Map.<p>
1303     *
1304     * @param key   a key string
1305     * @param value a Map value
1306     * @return      this
1307     * @throws JSONException if something goes wrong
1308     */
1309    public JSONObject put(String key, Map<String, Object> value) throws JSONException {
1310
1311        put(key, new JSONObject(value));
1312        return this;
1313    }
1314
1315    /**
1316     * Put a key/value pair in the JSONObject.<p>
1317     *
1318     * If the value is null,
1319     * then the key will be removed from the JSONObject if it is present.<p>
1320     *
1321     * @param key   a key string
1322     * @param value an object which is the value. It should be of one of these
1323     *  types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
1324     *  or the JSONObject.NULL object
1325     * @return this
1326     * @throws JSONException if the value is non-finite number
1327     *  or if the key is null.
1328     */
1329    public JSONObject put(String key, Object value) throws JSONException {
1330
1331        if (key == null) {
1332            throw new JSONException("Null key.");
1333        }
1334        if (value != null) {
1335            testValidity(value);
1336            m_map.put(key, value);
1337        } else {
1338            remove(key);
1339        }
1340        return this;
1341    }
1342
1343    /**
1344     * Put a key/value pair in the JSONObject, but only if the
1345     * key and the value are both non-null.<p>
1346     *
1347     * @param key   a key string
1348     * @param value an object which is the value. It should be of one of these
1349     *  types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
1350     *  or the JSONObject.NULL object
1351     * @return this
1352     * @throws JSONException if the value is a non-finite number.
1353     */
1354    public JSONObject putOpt(String key, Object value) throws JSONException {
1355
1356        if ((key != null) && (value != null)) {
1357            put(key, value);
1358        }
1359        return this;
1360    }
1361
1362    /**
1363     * Remove a name and its value, if present.<p>
1364     *
1365     * @param key the name to be removed
1366     * @return the value that was associated with the name,
1367     * or null if there was no value
1368     */
1369    public Object remove(String key) {
1370
1371        return m_map.remove(key);
1372    }
1373
1374    /**
1375     * Get an enumeration of the keys of the JSONObject.<p>
1376     *
1377     * The keys will be sorted alphabetically.<p>
1378     *
1379     * @return an iterator of the keys
1380     */
1381    public Iterator<String> sortedKeys() {
1382
1383        return new TreeSet<String>(m_map.keySet()).iterator();
1384    }
1385
1386    /**
1387     * Produce a JSONArray containing the values of the members of this
1388     * JSONObject.<p>
1389     *
1390     * @param names a JSONArray containing a list of key strings. This
1391     * determines the sequence of the values in the result
1392     * @return a JSONArray of values
1393     * @throws JSONException if any of the values are non-finite numbers.
1394     */
1395    public JSONArray toJSONArray(JSONArray names) throws JSONException {
1396
1397        if ((names == null) || (names.length() == 0)) {
1398            return null;
1399        }
1400        JSONArray ja = new JSONArray();
1401        for (int i = 0; i < names.length(); i += 1) {
1402            ja.put(opt(names.getString(i)));
1403        }
1404        return ja;
1405    }
1406
1407    /**
1408     * Make a JSON text of this JSONObject.<p>
1409     *
1410     * For compactness, no whitespace
1411     * is added. If this would not result in a syntactically correct JSON text,
1412     * then null will be returned instead.<p>
1413     *
1414     * Warning: This method assumes that the data structure is acyclical.<p>
1415     *
1416     * @return a printable, displayable, portable, transmittable
1417     *  representation of the object, beginning
1418     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1419     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
1420     */
1421    @Override
1422    public String toString() {
1423
1424        try {
1425            Iterator<String> keys = keys();
1426            StringBuffer sb = new StringBuffer("{");
1427
1428            while (keys.hasNext()) {
1429                if (sb.length() > 1) {
1430                    sb.append(',');
1431                }
1432                Object o = keys.next();
1433                sb.append(quote(o.toString()));
1434                sb.append(':');
1435                sb.append(valueToString(m_map.get(o)));
1436            }
1437            sb.append('}');
1438            return sb.toString();
1439        } catch (Exception e) {
1440            return null;
1441        }
1442    }
1443
1444    /**
1445     * Make a pretty printed JSON text of this JSONObject.<p>
1446     *
1447     * Warning: This method assumes that the data structure is acyclical.<p>
1448     *
1449     * @param indentFactor the number of spaces to add to each level of
1450     *  indentation
1451     * @return a printable, displayable, portable, transmittable
1452     *  representation of the object, beginning
1453     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1454     *  with <code>}</code>&nbsp;<small>(right brace)</small>
1455     * @throws JSONException If the object contains an invalid number
1456     */
1457    public String toString(int indentFactor) throws JSONException {
1458
1459        return toString(indentFactor, 0);
1460    }
1461
1462    /**
1463     * Write the contents of the JSONObject as JSON text to a writer.
1464     * For compactness, no whitespace is added.<p>
1465     *
1466     * Warning: This method assumes that the data structure is acyclical.<p>
1467     *
1468     * @param writer the writer to write the contents to
1469     * @return the writer
1470     * @throws JSONException if something goes wrong
1471     */
1472    public Writer write(Writer writer) throws JSONException {
1473
1474        try {
1475            boolean b = false;
1476            Iterator<String> keys = keys();
1477            writer.write('{');
1478
1479            while (keys.hasNext()) {
1480                if (b) {
1481                    writer.write(',');
1482                }
1483                String k = keys.next();
1484                writer.write(quote(k.toString()));
1485                writer.write(':');
1486                Object v = m_map.get(k);
1487                if (v instanceof JSONObject) {
1488                    ((JSONObject)v).write(writer);
1489                } else if (v instanceof JSONArray) {
1490                    ((JSONArray)v).write(writer);
1491                } else {
1492                    writer.write(valueToString(v));
1493                }
1494                b = true;
1495            }
1496            writer.write('}');
1497            return writer;
1498        } catch (IOException e) {
1499            throw new JSONException(e);
1500        }
1501    }
1502
1503    /**
1504     * Make a pretty printed JSON text of this JSONObject.<p>
1505     *
1506     * Warning: This method assumes that the data structure is acyclical.<p>
1507     *
1508     * @param indentFactor the number of spaces to add to each level of
1509     *  indentation
1510     * @param indent the indentation of the top level
1511     * @return a printable, displayable, transmittable
1512     *  representation of the object, beginning
1513     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1514     *  with <code>}</code>&nbsp;<small>(right brace)</small>
1515     * @throws JSONException if the object contains an invalid number
1516     */
1517    String toString(int indentFactor, int indent) throws JSONException {
1518
1519        int j;
1520        int n = length();
1521        if (n == 0) {
1522            return "{}";
1523        }
1524        Iterator<String> keys = sortedKeys();
1525        StringBuffer sb = new StringBuffer("{");
1526        int newindent = indent + indentFactor;
1527        String key;
1528        if (n == 1) {
1529            key = keys.next();
1530            sb.append(quote(key));
1531            sb.append(": ");
1532            sb.append(valueToString(m_map.get(key), indentFactor, indent));
1533        } else {
1534            while (keys.hasNext()) {
1535                key = keys.next();
1536                if (sb.length() > 1) {
1537                    sb.append(",\n");
1538                } else {
1539                    sb.append('\n');
1540                }
1541                for (j = 0; j < newindent; j += 1) {
1542                    sb.append(' ');
1543                }
1544                sb.append(quote(key));
1545                sb.append(": ");
1546                sb.append(valueToString(m_map.get(key), indentFactor, newindent));
1547            }
1548            if (sb.length() > 1) {
1549                sb.append('\n');
1550                for (j = 0; j < indent; j += 1) {
1551                    sb.append(' ');
1552                }
1553            }
1554        }
1555        sb.append('}');
1556        return sb.toString();
1557    }
1558
1559    /**
1560     * Returns if the given class is a standard property.<p>
1561     *
1562     * @param clazz the class
1563     *
1564     * @return <code>true</code> if the given class is a standard property
1565     */
1566    private boolean isStandardProperty(Class<?> clazz) {
1567
1568        return clazz.isPrimitive()
1569            || clazz.isAssignableFrom(Byte.class)
1570            || clazz.isAssignableFrom(Short.class)
1571            || clazz.isAssignableFrom(Integer.class)
1572            || clazz.isAssignableFrom(Long.class)
1573            || clazz.isAssignableFrom(Float.class)
1574            || clazz.isAssignableFrom(Double.class)
1575            || clazz.isAssignableFrom(Character.class)
1576            || clazz.isAssignableFrom(String.class)
1577            || clazz.isAssignableFrom(Boolean.class);
1578    }
1579
1580    /**
1581     * Populates the internal map.<p>
1582     *
1583     * @param bean the bean
1584     * @param includeSuperClass flag indicating if super class properties should be included
1585     */
1586    @SuppressWarnings("unchecked")
1587    private void populateInternalMap(Object bean, boolean includeSuperClass) {
1588
1589        Class<?> klass = bean.getClass();
1590
1591        //If klass.getSuperClass is System class then includeSuperClass = false;
1592
1593        if (klass.getClassLoader() == null) {
1594            includeSuperClass = false;
1595        }
1596
1597        Method[] methods = (includeSuperClass) ? klass.getMethods() : klass.getDeclaredMethods();
1598        for (int i = 0; i < methods.length; i += 1) {
1599            try {
1600                Method method = methods[i];
1601                String name = method.getName();
1602                String key = "";
1603                if (name.startsWith("get")) {
1604                    key = name.substring(3);
1605                } else if (name.startsWith("is")) {
1606                    key = name.substring(2);
1607                }
1608                if ((key.length() > 0)
1609                    && Character.isUpperCase(key.charAt(0))
1610                    && (method.getParameterTypes().length == 0)) {
1611                    if (key.length() == 1) {
1612                        key = key.toLowerCase();
1613                    } else if (!Character.isUpperCase(key.charAt(1))) {
1614                        key = key.substring(0, 1).toLowerCase() + key.substring(1);
1615                    }
1616
1617                    Object result = method.invoke(bean, (Object[])null);
1618                    if (result == null) {
1619                        m_map.put(key, NULL);
1620                    } else if (result.getClass().isArray()) {
1621                        m_map.put(key, new JSONArray(result, includeSuperClass));
1622                    } else if (result instanceof Collection) { //List or Set
1623                        m_map.put(key, new JSONArray((Collection<Object>)result, includeSuperClass));
1624                    } else if (result instanceof Map) {
1625                        m_map.put(key, new JSONObject((Map<String, Object>)result, includeSuperClass));
1626                    } else if (isStandardProperty(result.getClass())) { //Primitives, String and Wrapper
1627                        m_map.put(key, result);
1628                    } else {
1629                        if (result.getClass().getPackage().getName().startsWith("java")
1630                            || (result.getClass().getClassLoader() == null)) {
1631                            m_map.put(key, result.toString());
1632                        } else { //User defined Objects
1633                            m_map.put(key, new JSONObject(result, includeSuperClass));
1634                        }
1635                    }
1636                }
1637            } catch (Exception e) {
1638                throw new RuntimeException(e);
1639            }
1640        }
1641    }
1642}