f7e087489b2811dcffddf0b5f3042474b8c59a2a
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / utils / LOGJSONObject.java
1 package org.onap.dmaap.datarouter.provisioning.utils;
2
3 /*******************************************************************************
4  * ============LICENSE_START==================================================
5  * * org.onap.dmaap
6  * * ===========================================================================
7  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
8  * * ===========================================================================
9  * * Licensed under the Apache License, Version 2.0 (the "License");
10  * * you may not use this file except in compliance with the License.
11  * * You may obtain a copy of the License at
12  * *
13  *  *      http://www.apache.org/licenses/LICENSE-2.0
14  * *
15  *  * Unless required by applicable law or agreed to in writing, software
16  * * distributed under the License is distributed on an "AS IS" BASIS,
17  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * * See the License for the specific language governing permissions and
19  * * limitations under the License.
20  * * ============LICENSE_END====================================================
21  * *
22  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
23  * *
24  ******************************************************************************/
25
26 import com.att.eelf.configuration.EELFLogger;
27 import com.att.eelf.configuration.EELFManager;
28 import java.io.IOException;
29 import java.io.StringWriter;
30 import java.io.Writer;
31 import java.lang.reflect.Field;
32 import java.lang.reflect.Method;
33 import java.lang.reflect.Modifier;
34 import java.util.*;
35
36 import org.json.JSONArray;
37 import org.json.JSONException;
38 import org.json.JSONString;
39 import org.json.JSONTokener;
40
41 /**
42  * A JSONObject is an unordered collection of name/value pairs. Its external
43  * form is a string wrapped in curly braces with colons between the names and
44  * values, and commas between the values and names. The internal form is an
45  * object having <code>get</code> and <code>opt</code> methods for accessing the
46  * values by name, and <code>put</code> methods for adding or replacing values
47  * by name. The values can be any of these types: <code>Boolean</code>,
48  * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>,
49  * <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject
50  * constructor can be used to convert an external form JSON text into an
51  * internal form whose values can be retrieved with the <code>get</code> and
52  * <code>opt</code> methods, or to convert values into a JSON text using the
53  * <code>put</code> and <code>toString</code> methods. A <code>get</code> method
54  * returns a value if one can be found, and throws an exception if one cannot be
55  * found. An <code>opt</code> method returns a default value instead of throwing
56  * an exception, and so is useful for obtaining optional values.
57  * <p>
58  * The generic <code>get()</code> and <code>opt()</code> methods return an
59  * object, which you can cast or query for type. There are also typed
60  * <code>get</code> and <code>opt</code> methods that do type checking and type
61  * coercion for you. The opt methods differ from the get methods in that they do
62  * not throw. Instead, they return a specified value, such as null.
63  * <p>
64  * The <code>put</code> methods add or replace values in an object. For example,
65  *
66  * <pre>
67  * myString = new JSONObject().put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();
68  * </pre>
69  * <p>
70  * produces the string <code>{"JSON": "Hello, World"}</code>.
71  * <p>
72  * The texts produced by the <code>toString</code> methods strictly conform to
73  * the JSON syntax rules. The constructors are more forgiving in the texts they
74  * will accept:
75  * <ul>
76  * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
77  * before the closing brace.</li>
78  * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
79  * quote)</small>.</li>
80  * <li>Strings do not need to be quoted at all if they do not begin with a quote
81  * or single quote, and if they do not contain leading or trailing spaces, and
82  * if they do not contain any of these characters:
83  * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and
84  * if they are not the reserved words <code>true</code>, <code>false</code>, or
85  * <code>null</code>.</li>
86  * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as by
87  * <code>:</code>.</li>
88  * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as
89  * well as by <code>,</code> <small>(comma)</small>.</li>
90  * </ul>
91  *
92  * @author JSON.org
93  * @version 2012-12-01
94  */
95 public class LOGJSONObject {
96     /**
97      * The maximum number of keys in the key pool.
98      */
99     private static final int keyPoolSize = 100;
100
101     /**
102      * Key pooling is like string interning, but without permanently tying up
103      * memory. To help conserve memory, storage of duplicated key strings in
104      * JSONObjects will be avoided by using a key pool to manage unique key
105      * string objects. This is used by JSONObject.put(string, object).
106      */
107     private static Map<String, Object> keyPool = new LinkedHashMap<String, Object>(keyPoolSize);
108
109     private static final EELFLogger intlogger = EELFManager.getInstance().getLogger("InternalLog");
110
111     /**
112      * JSONObject.NULL is equivalent to the value that JavaScript calls null,
113      * whilst Java's null is equivalent to the value that JavaScript calls
114      * undefined.
115      */
116     private static final class Null {
117
118         /**
119          * There is only intended to be a single instance of the NULL object,
120          * so the clone method returns itself.
121          *
122          * @return NULL.
123          */
124         protected final Object clone() {
125             return this;
126         }
127
128         /**
129          * A Null object is equal to the null value and to itself.
130          *
131          * @param object An object to test for nullness.
132          * @return true if the object parameter is the JSONObject.NULL object
133          * or null.
134          */
135         public boolean equals(Object object) {
136             return object == null || object == this;
137         }
138
139         /**
140          * Returns a hash code value for the object. This method is
141          * supported for the benefit of hash tables such as those provided by
142          * {@link HashMap}.
143          * <p>
144          * The general contract of {@code hashCode} is:
145          * <ul>
146          * <li>Whenever it is invoked on the same object more than once during
147          * an execution of a Java application, the {@code hashCode} method
148          * must consistently return the same integer, provided no information
149          * used in {@code equals} comparisons on the object is modified.
150          * This integer need not remain consistent from one execution of an
151          * application to another execution of the same application.
152          * <li>If two objects are equal according to the {@code equals(Object)}
153          * method, then calling the {@code hashCode} method on each of
154          * the two objects must produce the same integer result.
155          * <li>It is <em>not</em> required that if two objects are unequal
156          * according to the {@link Object#equals(Object)}
157          * method, then calling the {@code hashCode} method on each of the
158          * two objects must produce distinct integer results.  However, the
159          * programmer should be aware that producing distinct integer results
160          * for unequal objects may improve the performance of hash tables.
161          * </ul>
162          * <p>
163          * As much as is reasonably practical, the hashCode method defined by
164          * class {@code Object} does return distinct integers for distinct
165          * objects. (This is typically implemented by converting the internal
166          * address of the object into an integer, but this implementation
167          * technique is not required by the
168          * Java&trade; programming language.)
169          *
170          * @return a hash code value for this object.
171          * @see Object#equals(Object)
172          * @see System#identityHashCode
173          */
174         @Override
175         public int hashCode() {
176             return super.hashCode();
177         }
178
179         /**
180          * Get the "null" string value.
181          *
182          * @return The string "null".
183          */
184         public String toString() {
185             return "null";
186         }
187     }
188
189
190     /**
191      * The map where the JSONObject's properties are kept.
192      */
193     private final Map<String, Object> map;
194
195
196     /**
197      * It is sometimes more convenient and less ambiguous to have a
198      * <code>NULL</code> object than to use Java's <code>null</code> value.
199      * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
200      * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
201      */
202     public static final Object NULL = new Null();
203
204
205     /**
206      * Construct an empty JSONObject.
207      */
208     public LOGJSONObject() {
209         this.map = new LinkedHashMap<>();
210     }
211
212
213     /**
214      * Construct a JSONObject from a subset of another JSONObject.
215      * An array of strings is used to identify the keys that should be copied.
216      * Missing keys are ignored.
217      *
218      * @param jo    A JSONObject.
219      * @param names An array of strings.
220      */
221     public LOGJSONObject(LOGJSONObject jo, String[] names) {
222         this();
223         for (int i = 0; i < names.length; i += 1) {
224             try {
225                 this.putOnce(names[i], jo.opt(names[i]));
226             } catch (Exception ignore) {
227                 intlogger.error("PROV0001 LOGJSONObject: " + ignore.getMessage(), ignore);
228             }
229         }
230     }
231
232
233     /**
234      * Construct a JSONObject from a JSONTokener.
235      *
236      * @param x A JSONTokener object containing the source string.
237      * @throws JSONException If there is a syntax error in the source string
238      *                       or a duplicated key.
239      */
240     public LOGJSONObject(JSONTokener x) {
241         this();
242         char c;
243         String key;
244
245         if (x.nextClean() != '{') {
246             throw x.syntaxError("A JSONObject text must begin with '{'");
247         }
248         for (; ; ) {
249             c = x.nextClean();
250             switch (c) {
251                 case 0:
252                     throw x.syntaxError("A JSONObject text must end with '}'");
253                 case '}':
254                     return;
255                 default:
256                     x.back();
257                     key = x.nextValue().toString();
258             }
259
260 // The key is followed by ':'. We will also tolerate '=' or '=>'.
261
262             c = x.nextClean();
263             if (c == '=') {
264                 if (x.next() != '>') {
265                     x.back();
266                 }
267             } else if (c != ':') {
268                 throw x.syntaxError("Expected a ':' after a key");
269             }
270             this.putOnce(key, x.nextValue());
271
272 // Pairs are separated by ','. We will also tolerate ';'.
273
274             switch (x.nextClean()) {
275                 case ';':
276                 case ',':
277                     if (x.nextClean() == '}') {
278                         return;
279                     }
280                     x.back();
281                     break;
282                 case '}':
283                     return;
284                 default:
285                     throw x.syntaxError("Expected a ',' or '}'");
286             }
287         }
288     }
289
290
291     /**
292      * Construct a JSONObject from a Map.
293      *
294      * @param map A map object that can be used to initialize the contents of
295      *            the JSONObject.
296      * @throws JSONException
297      */
298     public LOGJSONObject(Map<String, Object> map) {
299         this.map = new LinkedHashMap<String, Object>();
300         if (map != null) {
301             Iterator<Map.Entry<String, Object>> i = map.entrySet().iterator();
302             while (i.hasNext()) {
303                 Map.Entry<String, Object> e = i.next();
304                 Object value = e.getValue();
305                 if (value != null) {
306                     this.map.put(e.getKey(), wrap(value));
307                 }
308             }
309         }
310     }
311
312
313     /**
314      * Construct a JSONObject from an Object using bean getters.
315      * It reflects on all of the public methods of the object.
316      * For each of the methods with no parameters and a name starting
317      * with <code>"get"</code> or <code>"is"</code> followed by an uppercase letter,
318      * the method is invoked, and a key and the value returned from the getter method
319      * are put into the new JSONObject.
320      * <p>
321      * The key is formed by removing the <code>"get"</code> or <code>"is"</code> prefix.
322      * If the second remaining character is not upper case, then the first
323      * character is converted to lower case.
324      * <p>
325      * For example, if an object has a method named <code>"getName"</code>, and
326      * if the result of calling <code>object.getName()</code> is <code>"Larry Fine"</code>,
327      * then the JSONObject will contain <code>"name": "Larry Fine"</code>.
328      *
329      * @param bean An object that has getter methods that should be used
330      *             to make a JSONObject.
331      */
332     public LOGJSONObject(Object bean) {
333         this();
334         this.populateMap(bean);
335     }
336
337
338     /**
339      * Construct a JSONObject from an Object, using reflection to find the
340      * public members. The resulting JSONObject's keys will be the strings
341      * from the names array, and the values will be the field values associated
342      * with those keys in the object. If a key is not found or not visible,
343      * then it will not be copied into the new JSONObject.
344      *
345      * @param object An object that has fields that should be used to make a
346      *               JSONObject.
347      * @param names  An array of strings, the names of the fields to be obtained
348      *               from the object.
349      */
350     public LOGJSONObject(Object object, String names[]) {
351         this();
352         Class<? extends Object> c = object.getClass();
353         for (int i = 0; i < names.length; i += 1) {
354             String name = names[i];
355             try {
356                 this.putOpt(name, c.getField(name).get(object));
357             } catch (Exception ignore) {
358             }
359         }
360     }
361
362
363     /**
364      * Construct a JSONObject from a source JSON text string.
365      * This is the most commonly used JSONObject constructor.
366      *
367      * @param source A string beginning
368      *               with <code>{</code>&nbsp;<small>(left brace)</small> and ending
369      *               with <code>}</code>&nbsp;<small>(right brace)</small>.
370      * @throws JSONException If there is a syntax error in the source
371      *                       string or a duplicated key.
372      */
373     public LOGJSONObject(String source) throws JSONException {
374         this(new JSONTokener(source));
375     }
376
377
378     /**
379      * Construct a JSONObject from a ResourceBundle.
380      *
381      * @param baseName The ResourceBundle base name.
382      * @param locale   The Locale to load the ResourceBundle for.
383      * @throws JSONException If any JSONExceptions are detected.
384      */
385     public LOGJSONObject(String baseName, Locale locale) throws JSONException {
386         this();
387         ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale,
388                 Thread.currentThread().getContextClassLoader());
389
390 // Iterate through the keys in the bundle.
391
392         Enumeration<?> keys = bundle.getKeys();
393         while (keys.hasMoreElements()) {
394             Object key = keys.nextElement();
395             if (key instanceof String) {
396
397 // Go through the path, ensuring that there is a nested JSONObject for each
398 // segment except the last. Add the value using the last segment's name into
399 // the deepest nested JSONObject.
400
401                 String[] path = ((String) key).split("\\.");
402                 int last = path.length - 1;
403                 LOGJSONObject target = this;
404                 for (int i = 0; i < last; i += 1) {
405                     String segment = path[i];
406                     LOGJSONObject nextTarget = target.optJSONObject(segment);
407                     if (nextTarget == null) {
408                         nextTarget = new LOGJSONObject();
409                         target.put(segment, nextTarget);
410                     }
411                     target = nextTarget;
412                 }
413                 target.put(path[last], bundle.getString((String) key));
414             }
415         }
416     }
417
418
419     /**
420      * Accumulate values under a key. It is similar to the put method except
421      * that if there is already an object stored under the key then a
422      * JSONArray is stored under the key to hold all of the accumulated values.
423      * If there is already a JSONArray, then the new value is appended to it.
424      * In contrast, the put method replaces the previous value.
425      * <p>
426      * If only one value is accumulated that is not a JSONArray, then the
427      * result will be the same as using put. But if multiple values are
428      * accumulated, then the result will be like append.
429      *
430      * @param key   A key string.
431      * @param value An object to be accumulated under the key.
432      * @return this.
433      * @throws JSONException If the value is an invalid number
434      *                       or if the key is null.
435      */
436     public LOGJSONObject accumulate(
437             String key,
438             Object value
439     ) throws JSONException {
440         testValidity(value);
441         Object object = this.opt(key);
442         if (object == null) {
443             this.put(key, value instanceof JSONArray
444                     ? new JSONArray().put(value)
445                     : value);
446         } else if (object instanceof JSONArray) {
447             ((JSONArray) object).put(value);
448         } else {
449             this.put(key, new JSONArray().put(object).put(value));
450         }
451         return this;
452     }
453
454
455     /**
456      * Append values to the array under a key. If the key does not exist in the
457      * JSONObject, then the key is put in the JSONObject with its value being a
458      * JSONArray containing the value parameter. If the key was already
459      * associated with a JSONArray, then the value parameter is appended to it.
460      *
461      * @param key   A key string.
462      * @param value An object to be accumulated under the key.
463      * @return this.
464      * @throws JSONException If the key is null or if the current value
465      *                       associated with the key is not a JSONArray.
466      */
467     public LOGJSONObject append(String key, Object value) throws JSONException {
468         testValidity(value);
469         Object object = this.opt(key);
470         if (object == null) {
471             this.put(key, new JSONArray().put(value));
472         } else if (object instanceof JSONArray) {
473             this.put(key, ((JSONArray) object).put(value));
474         } else {
475             throw new JSONException("JSONObject[" + key +
476                     "] is not a JSONArray.");
477         }
478         return this;
479     }
480
481
482     /**
483      * Produce a string from a double. The string "null" will be returned if
484      * the number is not finite.
485      *
486      * @param d A double.
487      * @return A String.
488      */
489     public static String doubleToString(double d) {
490         if (Double.isInfinite(d) || Double.isNaN(d)) {
491             return "null";
492         }
493
494 // Shave off trailing zeros and decimal point, if possible.
495
496         String string = Double.toString(d);
497         if (string.indexOf('.') > 0 && string.indexOf('e') < 0 &&
498                 string.indexOf('E') < 0) {
499             while (string.endsWith("0")) {
500                 string = string.substring(0, string.length() - 1);
501             }
502             if (string.endsWith(".")) {
503                 string = string.substring(0, string.length() - 1);
504             }
505         }
506         return string;
507     }
508
509
510     /**
511      * Get the value object associated with a key.
512      *
513      * @param key A key string.
514      * @return The object associated with the key.
515      * @throws JSONException if the key is not found.
516      */
517     public Object get(String key) throws JSONException {
518         if (key == null) {
519             throw new JSONException("Null key.");
520         }
521         Object object = this.opt(key);
522         if (object == null) {
523             throw new JSONException("JSONObject[" + quote(key) +
524                     "] not found.");
525         }
526         return object;
527     }
528
529
530     /**
531      * Get the boolean value associated with a key.
532      *
533      * @param key A key string.
534      * @return The truth.
535      * @throws JSONException if the value is not a Boolean or the String "true" or "false".
536      */
537     public boolean getBoolean(String key) throws JSONException {
538         Object object = this.get(key);
539         if (object.equals(Boolean.FALSE) ||
540                 (object instanceof String &&
541                         ((String) object).equalsIgnoreCase("false"))) {
542             return false;
543         } else if (object.equals(Boolean.TRUE) ||
544                 (object instanceof String &&
545                         ((String) object).equalsIgnoreCase("true"))) {
546             return true;
547         }
548         throw new JSONException("JSONObject[" + quote(key) +
549                 "] is not a Boolean.");
550     }
551
552
553     /**
554      * Get the double value associated with a key.
555      *
556      * @param key A key string.
557      * @return The numeric value.
558      * @throws JSONException if the key is not found or
559      *                       if the value is not a Number object and cannot be converted to a number.
560      */
561     public double getDouble(String key) {
562         Object object = this.get(key);
563         try {
564             return object instanceof Number
565                     ? ((Number) object).doubleValue()
566                     : Double.parseDouble((String) object);
567         } catch (Exception e) {
568             intlogger.error("JSONObject[" + quote(key) + "] is not a number.", e);
569             throw new JSONException("JSONObject[" + quote(key) + "] is not a number.");
570         }
571     }
572
573
574     /**
575      * Get the int value associated with a key.
576      *
577      * @param key A key string.
578      * @return The integer value.
579      * @throws JSONException if the key is not found or if the value cannot
580      *                       be converted to an integer.
581      */
582     public int getInt(String key) {
583         Object object = this.get(key);
584         try {
585             return object instanceof Number
586                     ? ((Number) object).intValue()
587                     : Integer.parseInt((String) object);
588         } catch (Exception e) {
589             intlogger.error("JSONObject[" + quote(key) + "] is not an int.", e);
590             throw new JSONException("JSONObject[" + quote(key) + "] is not an int.");
591         }
592     }
593
594
595     /**
596      * Get the JSONArray value associated with a key.
597      *
598      * @param key A key string.
599      * @return A JSONArray which is the value.
600      * @throws JSONException if the key is not found or
601      *                       if the value is not a JSONArray.
602      */
603     public JSONArray getJSONArray(String key) throws JSONException {
604         Object object = this.get(key);
605         if (object instanceof JSONArray) {
606             return (JSONArray) object;
607         }
608         throw new JSONException("JSONObject[" + quote(key) +
609                 "] is not a JSONArray.");
610     }
611
612
613     /**
614      * Get the JSONObject value associated with a key.
615      *
616      * @param key A key string.
617      * @return A JSONObject which is the value.
618      * @throws JSONException if the key is not found or
619      *                       if the value is not a JSONObject.
620      */
621     public LOGJSONObject getJSONObject(String key) throws JSONException {
622         Object object = this.get(key);
623         if (object instanceof LOGJSONObject) {
624             return (LOGJSONObject) object;
625         }
626         throw new JSONException("JSONObject[" + quote(key) +
627                 "] is not a JSONObject.");
628     }
629
630
631     /**
632      * Get the long value associated with a key.
633      *
634      * @param key A key string.
635      * @return The long value.
636      * @throws JSONException if the key is not found or if the value cannot
637      *                       be converted to a long.
638      */
639     public long getLong(String key) throws JSONException {
640         Object object = this.get(key);
641         try {
642             return object instanceof Number
643                     ? ((Number) object).longValue()
644                     : Long.parseLong((String) object);
645         } catch (Exception e) {
646             intlogger.error("JSONObject[" + quote(key) + "] is not a long.", e);
647             throw new JSONException("JSONObject[" + quote(key) + "] is not a long.");
648         }
649     }
650
651
652     /**
653      * Get an array of field names from a JSONObject.
654      *
655      * @return An array of field names, or null if there are no names.
656      */
657     public static String[] getNames(LOGJSONObject jo) {
658         int length = jo.length();
659         if (length == 0) {
660             return null;
661         }
662         Iterator<String> iterator = jo.keys();
663         String[] names = new String[length];
664         int i = 0;
665         while (iterator.hasNext()) {
666             names[i] = iterator.next();
667             i += 1;
668         }
669         return names;
670     }
671
672
673     /**
674      * Get an array of field names from an Object.
675      *
676      * @return An array of field names, or null if there are no names.
677      */
678     public static String[] getNames(Object object) {
679         if (object == null) {
680             return null;
681         }
682         Class<? extends Object> klass = object.getClass();
683         Field[] fields = klass.getFields();
684         int length = fields.length;
685         if (length == 0) {
686             return null;
687         }
688         String[] names = new String[length];
689         for (int i = 0; i < length; i += 1) {
690             names[i] = fields[i].getName();
691         }
692         return names;
693     }
694
695
696     /**
697      * Get the string associated with a key.
698      *
699      * @param key A key string.
700      * @return A string which is the value.
701      * @throws JSONException if there is no string value for the key.
702      */
703     public String getString(String key) {
704         Object object = this.get(key);
705         if (object instanceof String) {
706             return (String) object;
707         }
708         throw new JSONException("JSONObject[" + quote(key) +
709                 "] not a string.");
710     }
711
712
713     /**
714      * Determine if the JSONObject contains a specific key.
715      *
716      * @param key A key string.
717      * @return true if the key exists in the JSONObject.
718      */
719     public boolean has(String key) {
720         return this.map.containsKey(key);
721     }
722
723
724     /**
725      * Increment a property of a JSONObject. If there is no such property,
726      * create one with a value of 1. If there is such a property, and if
727      * it is an Integer, Long, Double, or Float, then add one to it.
728      *
729      * @param key A key string.
730      * @return this.
731      * @throws JSONException If there is already a property with this name
732      *                       that is not an Integer, Long, Double, or Float.
733      */
734     public LOGJSONObject increment(String key) {
735         Object value = this.opt(key);
736         if (value == null) {
737             this.put(key, 1);
738         } else if (value instanceof Integer) {
739             this.put(key, ((Integer) value).intValue() + 1);
740         } else if (value instanceof Long) {
741             this.put(key, ((Long) value).longValue() + 1);
742         } else if (value instanceof Double) {
743             this.put(key, ((Double) value).doubleValue() + 1);
744         } else if (value instanceof Float) {
745             this.put(key, ((Float) value).floatValue() + 1);
746         } else {
747             throw new JSONException("Unable to increment [" + quote(key) + "].");
748         }
749         return this;
750     }
751
752
753     /**
754      * Determine if the value associated with the key is null or if there is
755      * no value.
756      *
757      * @param key A key string.
758      * @return true if there is no value associated with the key or if
759      * the value is the JSONObject.NULL object.
760      */
761     public boolean isNull(String key) {
762         return LOGJSONObject.NULL.equals(this.opt(key));
763     }
764
765
766     /**
767      * Get an enumeration of the keys of the JSONObject.
768      *
769      * @return An iterator of the keys.
770      */
771     public Iterator<String> keys() {
772         return this.keySet().iterator();
773     }
774
775
776     /**
777      * Get a set of keys of the JSONObject.
778      *
779      * @return A keySet.
780      */
781     public Set<String> keySet() {
782         return this.map.keySet();
783     }
784
785
786     /**
787      * Get the number of keys stored in the JSONObject.
788      *
789      * @return The number of keys in the JSONObject.
790      */
791     public int length() {
792         return this.map.size();
793     }
794
795
796     /**
797      * Produce a JSONArray containing the names of the elements of this
798      * JSONObject.
799      *
800      * @return A JSONArray containing the key strings, or null if the JSONObject
801      * is empty.
802      */
803     public JSONArray names() {
804         JSONArray ja = new JSONArray();
805         Iterator<String> keys = this.keys();
806         while (keys.hasNext()) {
807             ja.put(keys.next());
808         }
809         return ja.length() == 0 ? null : ja;
810     }
811
812     /**
813      * Produce a string from a Number.
814      *
815      * @param number A Number
816      * @return A String.
817      * @throws JSONException If n is a non-finite number.
818      */
819     public static String numberToString(Number number)
820             throws JSONException {
821         if (number == null) {
822             throw new JSONException("Null pointer");
823         }
824         testValidity(number);
825
826 // Shave off trailing zeros and decimal point, if possible.
827
828         String string = number.toString();
829         if (string.indexOf('.') > 0 && string.indexOf('e') < 0 &&
830                 string.indexOf('E') < 0) {
831             while (string.endsWith("0")) {
832                 string = string.substring(0, string.length() - 1);
833             }
834             if (string.endsWith(".")) {
835                 string = string.substring(0, string.length() - 1);
836             }
837         }
838         return string;
839     }
840
841
842     /**
843      * Get an optional value associated with a key.
844      *
845      * @param key A key string.
846      * @return An object which is the value, or null if there is no value.
847      */
848     public Object opt(String key) {
849         return key == null ? null : this.map.get(key);
850     }
851
852
853     /**
854      * Get an optional boolean associated with a key.
855      * It returns false if there is no such key, or if the value is not
856      * Boolean.TRUE or the String "true".
857      *
858      * @param key A key string.
859      * @return The truth.
860      */
861     public boolean optBoolean(String key) {
862         return this.optBoolean(key, false);
863     }
864
865
866     /**
867      * Get an optional boolean associated with a key.
868      * It returns the defaultValue if there is no such key, or if it is not
869      * a Boolean or the String "true" or "false" (case insensitive).
870      *
871      * @param key          A key string.
872      * @param defaultValue The default.
873      * @return The truth.
874      */
875     public boolean optBoolean(String key, boolean defaultValue) {
876         try {
877             return this.getBoolean(key);
878         } catch (Exception e) {
879             intlogger.trace("Using defaultValue: " + defaultValue, e);
880             return defaultValue;
881         }
882     }
883
884
885     /**
886      * Get an optional double associated with a key,
887      * or NaN if there is no such key or if its value is not a number.
888      * If the value is a string, an attempt will be made to evaluate it as
889      * a number.
890      *
891      * @param key A string which is the key.
892      * @return An object which is the value.
893      */
894     public double optDouble(String key) {
895         return this.optDouble(key, Double.NaN);
896     }
897
898
899     /**
900      * Get an optional double associated with a key, or the
901      * defaultValue if there is no such key or if its value is not a number.
902      * If the value is a string, an attempt will be made to evaluate it as
903      * a number.
904      *
905      * @param key          A key string.
906      * @param defaultValue The default.
907      * @return An object which is the value.
908      */
909     public double optDouble(String key, double defaultValue) {
910         try {
911             return this.getDouble(key);
912         } catch (Exception e) {
913             intlogger.trace("Using defaultValue: " + defaultValue, e);
914             return defaultValue;
915         }
916     }
917
918
919     /**
920      * Get an optional int value associated with a key,
921      * or zero if there is no such key or if the value is not a number.
922      * If the value is a string, an attempt will be made to evaluate it as
923      * a number.
924      *
925      * @param key A key string.
926      * @return An object which is the value.
927      */
928     public int optInt(String key) {
929         return this.optInt(key, 0);
930     }
931
932
933     /**
934      * Get an optional int value associated with a key,
935      * or the default if there is no such key or if the value is not a number.
936      * If the value is a string, an attempt will be made to evaluate it as
937      * a number.
938      *
939      * @param key          A key string.
940      * @param defaultValue The default.
941      * @return An object which is the value.
942      */
943     public int optInt(String key, int defaultValue) {
944         try {
945             return this.getInt(key);
946         } catch (Exception e) {
947             intlogger.trace("Using defaultValue: " + defaultValue, e);
948             return defaultValue;
949         }
950     }
951
952
953     /**
954      * Get an optional JSONArray associated with a key.
955      * It returns null if there is no such key, or if its value is not a
956      * JSONArray.
957      *
958      * @param key A key string.
959      * @return A JSONArray which is the value.
960      */
961     public JSONArray optJSONArray(String key) {
962         Object o = this.opt(key);
963         return o instanceof JSONArray ? (JSONArray) o : null;
964     }
965
966
967     /**
968      * Get an optional JSONObject associated with a key.
969      * It returns null if there is no such key, or if its value is not a
970      * JSONObject.
971      *
972      * @param key A key string.
973      * @return A JSONObject which is the value.
974      */
975     public LOGJSONObject optJSONObject(String key) {
976         Object object = this.opt(key);
977         return object instanceof LOGJSONObject ? (LOGJSONObject) object : null;
978     }
979
980
981     /**
982      * Get an optional long value associated with a key,
983      * or zero if there is no such key or if the value is not a number.
984      * If the value is a string, an attempt will be made to evaluate it as
985      * a number.
986      *
987      * @param key A key string.
988      * @return An object which is the value.
989      */
990     public long optLong(String key) {
991         return this.optLong(key, 0);
992     }
993
994
995     /**
996      * Get an optional long value associated with a key,
997      * or the default if there is no such key or if the value is not a number.
998      * If the value is a string, an attempt will be made to evaluate it as
999      * a number.
1000      *
1001      * @param key          A key string.
1002      * @param defaultValue The default.
1003      * @return An object which is the value.
1004      */
1005     public long optLong(String key, long defaultValue) {
1006         try {
1007             return this.getLong(key);
1008         } catch (Exception e) {
1009             return defaultValue;
1010         }
1011     }
1012
1013
1014     /**
1015      * Get an optional string associated with a key.
1016      * It returns an empty string if there is no such key. If the value is not
1017      * a string and is not null, then it is converted to a string.
1018      *
1019      * @param key A key string.
1020      * @return A string which is the value.
1021      */
1022     public String optString(String key) {
1023         return this.optString(key, "");
1024     }
1025
1026
1027     /**
1028      * Get an optional string associated with a key.
1029      * It returns the defaultValue if there is no such key.
1030      *
1031      * @param key          A key string.
1032      * @param defaultValue The default.
1033      * @return A string which is the value.
1034      */
1035     public String optString(String key, String defaultValue) {
1036         Object object = this.opt(key);
1037         return NULL.equals(object) ? defaultValue : object.toString();
1038     }
1039
1040
1041     private void populateMap(Object bean) {
1042         Class<? extends Object> klass = bean.getClass();
1043
1044 // If klass is a System class then set includeSuperClass to false.
1045
1046         boolean includeSuperClass = klass.getClassLoader() != null;
1047
1048         Method[] methods = includeSuperClass
1049                 ? klass.getMethods()
1050                 : klass.getDeclaredMethods();
1051         for (int i = 0; i < methods.length; i += 1) {
1052             try {
1053                 Method method = methods[i];
1054                 if (Modifier.isPublic(method.getModifiers())) {
1055                     String name = method.getName();
1056                     String key = "";
1057                     if (name.startsWith("get")) {
1058                         if ("getClass".equals(name) ||
1059                                 "getDeclaringClass".equals(name)) {
1060                             key = "";
1061                         } else {
1062                             key = name.substring(3);
1063                         }
1064                     } else if (name.startsWith("is")) {
1065                         key = name.substring(2);
1066                     }
1067                     if (key.length() > 0 &&
1068                             Character.isUpperCase(key.charAt(0)) &&
1069                             method.getParameterTypes().length == 0) {
1070                         if (key.length() == 1) {
1071                             key = key.toLowerCase();
1072                         } else if (!Character.isUpperCase(key.charAt(1))) {
1073                             key = key.substring(0, 1).toLowerCase() +
1074                                     key.substring(1);
1075                         }
1076
1077                         Object result = method.invoke(bean, (Object[]) null);
1078                         if (result != null) {
1079                             this.map.put(key, wrap(result));
1080                         }
1081                     }
1082                 }
1083             } catch (Exception ignore) {
1084                 intlogger.trace("populateMap: " + ignore.getMessage(), ignore);
1085             }
1086         }
1087     }
1088
1089
1090     /**
1091      * Put a key/boolean pair in the JSONObject.
1092      *
1093      * @param key   A key string.
1094      * @param value A boolean which is the value.
1095      * @return this.
1096      * @throws JSONException If the key is null.
1097      */
1098     public LOGJSONObject put(String key, boolean value) throws JSONException {
1099         this.put(key, value ? Boolean.TRUE : Boolean.FALSE);
1100         return this;
1101     }
1102
1103
1104     /**
1105      * Put a key/value pair in the JSONObject, where the value will be a
1106      * JSONArray which is produced from a Collection.
1107      *
1108      * @param key   A key string.
1109      * @param value A Collection value.
1110      * @return this.
1111      * @throws JSONException
1112      */
1113     public LOGJSONObject put(String key, Collection<Object> value) throws JSONException {
1114         this.put(key, new JSONArray(value));
1115         return this;
1116     }
1117
1118
1119     /**
1120      * Put a key/double pair in the JSONObject.
1121      *
1122      * @param key   A key string.
1123      * @param value A double which is the value.
1124      * @return this.
1125      * @throws JSONException If the key is null or if the number is invalid.
1126      */
1127     public LOGJSONObject put(String key, double value) throws JSONException {
1128         this.put(key, new Double(value));
1129         return this;
1130     }
1131
1132
1133     /**
1134      * Put a key/int pair in the JSONObject.
1135      *
1136      * @param key   A key string.
1137      * @param value An int which is the value.
1138      * @return this.
1139      * @throws JSONException If the key is null.
1140      */
1141     public LOGJSONObject put(String key, int value) throws JSONException {
1142         this.put(key, new Integer(value));
1143         return this;
1144     }
1145
1146
1147     /**
1148      * Put a key/long pair in the JSONObject.
1149      *
1150      * @param key   A key string.
1151      * @param value A long which is the value.
1152      * @return this.
1153      * @throws JSONException If the key is null.
1154      */
1155     public LOGJSONObject put(String key, long value) throws JSONException {
1156         this.put(key, new Long(value));
1157         return this;
1158     }
1159
1160
1161     /**
1162      * Put a key/value pair in the JSONObject, where the value will be a
1163      * JSONObject which is produced from a Map.
1164      *
1165      * @param key   A key string.
1166      * @param value A Map value.
1167      * @return this.
1168      * @throws JSONException
1169      */
1170     public LOGJSONObject put(String key, Map<String, Object> value) throws JSONException {
1171         this.put(key, new LOGJSONObject(value));
1172         return this;
1173     }
1174
1175
1176     /**
1177      * Put a key/value pair in the JSONObject. If the value is null,
1178      * then the key will be removed from the JSONObject if it is present.
1179      *
1180      * @param key   A key string.
1181      * @param value An object which is the value. It should be of one of these
1182      *              types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
1183      *              or the JSONObject.NULL object.
1184      * @return this.
1185      * @throws JSONException If the value is non-finite number
1186      *                       or if the key is null.
1187      */
1188     public LOGJSONObject put(String key, Object value) throws JSONException {
1189         String pooled;
1190         if (key == null) {
1191             throw new JSONException("Null key.");
1192         }
1193         if (value != null) {
1194             testValidity(value);
1195             pooled = (String) keyPool.get(key);
1196             if (pooled == null) {
1197                 if (keyPool.size() >= keyPoolSize) {
1198                     keyPool = new LinkedHashMap<String, Object>(keyPoolSize);
1199                 }
1200                 keyPool.put(key, key);
1201             } else {
1202                 key = pooled;
1203             }
1204             this.map.put(key, value);
1205         } else {
1206             this.remove(key);
1207         }
1208         return this;
1209     }
1210
1211
1212     /**
1213      * Put a key/value pair in the JSONObject, but only if the key and the
1214      * value are both non-null, and only if there is not already a member
1215      * with that name.
1216      *
1217      * @param key
1218      * @param value
1219      * @return his.
1220      * @throws JSONException if the key is a duplicate
1221      */
1222     public LOGJSONObject putOnce(String key, Object value) throws JSONException {
1223         if (key != null && value != null) {
1224             if (this.opt(key) != null) {
1225                 throw new JSONException("Duplicate key \"" + key + "\"");
1226             }
1227             this.put(key, value);
1228         }
1229         return this;
1230     }
1231
1232
1233     /**
1234      * Put a key/value pair in the JSONObject, but only if the
1235      * key and the value are both non-null.
1236      *
1237      * @param key   A key string.
1238      * @param value An object which is the value. It should be of one of these
1239      *              types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
1240      *              or the JSONObject.NULL object.
1241      * @return this.
1242      * @throws JSONException If the value is a non-finite number.
1243      */
1244     public LOGJSONObject putOpt(String key, Object value) throws JSONException {
1245         if (key != null && value != null) {
1246             this.put(key, value);
1247         }
1248         return this;
1249     }
1250
1251
1252     /**
1253      * Produce a string in double quotes with backslash sequences in all the
1254      * right places. A backslash will be inserted within </, producing <\/,
1255      * allowing JSON text to be delivered in HTML. In JSON text, a string
1256      * cannot contain a control character or an unescaped quote or backslash.
1257      *
1258      * @param string A String
1259      * @return A String correctly formatted for insertion in a JSON text.
1260      */
1261     public static String quote(String string) {
1262         StringWriter sw = new StringWriter();
1263         synchronized (sw.getBuffer()) {
1264             try {
1265                 return quote(string, sw).toString();
1266             } catch (IOException e) {
1267                 intlogger.trace("Ignore Exception message: ", e);
1268                 return "";
1269             }
1270         }
1271     }
1272
1273     public static Writer quote(String string, Writer w) throws IOException {
1274         if (string == null || string.length() == 0) {
1275             w.write("\"\"");
1276             return w;
1277         }
1278
1279         char b;
1280         char c = 0;
1281         String hhhh;
1282         int i;
1283         int len = string.length();
1284
1285         w.write('"');
1286         for (i = 0; i < len; i += 1) {
1287             b = c;
1288             c = string.charAt(i);
1289             switch (c) {
1290                 case '\\':
1291                 case '"':
1292                     w.write('\\');
1293                     w.write(c);
1294                     break;
1295                 case '/':
1296                     if (b == '<') {
1297                         w.write('\\');
1298                     }
1299                     w.write(c);
1300                     break;
1301                 case '\b':
1302                     w.write("\\b");
1303                     break;
1304                 case '\t':
1305                     w.write("\\t");
1306                     break;
1307                 case '\n':
1308                     w.write("\\n");
1309                     break;
1310                 case '\f':
1311                     w.write("\\f");
1312                     break;
1313                 case '\r':
1314                     w.write("\\r");
1315                     break;
1316                 default:
1317                     if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
1318                             || (c >= '\u2000' && c < '\u2100')) {
1319                         w.write("\\u");
1320                         hhhh = Integer.toHexString(c);
1321                         w.write("0000", 0, 4 - hhhh.length());
1322                         w.write(hhhh);
1323                     } else {
1324                         w.write(c);
1325                     }
1326             }
1327         }
1328         w.write('"');
1329         return w;
1330     }
1331
1332     /**
1333      * Remove a name and its value, if present.
1334      *
1335      * @param key The name to be removed.
1336      * @return The value that was associated with the name,
1337      * or null if there was no value.
1338      */
1339     public Object remove(String key) {
1340         return this.map.remove(key);
1341     }
1342
1343     /**
1344      * Try to convert a string into a number, boolean, or null. If the string
1345      * can't be converted, return the string.
1346      *
1347      * @param string A String.
1348      * @return A simple JSON value.
1349      */
1350     public static Object stringToValue(String string) {
1351         Double d;
1352         if (string.equals("")) {
1353             return string;
1354         }
1355         if (string.equalsIgnoreCase("true")) {
1356             return Boolean.TRUE;
1357         }
1358         if (string.equalsIgnoreCase("false")) {
1359             return Boolean.FALSE;
1360         }
1361         if (string.equalsIgnoreCase("null")) {
1362             return LOGJSONObject.NULL;
1363         }
1364
1365         /*
1366          * If it might be a number, try converting it.
1367          * If a number cannot be produced, then the value will just
1368          * be a string. Note that the plus and implied string
1369          * conventions are non-standard. A JSON parser may accept
1370          * non-JSON forms as long as it accepts all correct JSON forms.
1371          */
1372
1373         char b = string.charAt(0);
1374         if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
1375             try {
1376                 if (string.indexOf('.') > -1 ||
1377                         string.indexOf('e') > -1 || string.indexOf('E') > -1) {
1378                     d = Double.valueOf(string);
1379                     if (!d.isInfinite() && !d.isNaN()) {
1380                         return d;
1381                     }
1382                 } else {
1383                     Long myLong = new Long(string);
1384                     if (myLong.longValue() == myLong.intValue()) {
1385                         return new Integer(myLong.intValue());
1386                     } else {
1387                         return myLong;
1388                     }
1389                 }
1390             } catch (Exception e) {
1391                 intlogger.trace("Ignore Exception message: ", e);
1392             }
1393         }
1394         return string;
1395     }
1396
1397
1398     /**
1399      * Throw an exception if the object is a NaN or infinite number.
1400      *
1401      * @param o The object to test.
1402      * @throws JSONException If o is a non-finite number.
1403      */
1404     public static void testValidity(Object o) {
1405         if (o != null) {
1406             if (o instanceof Double) {
1407                 if (((Double) o).isInfinite() || ((Double) o).isNaN()) {
1408                     throw new JSONException(
1409                             "JSON does not allow non-finite numbers.");
1410                 }
1411             } else if (o instanceof Float) {
1412                 if (((Float) o).isInfinite() || ((Float) o).isNaN()) {
1413                     throw new JSONException(
1414                             "JSON does not allow non-finite numbers.");
1415                 }
1416             }
1417         }
1418     }
1419
1420
1421     /**
1422      * Produce a JSONArray containing the values of the members of this
1423      * JSONObject.
1424      *
1425      * @param names A JSONArray containing a list of key strings. This
1426      *              determines the sequence of the values in the result.
1427      * @return A JSONArray of values.
1428      * @throws JSONException If any of the values are non-finite numbers.
1429      */
1430     public JSONArray toJSONArray(JSONArray names) throws JSONException {
1431         if (names == null || names.length() == 0) {
1432             return null;
1433         }
1434         JSONArray ja = new JSONArray();
1435         for (int i = 0; i < names.length(); i += 1) {
1436             ja.put(this.opt(names.getString(i)));
1437         }
1438         return ja;
1439     }
1440
1441     /**
1442      * Make a JSON text of this JSONObject. For compactness, no whitespace
1443      * is added. If this would not result in a syntactically correct JSON text,
1444      * then null will be returned instead.
1445      * <p>
1446      * Warning: This method assumes that the data structure is acyclical.
1447      *
1448      * @return a printable, displayable, portable, transmittable
1449      * representation of the object, beginning
1450      * with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1451      * with <code>}</code>&nbsp;<small>(right brace)</small>.
1452      */
1453     public String toString() {
1454         try {
1455             return this.toString(0);
1456         } catch (Exception e) {
1457             intlogger.trace("Exception: ", e);
1458             return "";
1459         }
1460     }
1461
1462
1463     /**
1464      * Make a prettyprinted JSON text of this JSONObject.
1465      * <p>
1466      * Warning: This method assumes that the data structure is acyclical.
1467      *
1468      * @param indentFactor The number of spaces to add to each level of
1469      *                     indentation.
1470      * @return a printable, displayable, portable, transmittable
1471      * representation of the object, beginning
1472      * with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1473      * with <code>}</code>&nbsp;<small>(right brace)</small>.
1474      * @throws JSONException If the object contains an invalid number.
1475      */
1476     public String toString(int indentFactor) throws JSONException {
1477         StringWriter w = new StringWriter();
1478         synchronized (w.getBuffer()) {
1479             return this.write(w, indentFactor, 0).toString();
1480         }
1481     }
1482
1483     /**
1484      * Make a JSON text of an Object value. If the object has an
1485      * value.toJSONString() method, then that method will be used to produce
1486      * the JSON text. The method is required to produce a strictly
1487      * conforming text. If the object does not contain a toJSONString
1488      * method (which is the most common case), then a text will be
1489      * produced by other means. If the value is an array or Collection,
1490      * then a JSONArray will be made from it and its toJSONString method
1491      * will be called. If the value is a MAP, then a JSONObject will be made
1492      * from it and its toJSONString method will be called. Otherwise, the
1493      * value's toString method will be called, and the result will be quoted.
1494      *
1495      * <p>
1496      * Warning: This method assumes that the data structure is acyclical.
1497      *
1498      * @param value The value to be serialized.
1499      * @return a printable, displayable, transmittable
1500      * representation of the object, beginning
1501      * with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1502      * with <code>}</code>&nbsp;<small>(right brace)</small>.
1503      * @throws JSONException If the value is or contains an invalid number.
1504      */
1505     @SuppressWarnings("unchecked")
1506     public static String valueToString(Object value) throws JSONException {
1507         if (value == null) {
1508             return "null";
1509         }
1510         if (value instanceof JSONString) {
1511             Object object;
1512             try {
1513                 object = ((JSONString) value).toJSONString();
1514             } catch (Exception e) {
1515                 throw new JSONException(e);
1516             }
1517             if (object instanceof String) {
1518                 return (String) object;
1519             }
1520             throw new JSONException("Bad value from toJSONString: " + object);
1521         }
1522         if (value instanceof Number) {
1523             return numberToString((Number) value);
1524         }
1525         if (value instanceof Boolean || value instanceof LOGJSONObject ||
1526                 value instanceof JSONArray) {
1527             return value.toString();
1528         }
1529         if (value instanceof Map) {
1530             return new LOGJSONObject((Map<String, Object>) value).toString();
1531         }
1532         if (value instanceof Collection) {
1533             return new JSONArray((Collection<Object>) value).toString();
1534         }
1535         if (value.getClass().isArray()) {
1536             return new JSONArray(value).toString();
1537         }
1538         return quote(value.toString());
1539     }
1540
1541     /**
1542      * Wrap an object, if necessary. If the object is null, return the NULL
1543      * object. If it is an array or collection, wrap it in a JSONArray. If
1544      * it is a map, wrap it in a JSONObject. If it is a standard property
1545      * (Double, String, et al) then it is already wrapped. Otherwise, if it
1546      * comes from one of the java packages, turn it into a string. And if
1547      * it doesn't, try to wrap it in a JSONObject. If the wrapping fails,
1548      * then null is returned.
1549      *
1550      * @param object The object to wrap
1551      * @return The wrapped value
1552      */
1553     @SuppressWarnings("unchecked")
1554     public static Object wrap(Object object) {
1555         try {
1556             if (object == null) {
1557                 return NULL;
1558             }
1559             if (object instanceof LOGJSONObject || object instanceof JSONArray ||
1560                     NULL.equals(object) || object instanceof JSONString ||
1561                     object instanceof Byte || object instanceof Character ||
1562                     object instanceof Short || object instanceof Integer ||
1563                     object instanceof Long || object instanceof Boolean ||
1564                     object instanceof Float || object instanceof Double ||
1565                     object instanceof String) {
1566                 return object;
1567             }
1568
1569             if (object instanceof Collection) {
1570                 return new JSONArray((Collection<Object>) object);
1571             }
1572             if (object.getClass().isArray()) {
1573                 return new JSONArray(object);
1574             }
1575             if (object instanceof Map) {
1576                 return new LOGJSONObject((Map<String, Object>) object);
1577             }
1578             Package objectPackage = object.getClass().getPackage();
1579             String objectPackageName = objectPackage != null
1580                     ? objectPackage.getName()
1581                     : "";
1582             if (
1583                     objectPackageName.startsWith("java.") ||
1584                             objectPackageName.startsWith("javax.") ||
1585                             object.getClass().getClassLoader() == null
1586                     ) {
1587                 return object.toString();
1588             }
1589             return new LOGJSONObject(object);
1590         } catch (Exception exception) {
1591             intlogger.trace("Exception: ", exception);
1592             return null;
1593         }
1594     }
1595
1596
1597     /**
1598      * Write the contents of the JSONObject as JSON text to a writer.
1599      * For compactness, no whitespace is added.
1600      * <p>
1601      * Warning: This method assumes that the data structure is acyclical.
1602      *
1603      * @return The writer.
1604      * @throws JSONException
1605      */
1606     public Writer write(Writer writer) throws JSONException {
1607         return this.write(writer, 0, 0);
1608     }
1609
1610
1611     @SuppressWarnings("unchecked")
1612     static final Writer writeValue(Writer writer, Object value,
1613                                    int indentFactor, int indent) throws JSONException, IOException {
1614         if (value == null) {
1615             writer.write("null");
1616         } else if (value instanceof LOGJSONObject) {
1617             ((LOGJSONObject) value).write(writer, indentFactor, indent);
1618         } else if (value instanceof JSONArray) {
1619             ((JSONArray) value).write(writer, indentFactor, indent);
1620         } else if (value instanceof Map) {
1621             new LOGJSONObject((Map<String, Object>) value).write(writer, indentFactor, indent);
1622         } else if (value instanceof Collection) {
1623             new JSONArray((Collection<Object>) value).write(writer, indentFactor,
1624                     indent);
1625         } else if (value.getClass().isArray()) {
1626             new JSONArray(value).write(writer, indentFactor, indent);
1627         } else if (value instanceof Number) {
1628             writer.write(numberToString((Number) value));
1629         } else if (value instanceof Boolean) {
1630             writer.write(value.toString());
1631         } else if (value instanceof JSONString) {
1632             Object o;
1633             try {
1634                 o = ((JSONString) value).toJSONString();
1635             } catch (Exception e) {
1636                 throw new JSONException(e);
1637             }
1638             writer.write(o != null ? o.toString() : quote(value.toString()));
1639         } else {
1640             quote(value.toString(), writer);
1641         }
1642         return writer;
1643     }
1644
1645     static final void indent(Writer writer, int indent) throws IOException {
1646         for (int i = 0; i < indent; i += 1) {
1647             writer.write(' ');
1648         }
1649     }
1650
1651     /**
1652      * Write the contents of the JSONObject as JSON text to a writer. For
1653      * compactness, no whitespace is added.
1654      * <p>
1655      * Warning: This method assumes that the data structure is acyclical.
1656      *
1657      * @return The writer.
1658      * @throws JSONException
1659      */
1660     Writer write(Writer writer, int indentFactor, int indent)
1661             throws JSONException {
1662         try {
1663             boolean commanate = false;
1664             final int length = this.length();
1665             Iterator<String> keys = this.keys();
1666             writer.write('{');
1667
1668             if (length == 1) {
1669                 Object key = keys.next();
1670                 writer.write(quote(key.toString()));
1671                 writer.write(':');
1672                 if (indentFactor > 0) {
1673                     writer.write(' ');
1674                 }
1675                 writeValue(writer, this.map.get(key), indentFactor, indent);
1676             } else if (length != 0) {
1677                 final int newindent = indent + indentFactor;
1678                 while (keys.hasNext()) {
1679                     Object key = keys.next();
1680                     if (commanate) {
1681                         writer.write(',');
1682                     }
1683                     if (indentFactor > 0) {
1684                         writer.write('\n');
1685                     }
1686                     indent(writer, newindent);
1687                     writer.write(quote(key.toString()));
1688                     writer.write(':');
1689                     if (indentFactor > 0) {
1690                         writer.write(' ');
1691                     }
1692                     writeValue(writer, this.map.get(key), indentFactor,
1693                             newindent);
1694                     commanate = true;
1695                 }
1696                 if (indentFactor > 0) {
1697                     writer.write('\n');
1698                 }
1699                 indent(writer, indent);
1700             }
1701             writer.write('}');
1702             return writer;
1703         } catch (IOException exception) {
1704             throw new JSONException(exception);
1705         }
1706     }
1707 }