001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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 & Co. KG, 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 == '\'')
591                        || (c < ' ')
592                        || ((c >= '\u0080') && (c < '\u00a0'))
593                        || ((c >= '\u2000') && (c < '\u2100'))) {
594                        t = "000" + Integer.toHexString(c);
595                        sb.append("\\u" + t.substring(t.length() - 4));
596                    } else {
597                        sb.append(c);
598                    }
599            }
600        }
601        sb.append('"');
602        return sb.toString();
603    }
604
605    /**
606     * Make a JSON text of an Object value.<p>
607     *
608     * If the object has an value.toJSONString() method, then that method will be used to produce
609     * the JSON text. The method is required to produce a strictly
610     * conforming text. If the object does not contain a toJSONString
611     * method (which is the most common case), then a text will be
612     * produced by other means. If the value is an array or Collection,
613     * then a JSONArray will be made from it and its toJSONString method
614     * will be called. If the value is a MAP, then a JSONObject will be made
615     * from it and its toJSONString method will be called. Otherwise, the
616     * value's toString method will be called, and the result will be quoted.<p>
617     *
618     * Warning: This method assumes that the data structure is acyclical.<p>
619     *
620     * @param value the value to be serialized
621     * @return a printable, displayable, transmittable
622     *  representation of the object, beginning
623     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
624     *  with <code>}</code>&nbsp;<small>(right brace)</small>
625     * @throws JSONException if the value is or contains an invalid number
626     */
627    @SuppressWarnings("unchecked")
628    public static String valueToString(Object value) throws JSONException {
629
630        if ((value == null) || value.equals(null)) {
631            return "null";
632        }
633        if (value instanceof I_JSONString) {
634            Object o;
635            try {
636                o = ((I_JSONString)value).toJSONString();
637            } catch (Exception e) {
638                throw new JSONException(e);
639            }
640            if (o instanceof String) {
641                return (String)o;
642            }
643            throw new JSONException("Bad value from toJSONString: " + o);
644        }
645        if (value instanceof Number) {
646            return numberToString((Number)value);
647        }
648        if ((value instanceof Boolean) || (value instanceof JSONObject) || (value instanceof JSONArray)) {
649            return value.toString();
650        }
651        if (value instanceof Map) {
652            return new JSONObject((Map<String, Object>)value).toString();
653        }
654        if (value instanceof Collection) {
655            return new JSONArray((Collection<Object>)value).toString();
656        }
657        if (value.getClass().isArray()) {
658            return new JSONArray(value).toString();
659        }
660        return quote(value.toString());
661    }
662
663    /**
664     * Make a pretty printed JSON text of an object value.<p>
665     *
666     * Warning: This method assumes that the data structure is acyclical.<p>
667     *
668     * @param value the value to be serialized
669     * @param indentFactor the number of spaces to add to each level of
670     *  indentation
671     * @param indent the indentation of the top level
672     * @return a printable, displayable, transmittable
673     *  representation of the object, beginning
674     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
675     *  with <code>}</code>&nbsp;<small>(right brace)</small>
676     * @throws JSONException if the object contains an invalid number
677     */
678    @SuppressWarnings("unchecked")
679    public static String valueToString(Object value, int indentFactor, int indent) throws JSONException {
680
681        if ((value == null) || value.equals(null)) {
682            return "null";
683        }
684        try {
685            if (value instanceof I_JSONString) {
686                Object o = ((I_JSONString)value).toJSONString();
687                if (o instanceof String) {
688                    return (String)o;
689                }
690            }
691        } catch (Exception e) {
692            /* forget about it */
693        }
694        if (value instanceof Number) {
695            return numberToString((Number)value);
696        }
697        if (value instanceof Boolean) {
698            return value.toString();
699        }
700        if (value instanceof JSONObject) {
701            return ((JSONObject)value).toString(indentFactor, indent);
702        }
703        if (value instanceof JSONArray) {
704            return ((JSONArray)value).toString(indentFactor, indent);
705        }
706        if (value instanceof Map) {
707            return new JSONObject((Map<String, Object>)value).toString(indentFactor, indent);
708        }
709        if (value instanceof Collection) {
710            return new JSONArray((Collection<Object>)value).toString(indentFactor, indent);
711        }
712        if (value.getClass().isArray()) {
713            return new JSONArray(value).toString(indentFactor, indent);
714        }
715        return quote(value.toString());
716    }
717
718    /**
719     * Throws an exception if the object is an NaN or infinite number.<p>
720     *
721     * @param o the object to test
722     * @throws JSONException if o is a non-finite number
723     */
724    static void testValidity(Object o) throws JSONException {
725
726        if (o != null) {
727            if (o instanceof Double) {
728                if (((Double)o).isInfinite() || ((Double)o).isNaN()) {
729                    throw new JSONException("JSON does not allow non-finite numbers.");
730                }
731            } else if (o instanceof Float) {
732                if (((Float)o).isInfinite() || ((Float)o).isNaN()) {
733                    throw new JSONException("JSON does not allow non-finite numbers.");
734                }
735            }
736        }
737    }
738
739    /**
740     * Accumulate values under a key.<p>
741     *
742     * It is similar to the put method except
743     * that if there is already an object stored under the key then a
744     * JSONArray is stored under the key to hold all of the accumulated values.
745     * If there is already a JSONArray, then the new value is appended to it.
746     * In contrast, the put method replaces the previous value.<p>
747     *
748     * @param key   a key string
749     * @param value an object to be accumulated under the key
750     * @return this
751     * @throws JSONException if the value is an invalid number or if the key is null
752     */
753    public JSONObject accumulate(String key, Object value) throws JSONException {
754
755        testValidity(value);
756        Object o = opt(key);
757        if (o == null) {
758            put(key, value instanceof JSONArray ? new JSONArray().put(value) : value);
759        } else if (o instanceof JSONArray) {
760            ((JSONArray)o).put(value);
761        } else {
762            put(key, new JSONArray().put(o).put(value));
763        }
764        return this;
765    }
766
767    /**
768     * Append values to the array under a key.<p>
769     *
770     * If the key does not exist in the
771     * JSONObject, then the key is put in the JSONObject with its value being a
772     * JSONArray containing the value parameter. If the key was already
773     * associated with a JSONArray, then the value parameter is appended to it.<p>
774     *
775     * @param key   a key string
776     * @param value an object to be accumulated under the key
777     * @return this
778     * @throws JSONException if the key is null or if the current value
779     *  associated with the key is not a JSONArray
780     */
781    public JSONObject append(String key, Object value) throws JSONException {
782
783        testValidity(value);
784        Object o = opt(key);
785        if (o == null) {
786            put(key, new JSONArray().put(value));
787        } else if (o instanceof JSONArray) {
788            put(key, ((JSONArray)o).put(value));
789        } else {
790            throw new JSONException("JSONObject[" + key + "] is not a JSONArray.");
791        }
792        return this;
793    }
794
795    /**
796     * Get the value object associated with a key.<p>
797     *
798     * @param key   a key string
799     * @return      the object associated with the key
800     * @throws   JSONException if the key is not found
801     */
802    public Object get(String key) throws JSONException {
803
804        Object o = opt(key);
805        if (o == null) {
806            throw new JSONException("JSONObject[" + quote(key) + "] not found.");
807        }
808        return o;
809    }
810
811    /**
812     * Get the boolean value associated with a key.<p>
813     *
814     * @param key   A key string
815     * @return      the truth
816     * @throws   JSONException if the value is not a Boolean or the String "true" or "false"
817     */
818    public boolean getBoolean(String key) throws JSONException {
819
820        Object o = get(key);
821        if (o.equals(Boolean.FALSE) || ((o instanceof String) && ((String)o).equalsIgnoreCase("false"))) {
822            return false;
823        } else if (o.equals(Boolean.TRUE) || ((o instanceof String) && ((String)o).equalsIgnoreCase("true"))) {
824            return true;
825        }
826        throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean.");
827    }
828
829    /**
830     * Get the double value associated with a key.<p>
831     *
832     * @param key   a key string
833     * @return      the numeric value
834     * @throws JSONException if the key is not found or
835     *  if the value is not a Number object and cannot be converted to a number
836     */
837    public double getDouble(String key) throws JSONException {
838
839        Object o = get(key);
840        try {
841            return o instanceof Number ? ((Number)o).doubleValue() : Double.valueOf((String)o).doubleValue();
842        } catch (Exception e) {
843            throw new JSONException("JSONObject[" + quote(key) + "] is not a number.");
844        }
845    }
846
847    /**
848     * Get the int value associated with a key.<p>
849     *
850     * If the number value is too  large for an int, it will be clipped.<p>
851     *
852     * @param key   a key string
853     * @return      the integer value
854     * @throws   JSONException if the key is not found or if the value cannot
855     *  be converted to an integer
856     */
857    public int getInt(String key) throws JSONException {
858
859        Object o = get(key);
860        return o instanceof Number ? ((Number)o).intValue() : (int)getDouble(key);
861    }
862
863    /**
864     * Get the JSONArray value associated with a key.<p>
865     *
866     * @param key   a key string
867     * @return      a JSONArray which is the value
868     * @throws   JSONException if the key is not found or
869     *  if the value is not a JSONArray
870     */
871    public JSONArray getJSONArray(String key) throws JSONException {
872
873        Object o = get(key);
874        if (o instanceof JSONArray) {
875            return (JSONArray)o;
876        }
877        throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray.");
878    }
879
880    /**
881     * Get the JSONObject value associated with a key.<p>
882     *
883     * @param key   a key string
884     * @return      a JSONObject which is the value
885     * @throws   JSONException if the key is not found or
886     *  if the value is not a JSONObject
887     */
888    public JSONObject getJSONObject(String key) throws JSONException {
889
890        Object o = get(key);
891        if (o instanceof JSONObject) {
892            return (JSONObject)o;
893        }
894        throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject.");
895    }
896
897    /**
898     * Get the long value associated with a key.<p>
899     *
900     * If the number value is too long for a long, it will be clipped.<p>
901     *
902     * @param key   a key string
903     * @return      the long value.
904     * @throws   JSONException if the key is not found or if the value cannot
905     *  be converted to a long
906     */
907    public long getLong(String key) throws JSONException {
908
909        Object o = get(key);
910        return o instanceof Number ? ((Number)o).longValue() : (long)getDouble(key);
911    }
912
913    /**
914     * Get the string associated with a key.<p>
915     *
916     * @param key   a key string
917     * @return      a string which is the value
918     * @throws   JSONException if the key is not found
919     */
920    public String getString(String key) throws JSONException {
921
922        return get(key).toString();
923    }
924
925    /**
926     * Determine if the JSONObject contains a specific key.<p>
927     *
928     * @param key   a key string
929     * @return      true if the key exists in the JSONObject
930     */
931    public boolean has(String key) {
932
933        return m_map.containsKey(key);
934    }
935
936    /**
937     * Determine if the value associated with the key is null or if there is no value.<p>
938     *
939     * @param key   a key string
940     * @return      true if there is no value associated with the key or if
941     *  the value is the JSONObject.NULL object
942     */
943    public boolean isNull(String key) {
944
945        return JSONObject.NULL.equals(opt(key));
946    }
947
948    /**
949     * Get an enumeration of the keys of the JSONObject.<p>
950     *
951     * @return an iterator of the keys
952     */
953    public Iterator<String> keys() {
954
955        return m_map.keySet().iterator();
956    }
957
958    /**
959     * Gets the set of keys.<p>
960     *
961     * @return the set of keys
962     */
963    public Set<String> keySet() {
964
965        return m_map.keySet();
966    }
967
968    /**
969     * Get the number of keys stored in the JSONObject.<p>
970     *
971     * @return The number of keys in the JSONObject
972     */
973    public int length() {
974
975        return m_map.size();
976    }
977
978    /**
979     * Merges the current JSON object with the given one, modifying the this.<p>
980     *
981     * @param jo the JSON object to merge
982     * @param overwrite if to overwrite values
983     * @param deep if to recurse in object values
984     *
985     * @throws JSONException if a value is a non-finite number
986     *
987     * @since 7.6
988     */
989    public void merge(JSONObject jo, boolean overwrite, boolean deep) throws JSONException {
990
991        Iterator<String> it = jo.keys();
992        while (it.hasNext()) {
993            String key = it.next();
994            if (!has(key)) {
995                put(key, jo.get(key));
996                continue;
997            }
998            boolean recurse = deep && (jo.optJSONObject(key) != null) && (optJSONObject(key) != null);
999            if (overwrite && !recurse) {
1000                put(key, jo.get(key));
1001                continue;
1002            }
1003            if (recurse) {
1004                getJSONObject(key).merge(jo.getJSONObject(key), overwrite, deep);
1005            }
1006        }
1007    }
1008
1009    /**
1010     * Produce a JSONArray containing the names of the elements of this JSONObject.<p>
1011     *
1012     * @return a JSONArray containing the key strings, or null if the JSONObject is empty.
1013     */
1014    public JSONArray names() {
1015
1016        JSONArray ja = new JSONArray();
1017        Iterator<String> keys = keys();
1018        while (keys.hasNext()) {
1019            ja.put(keys.next());
1020        }
1021        return ja.length() == 0 ? null : ja;
1022    }
1023
1024    /**
1025     * Get an optional value associated with a key.<p>
1026     *
1027     * @param key   a key string
1028     * @return      an object which is the value, or null if there is no value
1029     */
1030    public Object opt(String key) {
1031
1032        return key == null ? null : m_map.get(key);
1033    }
1034
1035    /**
1036     * Get an optional boolean associated with a key.<p>
1037     *
1038     * It returns false if there is no such key, or if the value is not
1039     * Boolean.TRUE or the String "true".<p>
1040     *
1041     * @param key   a key string
1042     * @return      the truth
1043     */
1044    public boolean optBoolean(String key) {
1045
1046        return optBoolean(key, false);
1047    }
1048
1049    /**
1050     * Get an optional boolean associated with a key.<p>
1051     *
1052     * It returns the defaultValue if there is no such key, or if it is not
1053     * a Boolean or the String "true" or "false" (case insensitive).<p>
1054     *
1055     * @param key              a key string
1056     * @param defaultValue     the default
1057     * @return      the truth
1058     */
1059    public boolean optBoolean(String key, boolean defaultValue) {
1060
1061        try {
1062            return getBoolean(key);
1063        } catch (Exception e) {
1064            return defaultValue;
1065        }
1066    }
1067
1068    /**
1069     * Get an optional double associated with a key,
1070     * or NaN if there is no such key or if its value is not a number.<p>
1071     *
1072     * If the value is a string, an attempt will be made to evaluate it as
1073     * a number.<p>
1074     *
1075     * @param key   a string which is the key
1076     * @return      an object which is the value
1077     */
1078    public double optDouble(String key) {
1079
1080        return optDouble(key, Double.NaN);
1081    }
1082
1083    /**
1084     * Get an optional double associated with a key, or the
1085     * defaultValue if there is no such key or if its value is not a number.<p>
1086     *
1087     * If the value is a string, an attempt will be made to evaluate it as
1088     * a number.<p>
1089     *
1090     * @param key   a key string
1091     * @param defaultValue     the default
1092     * @return      an object which is the value
1093     */
1094    public double optDouble(String key, double defaultValue) {
1095
1096        try {
1097            Object o = opt(key);
1098            return o instanceof Number ? ((Number)o).doubleValue() : new Double((String)o).doubleValue();
1099        } catch (Exception e) {
1100            return defaultValue;
1101        }
1102    }
1103
1104    /**
1105     * Get an optional int value associated with a key,
1106     * or zero if there is no such key or if the value is not a number.<p>
1107     *
1108     * If the value is a string, an attempt will be made to evaluate it as
1109     * a number.<p>
1110     *
1111     * @param key   a key string
1112     * @return      an object which is the value
1113     */
1114    public int optInt(String key) {
1115
1116        return optInt(key, 0);
1117    }
1118
1119    /**
1120     * Get an optional int value associated with a key,
1121     * or the default if there is no such key or if the value is not a number.<p>
1122     *
1123     * If the value is a string, an attempt will be made to evaluate it as
1124     * a number.<p>
1125     *
1126     * @param key   a key string
1127     * @param defaultValue     the default
1128     * @return      an object which is the value
1129     */
1130    public int optInt(String key, int defaultValue) {
1131
1132        try {
1133            return getInt(key);
1134        } catch (Exception e) {
1135            return defaultValue;
1136        }
1137    }
1138
1139    /**
1140     * Get an optional JSONArray associated with a key.<p>
1141     *
1142     * It returns null if there is no such key, or if its value is not a
1143     * JSONArray.<p>
1144     *
1145     * @param key   a key string
1146     * @return      a JSONArray which is the value
1147     */
1148    public JSONArray optJSONArray(String key) {
1149
1150        Object o = opt(key);
1151        return o instanceof JSONArray ? (JSONArray)o : null;
1152    }
1153
1154    /**
1155     * Get an optional JSONObject associated with a key.<p>
1156     *
1157     * It returns null if there is no such key, or if its value is not a
1158     * JSONObject.<p>
1159     *
1160     * @param key   a key string
1161     * @return      a JSONObject which is the value
1162     */
1163    public JSONObject optJSONObject(String key) {
1164
1165        Object o = opt(key);
1166        return o instanceof JSONObject ? (JSONObject)o : null;
1167    }
1168
1169    /**
1170     * Get an optional long value associated with a key,
1171     * or zero if there is no such key or if the value is not a number.<p>
1172     *
1173     * If the value is a string, an attempt will be made to evaluate it as
1174     * a number.<p>
1175     *
1176     * @param key   a key string
1177     * @return      an object which is the value
1178     */
1179    public long optLong(String key) {
1180
1181        return optLong(key, 0);
1182    }
1183
1184    /**
1185     * Get an optional long value associated with a key,
1186     * or the default if there is no such key or if the value is not a number.<p>
1187     *
1188     * If the value is a string, an attempt will be made to evaluate it as
1189     * a number.<p>
1190     *
1191     * @param key   a key string
1192     * @param defaultValue     the default
1193     * @return      an object which is the value
1194     */
1195    public long optLong(String key, long defaultValue) {
1196
1197        try {
1198            return getLong(key);
1199        } catch (Exception e) {
1200            return defaultValue;
1201        }
1202    }
1203
1204    /**
1205     * Get an optional string associated with a key.<p>
1206     *
1207     * It returns an empty string if there is no such key. If the value is not
1208     * a string and is not null, then it is coverted to a string.<p>
1209     *
1210     * @param key   a key string
1211     * @return      a string which is the value
1212     */
1213    public String optString(String key) {
1214
1215        return optString(key, "");
1216    }
1217
1218    /**
1219     * Get an optional string associated with a key.
1220     * It returns the defaultValue if there is no such key.<p>
1221     *
1222     * @param key   a key string
1223     * @param defaultValue     the default
1224     * @return      a string which is the value
1225     */
1226    public String optString(String key, String defaultValue) {
1227
1228        Object o = opt(key);
1229        return o != null ? o.toString() : defaultValue;
1230    }
1231
1232    /**
1233     * Put a key/boolean pair in the JSONObject.<p>
1234     *
1235     * @param key   a key string
1236     * @param value a boolean which is the value
1237     * @return this
1238     * @throws JSONException if the key is null
1239     */
1240    public JSONObject put(String key, boolean value) throws JSONException {
1241
1242        put(key, value ? Boolean.TRUE : Boolean.FALSE);
1243        return this;
1244    }
1245
1246    /**
1247     * Put a key/value pair in the JSONObject, where the value will be a
1248     * JSONArray which is produced from a Collection.<p>
1249     *
1250     * @param key   a key string
1251     * @param value a Collection value
1252     * @return      this
1253     * @throws JSONException if something goes wrong
1254     */
1255    public JSONObject put(String key, Collection<Object> value) throws JSONException {
1256
1257        put(key, new JSONArray(value));
1258        return this;
1259    }
1260
1261    /**
1262     * Put a key/double pair in the JSONObject.<p>
1263     *
1264     * @param key   a key string
1265     * @param value a double which is the value
1266     * @return this
1267     * @throws JSONException if the key is null or if the number is invalid.
1268     */
1269    public JSONObject put(String key, double value) throws JSONException {
1270
1271        put(key, new Double(value));
1272        return this;
1273    }
1274
1275    /**
1276     * Put a key/int pair in the JSONObject.<p>
1277     *
1278     * @param key   a key string
1279     * @param value an int which is the value
1280     * @return this
1281     * @throws JSONException if the key is null
1282     */
1283    public JSONObject put(String key, int value) throws JSONException {
1284
1285        put(key, new Integer(value));
1286        return this;
1287    }
1288
1289    /**
1290     * Put a key/long pair in the JSONObject.<p>
1291     *
1292     * @param key   a key string
1293     * @param value a long which is the value
1294     * @return this
1295     * @throws JSONException If the key is null
1296     */
1297    public JSONObject put(String key, long value) throws JSONException {
1298
1299        put(key, new Long(value));
1300        return this;
1301    }
1302
1303    /**
1304     * Put a key/value pair in the JSONObject, where the value will be a
1305     * JSONObject which is produced from a Map.<p>
1306     *
1307     * @param key   a key string
1308     * @param value a Map value
1309     * @return      this
1310     * @throws JSONException if something goes wrong
1311     */
1312    public JSONObject put(String key, Map<String, Object> value) throws JSONException {
1313
1314        put(key, new JSONObject(value));
1315        return this;
1316    }
1317
1318    /**
1319     * Put a key/value pair in the JSONObject.<p>
1320     *
1321     * If the value is null,
1322     * then the key will be removed from the JSONObject if it is present.<p>
1323     *
1324     * @param key   a key string
1325     * @param value an object which is the value. It should be of one of these
1326     *  types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
1327     *  or the JSONObject.NULL object
1328     * @return this
1329     * @throws JSONException if the value is non-finite number
1330     *  or if the key is null.
1331     */
1332    public JSONObject put(String key, Object value) throws JSONException {
1333
1334        if (key == null) {
1335            throw new JSONException("Null key.");
1336        }
1337        if (value != null) {
1338            testValidity(value);
1339            m_map.put(key, value);
1340        } else {
1341            remove(key);
1342        }
1343        return this;
1344    }
1345
1346    /**
1347     * Put a key/value pair in the JSONObject, but only if the
1348     * key and the value are both non-null.<p>
1349     *
1350     * @param key   a key string
1351     * @param value an object which is the value. It should be of one of these
1352     *  types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
1353     *  or the JSONObject.NULL object
1354     * @return this
1355     * @throws JSONException if the value is a non-finite number.
1356     */
1357    public JSONObject putOpt(String key, Object value) throws JSONException {
1358
1359        if ((key != null) && (value != null)) {
1360            put(key, value);
1361        }
1362        return this;
1363    }
1364
1365    /**
1366     * Remove a name and its value, if present.<p>
1367     *
1368     * @param key the name to be removed
1369     * @return the value that was associated with the name,
1370     * or null if there was no value
1371     */
1372    public Object remove(String key) {
1373
1374        return m_map.remove(key);
1375    }
1376
1377    /**
1378     * Get an enumeration of the keys of the JSONObject.<p>
1379     *
1380     * The keys will be sorted alphabetically.<p>
1381     *
1382     * @return an iterator of the keys
1383     */
1384    public Iterator<String> sortedKeys() {
1385
1386        return new TreeSet<String>(m_map.keySet()).iterator();
1387    }
1388
1389    /**
1390     * Produce a JSONArray containing the values of the members of this
1391     * JSONObject.<p>
1392     *
1393     * @param names a JSONArray containing a list of key strings. This
1394     * determines the sequence of the values in the result
1395     * @return a JSONArray of values
1396     * @throws JSONException if any of the values are non-finite numbers.
1397     */
1398    public JSONArray toJSONArray(JSONArray names) throws JSONException {
1399
1400        if ((names == null) || (names.length() == 0)) {
1401            return null;
1402        }
1403        JSONArray ja = new JSONArray();
1404        for (int i = 0; i < names.length(); i += 1) {
1405            ja.put(opt(names.getString(i)));
1406        }
1407        return ja;
1408    }
1409
1410    /**
1411     * Make a JSON text of this JSONObject.<p>
1412     *
1413     * For compactness, no whitespace
1414     * is added. If this would not result in a syntactically correct JSON text,
1415     * then null will be returned instead.<p>
1416     *
1417     * Warning: This method assumes that the data structure is acyclical.<p>
1418     *
1419     * @return a printable, displayable, portable, transmittable
1420     *  representation of the object, beginning
1421     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1422     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
1423     */
1424    @Override
1425    public String toString() {
1426
1427        try {
1428            Iterator<String> keys = keys();
1429            StringBuffer sb = new StringBuffer("{");
1430
1431            while (keys.hasNext()) {
1432                if (sb.length() > 1) {
1433                    sb.append(',');
1434                }
1435                Object o = keys.next();
1436                sb.append(quote(o.toString()));
1437                sb.append(':');
1438                sb.append(valueToString(m_map.get(o)));
1439            }
1440            sb.append('}');
1441            return sb.toString();
1442        } catch (Exception e) {
1443            return null;
1444        }
1445    }
1446
1447    /**
1448     * Make a pretty printed JSON text of this JSONObject.<p>
1449     *
1450     * Warning: This method assumes that the data structure is acyclical.<p>
1451     *
1452     * @param indentFactor the number of spaces to add to each level of
1453     *  indentation
1454     * @return a printable, displayable, portable, transmittable
1455     *  representation of the object, beginning
1456     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1457     *  with <code>}</code>&nbsp;<small>(right brace)</small>
1458     * @throws JSONException If the object contains an invalid number
1459     */
1460    public String toString(int indentFactor) throws JSONException {
1461
1462        return toString(indentFactor, 0);
1463    }
1464
1465    /**
1466     * Write the contents of the JSONObject as JSON text to a writer.
1467     * For compactness, no whitespace is added.<p>
1468     *
1469     * Warning: This method assumes that the data structure is acyclical.<p>
1470     *
1471     * @param writer the writer to write the contents to
1472     * @return the writer
1473     * @throws JSONException if something goes wrong
1474     */
1475    public Writer write(Writer writer) throws JSONException {
1476
1477        try {
1478            boolean b = false;
1479            Iterator<String> keys = keys();
1480            writer.write('{');
1481
1482            while (keys.hasNext()) {
1483                if (b) {
1484                    writer.write(',');
1485                }
1486                String k = keys.next();
1487                writer.write(quote(k.toString()));
1488                writer.write(':');
1489                Object v = m_map.get(k);
1490                if (v instanceof JSONObject) {
1491                    ((JSONObject)v).write(writer);
1492                } else if (v instanceof JSONArray) {
1493                    ((JSONArray)v).write(writer);
1494                } else {
1495                    writer.write(valueToString(v));
1496                }
1497                b = true;
1498            }
1499            writer.write('}');
1500            return writer;
1501        } catch (IOException e) {
1502            throw new JSONException(e);
1503        }
1504    }
1505
1506    /**
1507     * Make a pretty printed JSON text of this JSONObject.<p>
1508     *
1509     * Warning: This method assumes that the data structure is acyclical.<p>
1510     *
1511     * @param indentFactor the number of spaces to add to each level of
1512     *  indentation
1513     * @param indent the indentation of the top level
1514     * @return a printable, displayable, transmittable
1515     *  representation of the object, beginning
1516     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1517     *  with <code>}</code>&nbsp;<small>(right brace)</small>
1518     * @throws JSONException if the object contains an invalid number
1519     */
1520    String toString(int indentFactor, int indent) throws JSONException {
1521
1522        int j;
1523        int n = length();
1524        if (n == 0) {
1525            return "{}";
1526        }
1527        Iterator<String> keys = sortedKeys();
1528        StringBuffer sb = new StringBuffer("{");
1529        int newindent = indent + indentFactor;
1530        String key;
1531        if (n == 1) {
1532            key = keys.next();
1533            sb.append(quote(key));
1534            sb.append(": ");
1535            sb.append(valueToString(m_map.get(key), indentFactor, indent));
1536        } else {
1537            while (keys.hasNext()) {
1538                key = keys.next();
1539                if (sb.length() > 1) {
1540                    sb.append(",\n");
1541                } else {
1542                    sb.append('\n');
1543                }
1544                for (j = 0; j < newindent; j += 1) {
1545                    sb.append(' ');
1546                }
1547                sb.append(quote(key));
1548                sb.append(": ");
1549                sb.append(valueToString(m_map.get(key), indentFactor, newindent));
1550            }
1551            if (sb.length() > 1) {
1552                sb.append('\n');
1553                for (j = 0; j < indent; j += 1) {
1554                    sb.append(' ');
1555                }
1556            }
1557        }
1558        sb.append('}');
1559        return sb.toString();
1560    }
1561
1562    /**
1563     * Returns if the given class is a standard property.<p>
1564     *
1565     * @param clazz the class
1566     *
1567     * @return <code>true</code> if the given class is a standard property
1568     */
1569    private boolean isStandardProperty(Class<?> clazz) {
1570
1571        return clazz.isPrimitive()
1572            || clazz.isAssignableFrom(Byte.class)
1573            || clazz.isAssignableFrom(Short.class)
1574            || clazz.isAssignableFrom(Integer.class)
1575            || clazz.isAssignableFrom(Long.class)
1576            || clazz.isAssignableFrom(Float.class)
1577            || clazz.isAssignableFrom(Double.class)
1578            || clazz.isAssignableFrom(Character.class)
1579            || clazz.isAssignableFrom(String.class)
1580            || clazz.isAssignableFrom(Boolean.class);
1581    }
1582
1583    /**
1584     * Populates the internal map.<p>
1585     *
1586     * @param bean the bean
1587     * @param includeSuperClass flag indicating if super class properties should be included
1588     */
1589    @SuppressWarnings("unchecked")
1590    private void populateInternalMap(Object bean, boolean includeSuperClass) {
1591
1592        Class<?> klass = bean.getClass();
1593
1594        //If klass.getSuperClass is System class then includeSuperClass = false;
1595
1596        if (klass.getClassLoader() == null) {
1597            includeSuperClass = false;
1598        }
1599
1600        Method[] methods = (includeSuperClass) ? klass.getMethods() : klass.getDeclaredMethods();
1601        for (int i = 0; i < methods.length; i += 1) {
1602            try {
1603                Method method = methods[i];
1604                String name = method.getName();
1605                String key = "";
1606                if (name.startsWith("get")) {
1607                    key = name.substring(3);
1608                } else if (name.startsWith("is")) {
1609                    key = name.substring(2);
1610                }
1611                if ((key.length() > 0)
1612                    && Character.isUpperCase(key.charAt(0))
1613                    && (method.getParameterTypes().length == 0)) {
1614                    if (key.length() == 1) {
1615                        key = key.toLowerCase();
1616                    } else if (!Character.isUpperCase(key.charAt(1))) {
1617                        key = key.substring(0, 1).toLowerCase() + key.substring(1);
1618                    }
1619
1620                    Object result = method.invoke(bean, (Object[])null);
1621                    if (result == null) {
1622                        m_map.put(key, NULL);
1623                    } else if (result.getClass().isArray()) {
1624                        m_map.put(key, new JSONArray(result, includeSuperClass));
1625                    } else if (result instanceof Collection) { //List or Set
1626                        m_map.put(key, new JSONArray((Collection<Object>)result, includeSuperClass));
1627                    } else if (result instanceof Map) {
1628                        m_map.put(key, new JSONObject((Map<String, Object>)result, includeSuperClass));
1629                    } else if (isStandardProperty(result.getClass())) { //Primitives, String and Wrapper
1630                        m_map.put(key, result);
1631                    } else {
1632                        if (result.getClass().getPackage().getName().startsWith("java")
1633                            || (result.getClass().getClassLoader() == null)) {
1634                            m_map.put(key, result.toString());
1635                        } else { //User defined Objects
1636                            m_map.put(key, new JSONObject(result, includeSuperClass));
1637                        }
1638                    }
1639                }
1640            } catch (Exception e) {
1641                throw new RuntimeException(e);
1642            }
1643        }
1644    }
1645}