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