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