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