Checkstyle fixes for prov eelf and utils
[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  *
61  * <p>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  *
67  * <p>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  *
73  * <p>* produces the string <code>{"JSON": "Hello, World"}</code>.
74  *
75  * <p>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
99 public class LOGJSONObject {
100
101     /**
102      * The maximum number of keys in the key pool.
103      */
104     private static final int KEY_POOL_SIZE = 100;
105     private static final String USING_DEFAULT_VALUE = "Using defaultValue: ";
106     private static final String JSON_OBJECT_CONST = "JSONObject[";
107
108     /**
109      * Key pooling is like string interning, but without permanently tying up
110      * memory. To help conserve memory, storage of duplicated key strings in
111      * JSONObjects will be avoided by using a key pool to manage unique key
112      * string objects. This is used by JSONObject.put(string, object).
113      */
114     private static Map<String, Object> keyPool = new LinkedHashMap<>(KEY_POOL_SIZE);
115
116     private static final EELFLogger intlogger = EELFManager.getInstance().getLogger("InternalLog");
117
118     /**
119      * JSONObject.NULL is equivalent to the value that JavaScript calls null,
120      * whilst Java's null is equivalent to the value that JavaScript calls
121      * undefined.
122      */
123     private static final class Null {
124
125         /**
126          * There is only intended to be a single instance of the NULL object,
127          * so the clone method returns itself.
128          *
129          * @return NULL.
130          */
131         protected final Object clone() {
132             return this;
133         }
134
135         /**
136          * A Null object is equal to the null value and to itself.
137          *
138          * @param object An object to test for nullness.
139          * @return true if the object parameter is the JSONObject.NULL object
140          * or null.
141          */
142         public boolean equals(Object object) {
143             return object == null || object == this;
144         }
145
146         /**
147          * Returns a hash code value for the object. This method is
148          * supported for the benefit of hash tables such as those provided by
149          * {@link HashMap}.
150          *
151          * <p>* The general contract of {@code hashCode} is:
152          * <ul>
153          * <li>Whenever it is invoked on the same object more than once during
154          * an execution of a Java application, the {@code hashCode} method
155          * must consistently return the same integer, provided no information
156          * used in {@code equals} comparisons on the object is modified.
157          * This integer need not remain consistent from one execution of an
158          * application to another execution of the same application.
159          * <li>If two objects are equal according to the {@code equals(Object)}
160          * method, then calling the {@code hashCode} method on each of
161          * the two objects must produce the same integer result.
162          * <li>It is <em>not</em> required that if two objects are unequal
163          * according to the {@link Object#equals(Object)}
164          * method, then calling the {@code hashCode} method on each of the
165          * two objects must produce distinct integer results.  However, the
166          * programmer should be aware that producing distinct integer results
167          * for unequal objects may improve the performance of hash tables.
168          * </ul>
169          *
170          * <p>* As much as is reasonably practical, the hashCode method defined by
171          * class {@code Object} does return distinct integers for distinct
172          * objects. (This is typically implemented by converting the internal
173          * address of the object into an integer, but this implementation
174          * technique is not required by the
175          * Java&trade; programming language.)
176          *
177          * @return a hash code value for this object.
178          * @see Object#equals(Object)
179          * @see System#identityHashCode
180          */
181         @Override
182         public int hashCode() {
183             return super.hashCode();
184         }
185
186         /**
187          * Get the "null" string value.
188          *
189          * @return The string "null".
190          */
191         public String toString() {
192             return "null";
193         }
194     }
195
196     /**
197      * The map where the JSONObject's properties are kept.
198      */
199     private final Map<String, Object> map;
200
201     /**
202      * It is sometimes more convenient and less ambiguous to have a
203      * <code>NULL</code> object than to use Java's <code>null</code> value.
204      * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
205      * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
206      */
207     private static final Object NULL = new Null();
208
209     /**
210      * Construct an empty JSONObject.
211      */
212     public LOGJSONObject() {
213         this.map = new LinkedHashMap<>();
214     }
215
216     /**
217      * Construct a JSONObject from a subset of another JSONObject.
218      * An array of strings is used to identify the keys that should be copied.
219      * Missing keys are ignored.
220      *
221      * @param jo    A JSONObject.
222      * @param names An array of strings.
223      */
224     public LOGJSONObject(LOGJSONObject jo, String[] names) {
225         this();
226         for (int i = 0; i < names.length; i += 1) {
227             try {
228                 this.putOnce(names[i], jo.opt(names[i]));
229             } catch (Exception ignore) {
230                 intlogger.error("PROV0001 LOGJSONObject: " + ignore.getMessage(), ignore);
231             }
232         }
233     }
234
235     /**
236      * Construct a JSONObject from a JSONTokener.
237      *
238      * @param tokener A JSONTokener object containing the source string.
239      * @throws JSONException If there is a syntax error in the source string
240      *                       or a duplicated key.
241      */
242     public LOGJSONObject(JSONTokener tokener) {
243         this();
244         char chr;
245         String key;
246
247         if (tokener.nextClean() != '{') {
248             throw tokener.syntaxError("A JSONObject text must begin with '{'");
249         }
250         for (; ; ) {
251             chr = tokener.nextClean();
252             switch (chr) {
253                 case 0:
254                     throw tokener.syntaxError("A JSONObject text must end with '}'");
255                 case '}':
256                     return;
257                 default:
258                     tokener.back();
259                     key = tokener.nextValue().toString();
260             }
261
262             // The key is followed by ':'. We will also tolerate '=' or '=>'.
263
264             chr = tokener.nextClean();
265             if (chr == '=') {
266                 if (tokener.next() != '>') {
267                     tokener.back();
268                 }
269             } else if (chr != ':') {
270                 throw tokener.syntaxError("Expected a ':' after a key");
271             }
272             this.putOnce(key, tokener.nextValue());
273
274             // Pairs are separated by ','. We will also tolerate ';'.
275
276             switch (tokener.nextClean()) {
277                 case ';':
278                 case ',':
279                     if (tokener.nextClean() == '}') {
280                         return;
281                     }
282                     tokener.back();
283                     break;
284                 case '}':
285                     return;
286                 default:
287                     throw tokener.syntaxError("Expected a ',' or '}'");
288             }
289         }
290     }
291
292     /**
293      * Construct a JSONObject from a Map.
294      *
295      * @param map A map object that can be used to initialize the contents of
296      *            the JSONObject.
297      * @throws JSONException json exception
298      */
299     public LOGJSONObject(Map<String, Object> map) {
300         this.map = new LinkedHashMap<>();
301         if (map != null) {
302             Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
303             while (iterator.hasNext()) {
304                 Map.Entry<String, Object> entry = iterator.next();
305                 Object value = entry.getValue();
306                 if (value != null) {
307                     this.map.put(entry.getKey(), wrap(value));
308                 }
309             }
310         }
311     }
312
313     /**
314      * Construct a JSONObject from an Object using bean getters.
315      * It reflects on all of the public methods of the object.
316      * For each of the methods with no parameters and a name starting
317      * with <code>"get"</code> or <code>"is"</code> followed by an uppercase letter,
318      * the method is invoked, and a key and the value returned from the getter method
319      * are put into the new JSONObject.
320      *
321      * <p>* The key is formed by removing the <code>"get"</code> or <code>"is"</code> prefix.
322      * If the second remaining character is not upper case, then the first
323      * character is converted to lower case.
324      *
325      * <p>* For example, if an object has a method named <code>"getName"</code>, and
326      * if the result of calling <code>object.getName()</code> is <code>"Larry Fine"</code>,
327      * then the JSONObject will contain <code>"name": "Larry Fine"</code>.
328      *
329      * @param bean An object that has getter methods that should be used
330      *             to make a JSONObject.
331      */
332     public LOGJSONObject(Object bean) {
333         this();
334         this.populateMap(bean);
335     }
336
337     /**
338      * Accumulate values under a key. It is similar to the put method except
339      * that if there is already an object stored under the key then a
340      * JSONArray is stored under the key to hold all of the accumulated values.
341      * If there is already a JSONArray, then the new value is appended to it.
342      * In contrast, the put method replaces the previous value.
343      *
344      * <p>* If only one value is accumulated that is not a JSONArray, then the
345      * result will be the same as using put. But if multiple values are
346      * accumulated, then the result will be like append.
347      *
348      * @param key   A key string.
349      * @param value An object to be accumulated under the key.
350      * @return this.
351      * @throws JSONException If the value is an invalid number
352      *                       or if the key is null.
353      */
354     public LOGJSONObject accumulate(
355         String key,
356         Object value
357     ) {
358         testValidity(value);
359         Object object = this.opt(key);
360         if (object == null) {
361             this.put(key, value instanceof JSONArray
362                 ? new JSONArray().put(value)
363                 : value);
364         } else if (object instanceof JSONArray) {
365             ((JSONArray) object).put(value);
366         } else {
367             this.put(key, new JSONArray().put(object).put(value));
368         }
369         return this;
370     }
371
372     /**
373      * Append values to the array under a key. If the key does not exist in the
374      * JSONObject, then the key is put in the JSONObject with its value being a
375      * JSONArray containing the value parameter. If the key was already
376      * associated with a JSONArray, then the value parameter is appended to it.
377      *
378      * @param key   A key string.
379      * @param value An object to be accumulated under the key.
380      * @return this.
381      * @throws JSONException If the key is null or if the current value
382      *                       associated with the key is not a JSONArray.
383      */
384     public LOGJSONObject append(String key, Object value) {
385         testValidity(value);
386         Object object = this.opt(key);
387         if (object == null) {
388             this.put(key, new JSONArray().put(value));
389         } else if (object instanceof JSONArray) {
390             this.put(key, ((JSONArray) object).put(value));
391         } else {
392             throw new JSONException(JSON_OBJECT_CONST + key
393                 + "] is not a JSONArray.");
394         }
395         return this;
396     }
397
398     /**
399      * Produce a string from a double. The string "null" will be returned if
400      * the number is not finite.
401      *
402      * @param dub A double.
403      * @return A String.
404      */
405     public static String doubleToString(double dub) {
406         if (Double.isInfinite(dub) || Double.isNaN(dub)) {
407             return "null";
408         }
409
410         // Shave off trailing zeros and decimal point, if possible.
411
412         String string = Double.toString(dub);
413         if (string.indexOf('.') > 0 && string.indexOf('e') < 0
414             && string.indexOf('E') < 0) {
415             while (string.endsWith("0")) {
416                 string = string.substring(0, string.length() - 1);
417             }
418             if (string.endsWith(".")) {
419                 string = string.substring(0, string.length() - 1);
420             }
421         }
422         return string;
423     }
424
425     /**
426      * Get the value object associated with a key.
427      *
428      * @param key A key string.
429      * @return The object associated with the key.
430      * @throws JSONException if the key is not found.
431      */
432     public Object get(String key) {
433         if (key == null) {
434             throw new JSONException("Null key.");
435         }
436         Object object = this.opt(key);
437         if (object == null) {
438             throw new JSONException(JSON_OBJECT_CONST + quote(key)
439                 + "] not found.");
440         }
441         return object;
442     }
443
444     /**
445      * Get the boolean value associated with a key.
446      *
447      * @param key A key string.
448      * @return The truth.
449      * @throws JSONException if the value is not a Boolean or the String "true" or "false".
450      */
451     public boolean getBoolean(String key) {
452         Object object = this.get(key);
453         if (object.equals(Boolean.FALSE)
454             || (object instanceof String
455             && "false".equalsIgnoreCase((String) object))) {
456             return false;
457         } else if (object.equals(Boolean.TRUE)
458             || (object instanceof String
459                 && "true".equalsIgnoreCase((String) object))) {
460             return true;
461         }
462         throw new JSONException(JSON_OBJECT_CONST + quote(key)
463             + "] is not a Boolean.");
464     }
465
466     /**
467      * Get the double value associated with a key.
468      *
469      * @param key A key string.
470      * @return The numeric value.
471      * @throws JSONException if the key is not found or
472      *                       if the value is not a Number object and cannot be converted to a number.
473      */
474     public double getDouble(String key) {
475         Object object = this.get(key);
476         try {
477             return object instanceof Number
478                 ? ((Number) object).doubleValue()
479                 : Double.parseDouble((String) object);
480         } catch (Exception e) {
481             intlogger.error(JSON_OBJECT_CONST + quote(key) + "] is not a number.", e);
482             throw new JSONException(JSON_OBJECT_CONST + quote(key) + "] is not a number.");
483         }
484     }
485
486     /**
487      * Get the int value associated with a key.
488      *
489      * @param key A key string.
490      * @return The integer value.
491      * @throws JSONException if the key is not found or if the value cannot
492      *                       be converted to an integer.
493      */
494     public int getInt(String key) {
495         Object object = this.get(key);
496         try {
497             return object instanceof Number
498                 ? ((Number) object).intValue()
499                 : Integer.parseInt((String) object);
500         } catch (Exception e) {
501             intlogger.error(JSON_OBJECT_CONST + quote(key) + "] is not an int.", e);
502             throw new JSONException(JSON_OBJECT_CONST + quote(key) + "] is not an int.");
503         }
504     }
505
506     /**
507      * Get the JSONArray value associated with a key.
508      *
509      * @param key A key string.
510      * @return A JSONArray which is the value.
511      * @throws JSONException if the key is not found or
512      *                       if the value is not a JSONArray.
513      */
514     public JSONArray getJSONArray(String key) {
515         Object object = this.get(key);
516         if (object instanceof JSONArray) {
517             return (JSONArray) object;
518         }
519         throw new JSONException(JSON_OBJECT_CONST + quote(key)
520             + "] is not a JSONArray.");
521     }
522
523     /**
524      * Get the JSONObject value associated with a key.
525      *
526      * @param key A key string.
527      * @return A JSONObject which is the value.
528      * @throws JSONException if the key is not found or
529      *                       if the value is not a JSONObject.
530      */
531     public LOGJSONObject getJSONObject(String key) {
532         Object object = this.get(key);
533         if (object instanceof LOGJSONObject) {
534             return (LOGJSONObject) object;
535         }
536         throw new JSONException(JSON_OBJECT_CONST + quote(key)
537             + "] is not a JSONObject.");
538     }
539
540     /**
541      * Get the long value associated with a key.
542      *
543      * @param key A key string.
544      * @return The long value.
545      * @throws JSONException if the key is not found or if the value cannot
546      *                       be converted to a long.
547      */
548     public long getLong(String key) {
549         Object object = this.get(key);
550         try {
551             return object instanceof Number
552                 ? ((Number) object).longValue()
553                 : Long.parseLong((String) object);
554         } catch (Exception e) {
555             intlogger.error(JSON_OBJECT_CONST + quote(key) + "] is not a long.", e);
556             throw new JSONException(JSON_OBJECT_CONST + quote(key) + "] is not a long.");
557         }
558     }
559
560     /**
561      * Get an array of field names from a JSONObject.
562      *
563      * @return An array of field names, or null if there are no names.
564      */
565     public static String[] getNames(LOGJSONObject jo) {
566         int length = jo.length();
567         if (length == 0) {
568             return new String[]{};
569         }
570         Iterator<String> iterator = jo.keys();
571         String[] names = new String[length];
572         int iter = 0;
573         while (iterator.hasNext()) {
574             names[iter] = iterator.next();
575             iter += 1;
576         }
577         return names;
578     }
579
580     /**
581      * Get the string associated with a key.
582      *
583      * @param key A key string.
584      * @return A string which is the value.
585      * @throws JSONException if there is no string value for the key.
586      */
587     public String getString(String key) {
588         Object object = this.get(key);
589         if (object instanceof String) {
590             return (String) object;
591         }
592         throw new JSONException(JSON_OBJECT_CONST + quote(key)
593             + "] not a string.");
594     }
595
596     /**
597      * Determine if the JSONObject contains a specific key.
598      *
599      * @param key A key string.
600      * @return true if the key exists in the JSONObject.
601      */
602     public boolean has(String key) {
603         return this.map.containsKey(key);
604     }
605
606     /**
607      * Increment a property of a JSONObject. If there is no such property,
608      * create one with a value of 1. If there is such a property, and if
609      * it is an Integer, Long, Double, or Float, then add one to it.
610      *
611      * @param key A key string.
612      * @return this.
613      * @throws JSONException If there is already a property with this name
614      *                       that is not an Integer, Long, Double, or Float.
615      */
616     public LOGJSONObject increment(String key) {
617         Object value = this.opt(key);
618         if (value == null) {
619             this.put(key, 1);
620         } else if (value instanceof Integer) {
621             this.put(key, ((Integer) value).intValue() + 1);
622         } else if (value instanceof Long) {
623             this.put(key, ((Long) value).longValue() + 1);
624         } else if (value instanceof Double) {
625             this.put(key, ((Double) value).doubleValue() + 1);
626         } else if (value instanceof Float) {
627             this.put(key, ((Float) value).floatValue() + 1);
628         } else {
629             throw new JSONException("Unable to increment [" + quote(key) + "].");
630         }
631         return this;
632     }
633
634     /**
635      * Get an enumeration of the keys of the JSONObject.
636      *
637      * @return An iterator of the keys.
638      */
639     public Iterator<String> keys() {
640         return this.keySet().iterator();
641     }
642
643     /**
644      * Get a set of keys of the JSONObject.
645      *
646      * @return A keySet.
647      */
648     public Set<String> keySet() {
649         return this.map.keySet();
650     }
651
652     /**
653      * Get the number of keys stored in the JSONObject.
654      *
655      * @return The number of keys in the JSONObject.
656      */
657     public int length() {
658         return this.map.size();
659     }
660
661     /**
662      * Produce a JSONArray containing the names of the elements of this
663      * JSONObject.
664      *
665      * @return A JSONArray containing the key strings, or null if the JSONObject
666      * is empty.
667      */
668     public JSONArray names() {
669         JSONArray ja = new JSONArray();
670         Iterator<String> keys = this.keys();
671         while (keys.hasNext()) {
672             ja.put(keys.next());
673         }
674         return ja.length() == 0 ? null : ja;
675     }
676
677     /**
678      * Produce a string from a Number.
679      *
680      * @param number A Number
681      * @return A String.
682      * @throws JSONException If n is a non-finite number.
683      */
684     public static String numberToString(Number number) {
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 string key
931      * @param value obj value
932      * @return this LOGJSONObject
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. In JSON text, a string
948      * cannot contain a control character or an unescaped quote or backslash.
949      *
950      * @param string A String
951      * @return A String correctly formatted for insertion in a JSON text.
952      */
953     public static String quote(String string) {
954         StringWriter sw = new StringWriter();
955         synchronized (sw.getBuffer()) {
956             try {
957                 return quote(string, sw).toString();
958             } catch (IOException e) {
959                 intlogger.trace("Ignore Exception message: ", e);
960                 return "";
961             }
962         }
963     }
964
965     /**
966      * Writer method.
967      * @param string string
968      * @param writer writer
969      * @return A writer
970      * @throws IOException input/output exception
971      */
972     public static Writer quote(String string, Writer writer) throws IOException {
973         if (string == null || string.length() == 0) {
974             writer.write("\"\"");
975             return writer;
976         }
977
978         char char1;
979         char char2 = 0;
980         String hhhh;
981         int len = string.length();
982
983         writer.write('"');
984         for (int i = 0; i < len; i += 1) {
985             char1 = char2;
986             char2 = string.charAt(i);
987             switch (char2) {
988                 case '\\':
989                 case '"':
990                     writer.write('\\');
991                     writer.write(char2);
992                     break;
993                 case '/':
994                     if (char1 == '<') {
995                         writer.write('\\');
996                     }
997                     writer.write(char2);
998                     break;
999                 case '\b':
1000                     writer.write("\\b");
1001                     break;
1002                 case '\t':
1003                     writer.write("\\t");
1004                     break;
1005                 case '\n':
1006                     writer.write("\\n");
1007                     break;
1008                 case '\f':
1009                     writer.write("\\f");
1010                     break;
1011                 case '\r':
1012                     writer.write("\\r");
1013                     break;
1014                 default:
1015                     if (char2 < ' ' || (char2 >= '\u0080' && char2 < '\u00a0')
1016                         || (char2 >= '\u2000' && char2 < '\u2100')) {
1017                         writer.write("\\u");
1018                         hhhh = Integer.toHexString(char2);
1019                         writer.write("0000", 0, 4 - hhhh.length());
1020                         writer.write(hhhh);
1021                     } else {
1022                         writer.write(char2);
1023                     }
1024             }
1025         }
1026         writer.write('"');
1027         return writer;
1028     }
1029
1030     /**
1031      * Remove a name and its value, if present.
1032      *
1033      * @param key The name to be removed.
1034      * @return The value that was associated with the name,
1035      * or null if there was no value.
1036      */
1037     public Object remove(String key) {
1038         return this.map.remove(key);
1039     }
1040
1041     /**
1042      * Try to convert a string into a number, boolean, or null. If the string
1043      * can't be converted, return the string.
1044      *
1045      * @param string A String.
1046      * @return A simple JSON value.
1047      */
1048     public static Object stringToValue(String string) {
1049         Double dub;
1050         if ("".equals(string)) {
1051             return string;
1052         }
1053         if ("true".equalsIgnoreCase(string)) {
1054             return Boolean.TRUE;
1055         }
1056         if ("false".equalsIgnoreCase(string)) {
1057             return Boolean.FALSE;
1058         }
1059         if ("null".equalsIgnoreCase(string)) {
1060             return LOGJSONObject.NULL;
1061         }
1062
1063         /*
1064          * If it might be a number, try converting it.
1065          * If a number cannot be produced, then the value will just
1066          * be a string. Note that the plus and implied string
1067          * conventions are non-standard. A JSON parser may accept
1068          * non-JSON forms as long as it accepts all correct JSON forms.
1069          */
1070
1071         char chr = string.charAt(0);
1072         if ((chr >= '0' && chr <= '9') || chr == '.' || chr == '-' || chr == '+') {
1073             try {
1074                 if (string.indexOf('.') > -1 || string.indexOf('e') > -1
1075                     || string.indexOf('E') > -1) {
1076                     dub = Double.valueOf(string);
1077                     if (!dub.isInfinite() && !dub.isNaN()) {
1078                         return dub;
1079                     }
1080                 } else {
1081                     Long myLong = new Long(string);
1082                     if (myLong == myLong.intValue()) {
1083                         return myLong.intValue();
1084                     } else {
1085                         return myLong;
1086                     }
1087                 }
1088             } catch (Exception e) {
1089                 intlogger.trace("Ignore Exception message: ", e);
1090             }
1091         }
1092         return string;
1093     }
1094
1095     /**
1096      * Throw an exception if the object is a NaN or infinite number.
1097      *
1098      * @param obj The object to test.
1099      * @throws JSONException If o is a non-finite number.
1100      */
1101     public static void testValidity(Object obj) {
1102         if (obj != null) {
1103             if (obj instanceof Double) {
1104                 if (((Double) obj).isInfinite() || ((Double) obj).isNaN()) {
1105                     throw new JSONException(
1106                         "JSON does not allow non-finite numbers.");
1107                 }
1108             } else if (obj instanceof Float && (((Float) obj).isInfinite() || ((Float) obj).isNaN())) {
1109                 throw new JSONException(
1110                     "JSON does not allow non-finite numbers.");
1111             }
1112         }
1113     }
1114
1115     /**
1116      * Produce a JSONArray containing the values of the members of this
1117      * JSONObject.
1118      *
1119      * @param names A JSONArray containing a list of key strings. This
1120      *              determines the sequence of the values in the result.
1121      * @return A JSONArray of values.
1122      * @throws JSONException If any of the values are non-finite numbers.
1123      */
1124     public JSONArray toJSONArray(JSONArray names) {
1125         if (names == null || names.length() == 0) {
1126             return null;
1127         }
1128         JSONArray ja = new JSONArray();
1129         for (int i = 0; i < names.length(); i += 1) {
1130             ja.put(this.opt(names.getString(i)));
1131         }
1132         return ja;
1133     }
1134
1135     /**
1136      * Make a JSON text of this JSONObject. For compactness, no whitespace
1137      * is added. If this would not result in a syntactically correct JSON text,
1138      * then null will be returned instead.
1139      *
1140      * <p>* Warning: This method assumes that the data structure is acyclical.
1141      *
1142      * @return a printable, displayable, portable, transmittable
1143      * representation of the object, beginning
1144      * with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1145      * with <code>}</code>&nbsp;<small>(right brace)</small>.
1146      */
1147     public String toString() {
1148         try {
1149             return this.toString(0);
1150         } catch (Exception e) {
1151             intlogger.trace("Exception: ", e);
1152             return "";
1153         }
1154     }
1155
1156     /**
1157      * Make a prettyprinted JSON text of this JSONObject.
1158      *
1159      * <p>* Warning: This method assumes that the data structure is acyclical.
1160      *
1161      * @param indentFactor The number of spaces to add to each level of
1162      *                     indentation.
1163      * @return a printable, displayable, portable, transmittable
1164      * representation of the object, beginning
1165      * with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1166      * with <code>}</code>&nbsp;<small>(right brace)</small>.
1167      * @throws JSONException If the object contains an invalid number.
1168      */
1169     public String toString(int indentFactor) {
1170         StringWriter writer = new StringWriter();
1171         synchronized (writer.getBuffer()) {
1172             return this.write(writer, indentFactor, 0).toString();
1173         }
1174     }
1175
1176     /**
1177      * Make a JSON text of an Object value. If the object has an
1178      * value.toJSONString() method, then that method will be used to produce
1179      * the JSON text. The method is required to produce a strictly
1180      * conforming text. If the object does not contain a toJSONString
1181      * method (which is the most common case), then a text will be
1182      * produced by other means. If the value is an array or Collection,
1183      * then a JSONArray will be made from it and its toJSONString method
1184      * will be called. If the value is a MAP, then a JSONObject will be made
1185      * from it and its toJSONString method will be called. Otherwise, the
1186      * value's toString method will be called, and the result will be quoted.
1187      *
1188      * <p>* Warning: This method assumes that the data structure is acyclical.
1189      *
1190      * @param value The value to be serialized.
1191      * @return a printable, displayable, transmittable
1192      * representation of the object, beginning
1193      * with <code>{</code>&nbsp;<small>(left brace)</small> and ending
1194      * with <code>}</code>&nbsp;<small>(right brace)</small>.
1195      * @throws JSONException If the value is or contains an invalid number.
1196      */
1197     @SuppressWarnings("unchecked")
1198     public static String valueToString(Object value) {
1199         if (value == null) {
1200             return "null";
1201         }
1202         if (value instanceof JSONString) {
1203             String object;
1204             try {
1205                 object = ((JSONString) value).toJSONString();
1206             } catch (Exception e) {
1207                 throw new JSONException(e);
1208             }
1209             if (object != null) {
1210                 return object;
1211             }
1212             throw new JSONException("Bad value from toJSONString: " + object);
1213         }
1214         if (value instanceof Number) {
1215             return numberToString((Number) value);
1216         }
1217         if (value instanceof Boolean || value instanceof LOGJSONObject
1218             || value instanceof JSONArray) {
1219             return value.toString();
1220         }
1221         if (value instanceof Map) {
1222             return new LOGJSONObject((Map<String, Object>) value).toString();
1223         }
1224         if (value instanceof Collection) {
1225             return new JSONArray((Collection<Object>) value).toString();
1226         }
1227         if (value.getClass().isArray()) {
1228             return new JSONArray(value).toString();
1229         }
1230         return quote(value.toString());
1231     }
1232
1233     /**
1234      * Wrap an object, if necessary. If the object is null, return the NULL
1235      * object. If it is an array or collection, wrap it in a JSONArray. If
1236      * it is a map, wrap it in a JSONObject. If it is a standard property
1237      * (Double, String, et al) then it is already wrapped. Otherwise, if it
1238      * comes from one of the java packages, turn it into a string. And if
1239      * it doesn't, try to wrap it in a JSONObject. If the wrapping fails,
1240      * then null is returned.
1241      *
1242      * @param object The object to wrap
1243      * @return The wrapped value
1244      */
1245     @SuppressWarnings("unchecked")
1246     public static Object wrap(Object object) {
1247         try {
1248             if (object == null) {
1249                 return NULL;
1250             }
1251             if (object instanceof LOGJSONObject || object instanceof JSONArray
1252                 || NULL.equals(object) || object instanceof JSONString
1253                 || object instanceof Byte || object instanceof Character
1254                 || object instanceof Short || object instanceof Integer
1255                 || object instanceof Long || object instanceof Boolean
1256                 || object instanceof Float || object instanceof Double
1257                 || object instanceof String) {
1258                 return object;
1259             }
1260
1261             if (object instanceof Collection) {
1262                 return new JSONArray((Collection<Object>) object);
1263             }
1264             if (object.getClass().isArray()) {
1265                 return new JSONArray(object);
1266             }
1267             if (object instanceof Map) {
1268                 return new LOGJSONObject((Map<String, Object>) object);
1269             }
1270             Package objectPackage = object.getClass().getPackage();
1271             String objectPackageName = objectPackage != null
1272                 ? objectPackage.getName()
1273                 : "";
1274             if (
1275                 objectPackageName.startsWith("java.")
1276                     || objectPackageName.startsWith("javax.")
1277                     || object.getClass().getClassLoader() == null
1278             ) {
1279                 return object.toString();
1280             }
1281             return new LOGJSONObject(object);
1282         } catch (Exception exception) {
1283             intlogger.trace("Exception: ", exception);
1284             return null;
1285         }
1286     }
1287
1288     @SuppressWarnings("unchecked")
1289     static Writer writeValue(Writer writer, Object value, int indentFactor, int indent) throws IOException {
1290         if (value == null) {
1291             writer.write("null");
1292         } else if (value instanceof LOGJSONObject) {
1293             ((LOGJSONObject) value).write(writer, indentFactor, indent);
1294         } else if (value instanceof JSONArray) {
1295             ((JSONArray) value).write(writer, indentFactor, indent);
1296         } else if (value instanceof Map) {
1297             new LOGJSONObject((Map<String, Object>) value).write(writer, indentFactor, indent);
1298         } else if (value instanceof Collection) {
1299             new JSONArray((Collection<Object>) value).write(writer, indentFactor, indent);
1300         } else if (value.getClass().isArray()) {
1301             new JSONArray(value).write(writer, indentFactor, indent);
1302         } else if (value instanceof Number) {
1303             writer.write(numberToString((Number) value));
1304         } else if (value instanceof Boolean) {
1305             writer.write(value.toString());
1306         } else if (value instanceof JSONString) {
1307             Object obj;
1308             try {
1309                 obj = ((JSONString) value).toJSONString();
1310             } catch (Exception e) {
1311                 throw new JSONException(e);
1312             }
1313             writer.write(obj != null ? obj.toString() : quote(value.toString()));
1314         } else {
1315             quote(value.toString(), writer);
1316         }
1317         return writer;
1318     }
1319
1320     private static void indent(Writer writer, int indent) throws IOException {
1321         for (int i = 0; i < indent; i += 1) {
1322             writer.write(' ');
1323         }
1324     }
1325
1326     /**
1327      * Write the contents of the JSONObject as JSON text to a writer. For
1328      * compactness, no whitespace is added.
1329      *
1330      * <p>* Warning: This method assumes that the data structure is acyclical.
1331      *
1332      * @return The writer.
1333      * @throws JSONException JSON exception
1334      */
1335     Writer write(Writer writer, int indentFactor, int indent) {
1336         try {
1337             boolean commanate = false;
1338             final int length = this.length();
1339             Iterator<String> keys = this.keys();
1340             writer.write('{');
1341
1342             if (length == 1) {
1343                 Object key = keys.next();
1344                 writer.write(quote(key.toString()));
1345                 writer.write(':');
1346                 if (indentFactor > 0) {
1347                     writer.write(' ');
1348                 }
1349                 writeValue(writer, this.map.get(key), indentFactor, indent);
1350             } else if (length != 0) {
1351                 final int newindent = indent + indentFactor;
1352                 while (keys.hasNext()) {
1353                     if (commanate) {
1354                         writer.write(',');
1355                     }
1356                     if (indentFactor > 0) {
1357                         writer.write('\n');
1358                     }
1359                     indent(writer, newindent);
1360                     Object key = keys.next();
1361                     writer.write(quote(key.toString()));
1362                     writer.write(':');
1363                     if (indentFactor > 0) {
1364                         writer.write(' ');
1365                     }
1366                     writeValue(writer, this.map.get(key), indentFactor,
1367                         newindent);
1368                     commanate = true;
1369                 }
1370                 if (indentFactor > 0) {
1371                     writer.write('\n');
1372                 }
1373                 indent(writer, indent);
1374             }
1375             writer.write('}');
1376             return writer;
1377         } catch (IOException exception) {
1378             throw new JSONException(exception);
1379         }
1380     }
1381 }