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