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