View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyrighted [year] [name of copyright owner]".
13   *
14   * Copyright © 2010–2011 ApexIdentity Inc. All rights reserved.
15   * Portions Copyrighted 2011-2017 ForgeRock AS.
16   */
17  
18  package org.forgerock.json;
19  
20  import java.util.AbstractMap;
21  import java.util.AbstractSet;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import org.forgerock.util.Function;
33  import org.forgerock.util.RangeSet;
34  
35  /**
36   * Represents a value in a JSON object model structure. JSON values are
37   * represented with standard Java objects: {@link String}, {@link Number},
38   * {@link Map}, {@link List}, {@link Boolean} and {@code null}.
39   */
40  public class JsonValue implements Cloneable, Iterable<JsonValue> {
41  
42      /**
43       * Returns a mutable JSON array containing the provided objects. This method
44       * is provided as a convenience method for constructing JSON arrays. Example
45       * usage:
46       *
47       * <pre>
48       * JsonValue value = json(array(1, 2, 3));
49       * </pre>
50       *
51       * @param objects
52       *            The array elements.
53       * @return A JSON array.
54       */
55      public static List<Object> array(final Object... objects) {
56          List<Object> array = new ArrayList<>(objects.length);
57          for (Object o : objects) {
58              array.add(unwrap(o));
59          }
60          return array;
61      }
62  
63      /**
64       * Returns a JSON field for inclusion in a JSON object using
65       * {@link #object(java.util.Map.Entry...) object} only if its value is not {@code null}.
66       * Example usage:
67       *
68       * <pre>
69       * JsonValue value = json(object(fieldIfNotNull(&quot;uid&quot;, getUid()));
70       * </pre>
71       * <p>
72       * Note: This feature depends on the {@link #object(java.util.Map.Entry...)} method that
73       * checks if the entry is not {@code null} before including it into the map.
74       * </p>
75       *
76       * @param key
77       *            The JSON field name.
78       * @param value
79       *            The JSON field value (may be {@code null}).
80       * @return The JSON field for inclusion in a JSON object or {@code null}.
81       * @see #object(java.util.Map.Entry...)
82       */
83      public static Map.Entry<String, Object> fieldIfNotNull(final String key, final Object value) {
84          return value != null ? field(key, value) : null;
85      }
86  
87      /**
88       * Returns a JSON field for inclusion in a JSON object using
89       * {@link #object(java.util.Map.Entry...) object}. This method is provided
90       * as a convenience method for constructing JSON objects. Example usage:
91       *
92       * <pre>
93       * JsonValue value = json(object(field(&quot;uid&quot;, &quot;bjensen&quot;), field(&quot;age&quot;, 30)));
94       * </pre>
95       *
96       * @param key
97       *            The JSON field name.
98       * @param value
99       *            The JSON field value.
100      * @return The JSON field for inclusion in a JSON object.
101      */
102     public static Map.Entry<String, Object> field(final String key, final Object value) {
103         return new AbstractMap.SimpleImmutableEntry<>(key, unwrap(value));
104     }
105 
106     /**
107      * Returns a JSON value whose content is the provided object. This method is
108      * provided as a convenience method for constructing JSON objects, instead
109      * of using {@link #JsonValue(Object)}. Example usage:
110      *
111      * <pre>
112      * JsonValue value =
113      *         json(object(field(&quot;uid&quot;, &quot;bjensen&quot;),
114      *                     field(&quot;roles&quot;, array(&quot;sales&quot;, &quot;marketing&quot;))));
115      * </pre>
116      *
117      * @param object
118      *            the Java object representing the JSON value.
119      * @return The JSON value.
120      */
121     public static JsonValue json(final Object object) {
122         return new JsonValue(unwrap(object));
123     }
124 
125     /**
126      * Returns a JSON object comprised of the provided JSON
127      * {@link #field(String, Object) fields}. This method is provided as a
128      * convenience method for constructing JSON objects. Example usage:
129      *
130      * <pre>
131      * JsonValue value = json(object(field(&quot;uid&quot;, &quot;bjensen&quot;), field(&quot;age&quot;, 30)));
132      * </pre>
133      *
134      * @param fields
135      *            The list of {@link #field(String, Object) fields} to include
136      *            in the JSON object. {@code null} elements are allowed, but are
137      *            not included in the returned map (this makes it easier to
138      *            include optional elements).
139      * @return The JSON object.
140      */
141     @SafeVarargs
142     public static Map<String, Object> object(final Map.Entry<String, Object>... fields) {
143         final Map<String, Object> object = object(fields.length);
144         for (final Map.Entry<String, Object> field : fields) {
145             if (field != null) {
146                 object.put(field.getKey(), unwrap(field.getValue()));
147             }
148         }
149         return object;
150     }
151 
152     /**
153      * Produces an empty JSON object pre-allocated for {@code size}
154      * {@link #field(String, Object) fields}. This method is provided as a
155      * convenience method for constructing JSON objects. Example usage:
156      *
157      * <pre>
158      * JsonValue value = json(object(20));
159      * for (Map.Entry&lt;String, Object&gt; entry : someMap.entrySet()) {
160      *     value.put(entry.getKey(), entry.getValue());
161      * }
162      * </pre>
163      *
164      * @param size
165      *            The size of the JSON object to allocate.
166      * @return The [empty] JSON object.
167      */
168     public static Map<String, Object> object(int size) {
169         return new LinkedHashMap<>(size);
170     }
171 
172     /**
173      * Returns the key as an list index value. If the string does not represent
174      * a valid list index value, then {@code -1} is returned.
175      *
176      * @param key
177      *            the key to be converted into an list index value.
178      * @return the converted index value, or {@code -1} if invalid.
179      */
180     public static int toIndex(final String key) {
181         if (key == null || key.isEmpty()) {
182             return -1;
183         }
184 
185         // verify that every character is a digit (this also prevents negative values)
186         int result = 0;
187 
188         for (int i = 0; i < key.length(); ++i) {
189             final char c = key.charAt(i);
190             if (c < '0' || c > '9') {
191                 return -1;
192             }
193             result = result * 10 + (c - '0');
194         }
195         return result;
196     }
197 
198     /**
199      * Unwrap the object if it is a JsonValue - used when combining JsonValues so we
200      * do not get nested JsonValue wrappers.
201      *
202      * @param object the object to unwrap.
203      * @return the unwrapped object.
204      */
205     private static Object unwrap(final Object object) {
206         return object instanceof JsonValue
207                 ? ((JsonValue) object).getObject()
208                 : object;
209     }
210 
211     /** The Java object representing this JSON value. */
212     private Object object;
213 
214     /** The pointer to the value within a JSON structure. */
215     private JsonPointer pointer;
216 
217     /**
218      * Constructs a JSON value object with a given object. This constructor will
219      * automatically unwrap {@link JsonValue} objects.
220      *
221      * @param object
222      *            the Java object representing the JSON value.
223      */
224     public JsonValue(final Object object) {
225         this(object, null);
226     }
227 
228     /**
229      * Constructs a JSON value object with a given object and pointer. This
230      * constructor will automatically unwrap {@link JsonValue} objects.
231      *
232      * @param object
233      *            the Java object representing the JSON value.
234      * @param pointer
235      *            the pointer to the value in a JSON structure.
236      */
237     public JsonValue(final Object object, final JsonPointer pointer) {
238         this.object = object;
239         this.pointer = pointer;
240         if (object instanceof JsonValue) {
241             final JsonValue jv = (JsonValue) object;
242             this.object = jv.object;
243             if (pointer == null) {
244                 this.pointer = jv.pointer;
245             }
246         }
247         if (this.pointer == null) {
248             this.pointer = new JsonPointer();
249         }
250     }
251 
252     /**
253      * Adds the specified value to the list. Adding a value to a list shifts any
254      * existing elements at or above the specified index to the right by one.
255      *
256      * @param index
257      *            the {@code List} index of the value to add.
258      * @param object
259      *            the java object to add.
260      * @return this JSON value.
261      * @throws JsonValueException
262      *             if this JSON value is not a {@code List} or index is out of
263      *             range.
264      */
265     public JsonValue add(final int index, final Object object) {
266         final List<Object> list = required().asList();
267         if (index < 0 || index > list.size()) {
268             throw new JsonValueException(this, "List index out of range: " + index);
269         }
270         list.add(index, unwrap(object));
271         return this;
272     }
273 
274     /**
275      * Adds the value identified by the specified pointer, relative to this
276      * value as root. If doing so would require the creation of a new object or
277      * list, a {@code JsonValueException} will be thrown.
278      * <p>
279      * NOTE: values may be added to a list using the reserved JSON pointer token
280      * "-". For example, the pointer "/a/b/-" will add a new element to the list
281      * referenced by "/a/b".
282      *
283      * @param pointer
284      *            identifies the child value to add.
285      * @param object
286      *            the Java object value to add.
287      * @return this JSON value.
288      * @throws JsonValueException
289      *             if the specified pointer is invalid.
290      */
291     public JsonValue add(final JsonPointer pointer, final Object object) {
292         navigateToParentOf(pointer).required().addToken(pointer.leaf(), object);
293         return this;
294     }
295 
296     /**
297      * Adds the specified value to the end of the list. This method is
298      * equivalent to the following code:
299      *
300      * <pre>
301      * add(size(), object);
302      * </pre>
303      *
304      * @param object
305      *            the java object to add.
306      * @return this JSON value.
307      * @throws JsonValueException
308      *             if this JSON value is not a {@code List}.
309      */
310     public JsonValue add(final Object object) {
311         if (isList()) {
312             return add(size(), object);
313         }
314         throw new JsonValueException(this, "Expecting a List");
315     }
316 
317     /**
318      * Adds the specified value.
319      * <p>
320      * If adding to a list value, the specified key must be parseable as an
321      * unsigned base-10 integer and be less than or equal to the list size.
322      * Adding a value to a list shifts any existing elements at or above the
323      * specified index to the right by one.
324      *
325      * @param key
326      *            the {@code Map} key or {@code List} index to add.
327      * @param object
328      *            the Java object to add.
329      * @return this JSON value.
330      * @throws JsonValueException
331      *             if not a {@code Map} or {@code List}, the {@code Map} key
332      *             already exists, or the {@code List} index is out of range.
333      */
334     public JsonValue add(final String key, final Object object) {
335         if (isMap()) {
336             final Map<String, Object> map = asMap();
337             if (map.containsKey(key)) {
338                 throw new JsonValueException(this, "Map key " + key + " already exists");
339             }
340             map.put(key, unwrap(object));
341         } else if (isList()) {
342             add(toIndex(key), object);
343         } else {
344             throw new JsonValueException(this, "Expecting a Map or List");
345         }
346         return this;
347     }
348 
349     /**
350      * Adds the value identified by the specified pointer, relative to this
351      * value as root. Missing parent objects or lists will be created on demand.
352      * <p>
353      * NOTE: values may be added to a list using the reserved JSON pointer token
354      * "-". For example, the pointer "/a/b/-" will add a new element to the list
355      * referenced by "/a/b".
356      *
357      * @param pointer
358      *            identifies the child value to add.
359      * @param object
360      *            the Java object value to add.
361      * @return this JSON value.
362      * @throws JsonValueException
363      *             if the specified pointer is invalid.
364      */
365     public JsonValue addPermissive(final JsonPointer pointer, final Object object) {
366         navigateToParentOfPermissive(pointer).addToken(pointer.leaf(), object);
367         return this;
368     }
369 
370     /**
371      * Returns the JSON value as a {@link Boolean} object. If the value is
372      * {@code null}, this method returns {@code null}.
373      *
374      * @return the boolean value.
375      * @throws JsonValueException
376      *             if the JSON value is not a boolean type.
377      */
378     public Boolean asBoolean() {
379         return (object == null ? null : (Boolean) (expect(Boolean.class).object));
380     }
381 
382     /**
383      * Returns the JSON value as a {@link Double} object. This may involve
384      * rounding. If the JSON value is {@code null}, this method returns
385      * {@code null}.
386      *
387      * @return the double-precision floating point value.
388      * @throws JsonValueException
389      *             if the JSON value is not a number.
390      */
391     public Double asDouble() {
392         return (object == null ? null : Double.valueOf(asNumber().doubleValue()));
393     }
394 
395     /**
396      * Returns the JSON value as an {@link Integer} object. This may involve
397      * rounding or truncation. If the JSON value is {@code null}, this method
398      * returns {@code null}.
399      *
400      * @return the integer value.
401      * @throws JsonValueException
402      *             if the JSON value is not a number.
403      */
404     public Integer asInteger() {
405         return (object == null ? null : Integer.valueOf(asNumber().intValue()));
406     }
407 
408     /**
409      * Returns the JSON value as a {@link Collection} object. If the JSON value is
410      * {@code null}, this method returns {@code null}.
411      *
412      * @return the collection value, or {@code null} if no value.
413      * @throws JsonValueException
414      *             if the JSON value is not a {@code Collection}.
415      */
416     public Collection<Object> asCollection() {
417         return asCollection(Object.class);
418     }
419 
420     /**
421      * Returns the JSON value as a {@link List} object. If the JSON value is
422      * {@code null}, this method returns {@code null}.
423      * The returned {@link List} is <b>not</b> a copy : any interaction with it
424      * will affect the {@link JsonValue}.
425      *
426      * @return the list value, or {@code null} if no value.
427      * @throws JsonValueException
428      *             if the JSON value is not a {@code List}.
429      */
430     public List<Object> asList() {
431         return asList(Object.class);
432     }
433 
434     /**
435      * Returns the JSON value as a {@link Collection} containing objects of the
436      * specified type. If the value is {@code null}, this method returns
437      * {@code null}. If any of the elements of the collection are not {@code null} and
438      * not of the specified type, {@code JsonValueException} is thrown.
439      * The returned {@link Collection} is <b>not</b> a copy : any interaction with it
440      * will affect the {@link JsonValue}.
441      *
442      * @param <E>
443      *            the type of elements in this collection
444      * @param type
445      *            the type of object that all elements are expected to be.
446      * @return the collection value, or {@code null} if no value.
447      * @throws JsonValueException
448      *             if the JSON value is not a {@code Collection} or contains an
449      *             unexpected type.
450      * @throws NullPointerException
451      *             if {@code type} is {@code null}.
452      */
453     @SuppressWarnings("unchecked")
454     public <E> Collection<E> asCollection(final Class<E> type) {
455         if (object != null) {
456             expect(Collection.class);
457             if (type != Object.class) {
458                 final Collection<Object> coll = (Collection<Object>) this.object;
459                 for (final Object element : coll) {
460                     if (element != null && !type.isInstance(element)) {
461                         throw new JsonValueException(this, "Expecting a Collection of " + type.getName()
462                                 + " elements");
463                     }
464                 }
465             }
466         }
467         return (Collection<E>) object;
468     }
469 
470     /**
471      * Returns the JSON value as a {@link List} containing objects of the
472      * specified type. If the value is {@code null}, this method returns
473      * {@code null}. If any of the elements of the list are not {@code null} and
474      * not of the specified type, {@code JsonValueException} is thrown.
475      * The returned {@link List} is <b>not</b> a copy : any interaction with it
476      * will affect the {@link JsonValue}.
477      *
478      * @param <E>
479      *            the type of elements in this list
480      * @param type
481      *            the type of object that all elements are expected to be.
482      * @return the list value, or {@code null} if no value.
483      * @throws JsonValueException
484      *             if the JSON value is not a {@code List} or contains an unexpected type.
485      * @throws NullPointerException
486      *             if {@code type} is {@code null}.
487      */
488     @SuppressWarnings("unchecked")
489     public <E> List<E> asList(final Class<E> type) {
490         if (object != null) {
491             expect(List.class);
492             if (type != Object.class) {
493                 final List<Object> list = (List<Object>) this.object;
494                 for (final Object element : list) {
495                     if (element != null && !type.isInstance(element)) {
496                         throw new JsonValueException(this, "Expecting a List of " + type.getName()
497                                 + " elements");
498                     }
499                 }
500             }
501         }
502         return (List<E>) object;
503     }
504 
505     /**
506      * Returns the JSON value as an object whose type
507      * (and value) is specified by a transformation function. It is up to to the
508      * transformation function to transform/enforce source types of the elements
509      * in the Json source element and to decide what to do depending on the kind
510      * of {@link JsonValue} : if it is null, a {@link String}, a {@link List},
511      * or {@link Map}. If the type-transformation cannot occur,
512      * the exception specified by the transformation function is thrown.
513      *
514      * @param <V>
515      *            the type of element
516      * @param <E>
517      *            the type of exception thrown by the transformation function
518      * @param transformFunction
519      *            a {@link Function} to transform the JsonValue element to the desired type
520      * @return the value, or {@code null} if no value.
521      * @throws E
522      *             if the JsonValue element cannot be transformed
523      * @throws NullPointerException
524      *             if {@code transformFunction} is {@code null}.
525      */
526     public <V, E extends Exception> V as(final Function<JsonValue, V, E> transformFunction) throws E {
527         return transformFunction.apply(this);
528     }
529 
530     /**
531      * Returns the JSON value as a {@link Long} object. This may involve
532      * rounding or truncation. If the JSON value is {@code null}, this method
533      * returns {@code null}.
534      *
535      * @return the long integer value.
536      * @throws JsonValueException
537      *             if the JSON value is not a number.
538      */
539     public Long asLong() {
540         return (object == null ? null : Long.valueOf(asNumber().longValue()));
541     }
542 
543     /**
544      * Returns the JSON value as a {@code Map} object. If the JSON value is
545      * {@code null}, this method returns {@code null}.
546      *
547      * @return the map value, or {@code null} if no value.
548      * @throws JsonValueException
549      *             if the JSON value is not a {@code Map}.
550      */
551     @SuppressWarnings("unchecked")
552     public Map<String, Object> asMap() {
553         return (object == null ? null : (Map<String, Object>) (expect(Map.class).object));
554     }
555 
556     /**
557      * Returns the JSON value as a {@link Map} containing objects of the
558      * specified type. If the value is {@code null}, this method returns
559      * {@code null}. If any of the values of the map are not {@code null} and
560      * not of the specified type, {@code JsonValueException} is thrown.
561      *
562      * @param <V>
563      *            the type of values in this map
564      * @param type
565      *            the type of object that all values are expected to be.
566      * @return the map value, or {@code null} if no value.
567      * @throws JsonValueException
568      *             if the JSON value is not a {@code Map} or contains an
569      *             unexpected type.
570      * @throws NullPointerException
571      *             if {@code type} is {@code null}.
572      */
573     @SuppressWarnings("unchecked")
574     public <V> Map<String, V> asMap(final Class<V> type) {
575         if (object != null) {
576             expect(Map.class);
577             if (type != Object.class) {
578                 final Map<String, Object> map = (Map<String, Object>) this.object;
579                 for (final Object element : map.values()) {
580                     if (element != null && !type.isInstance(element)) {
581                         throw new JsonValueException(this, "Expecting a Map of " + type.getName()
582                                 + " elements");
583                     }
584                 }
585             }
586         }
587         return (Map<String, V>) object;
588     }
589     /**
590      * Returns the JSON value as a {@link Map} containing a collection of
591      * objects of the specified type. If the value is {@code null}, this method
592      * returns {@code null}. If any of the values of the map are not {@code null} and
593      * not of the specified type, {@code JsonValueException} is thrown.
594      *
595      * @param <E>
596      *            the type of elements in the collection
597      * @param elementType
598      *            the type of object that all collection elements are
599      *            expected to be.
600      * @return the map value, or {@code null} if no value.
601      * @throws JsonValueException
602      *             if the JSON value is not a {@code Map} or contains an
603      *             unexpected type.
604      * @throws NullPointerException
605      *             if {@code type} is {@code null}.
606      */
607     @SuppressWarnings("unchecked")
608     public <E> Map<String, List<E>> asMapOfList(final Class<E> elementType) {
609         if (object != null) {
610             expect(Map.class);
611             if (elementType != Object.class) {
612                 final Map<String, Object> map = (Map<String, Object>) this.object;
613                 for (final Object value : map.values()) {
614                     if (value != null && !(value instanceof List)) {
615                         throw new JsonValueException(this, "Expecting a Map of List values");
616                     }
617                     final List<?> list = (List<?>) value;
618                     for (final Object element : list) {
619                         if (element != null && !elementType.isInstance(element)) {
620                             throw new JsonValueException(this, "Expecting a Map of Lists with "
621                                     + elementType.getName() + " elements");
622                         }
623                     }
624                 }
625             }
626         }
627         return (Map<String, List<E>>) object;
628     }
629 
630     /**
631      * Returns the JSON value as a {@code Number} object. If the JSON value is
632      * {@code null}, this method returns {@code null}.
633      *
634      * @return the numeric value.
635      * @throws JsonValueException
636      *             if the JSON value is not a number.
637      */
638     public Number asNumber() {
639         return (object == null ? null : (Number) (expect(Number.class).object));
640     }
641 
642     /**
643      * Returns the JSON value as a {@code String} object. If the JSON value is
644      * {@code null}, this method returns {@code null}.
645      *
646      * @return the string value.
647      * @throws JsonValueException
648      *             if the JSON value is not a string.
649      */
650     public String asString() {
651         return (object == null ? null : (String) (expect(String.class).object));
652     }
653 
654     /**
655      * Removes all child values from this JSON value, if it has any.
656      */
657     public void clear() {
658         if (isMap()) {
659             asMap().clear();
660         } else if (isCollection()) {
661             asCollection().clear();
662         }
663     }
664 
665     /**
666      * Returns a shallow copy of this JSON value. If this JSON value contains a
667      * {@code Map} or a {@code List} object, the returned JSON
668      * value will contain a shallow copy of the original contained object.
669      * <p>
670      * The new value's members can be modified without affecting the original
671      * value. Modifying the member's members will almost certainly affect the
672      * original value. To avoid this, use the {@link #copy} method to return a
673      * deep copy of the JSON value.
674      * <p>
675      * This method does not traverse the value's members, nor will it apply any
676      * transformations.
677      *
678      * @return a shallow copy of this JSON value.
679      */
680     @Override
681     public JsonValue clone() {
682         final JsonValue result = new JsonValue(this.object, this.pointer);
683         if (isMap()) {
684             result.object = new LinkedHashMap<>(this.asMap());
685         } else if (isList()) {
686             result.object = new ArrayList<>(this.asList());
687         }
688         return result;
689     }
690 
691     /**
692      * Returns {@code true} this JSON value contains an item with the specified
693      * value.
694      *
695      * @param object
696      *            the object to seek within this JSON value.
697      * @return {@code true} if this value contains the specified member value.
698      */
699     public boolean contains(final Object object) {
700         boolean result = false;
701         if (isMap()) {
702             result = asMap().containsValue(object);
703         } else if (isCollection()) {
704             result = asCollection().contains(object);
705         }
706         return result;
707     }
708 
709     /**
710      * Returns a deep copy of this JSON value.
711      * <p>
712      * Note: This method is recursive, and currently has no ability to detect or
713      * correct for structures containing cyclic references. Processing such a
714      * structure will result in a {@link StackOverflowError} being thrown.
715      *
716      * @return a deep copy of this JSON value.
717      */
718     public JsonValue copy() {
719         // TODO: track original values to resolve cyclic references
720         final JsonValue result = new JsonValue(object, pointer); // start with shallow copy
721         if (this.isMap()) {
722             final Map<String, Object> map = object(size());
723             for (final String key : keys()) {
724                 map.put(key, this.get(key).copy().getObject()); // recursion
725             }
726             result.object = map;
727         } else if (isList()) {
728             final ArrayList<Object> list = new ArrayList<>(size());
729             for (final JsonValue element : this) {
730                 list.add(element.copy().getObject()); // recursion
731             }
732             result.object = list;
733         }
734         return result;
735     }
736 
737     /**
738      * Defaults the JSON value to the specified value if it is currently
739      * {@code null}.
740      *
741      * @param object
742      *            the object to default to.
743      * @return this JSON value or a new JSON value containing the default value.
744      */
745     public JsonValue defaultTo(final Object object) {
746         return (this.object != null ? this : new JsonValue(object, this.pointer));
747     }
748 
749     /**
750      * Called to enforce that the JSON value is of a particular type. A value of
751      * {@code null} is allowed.
752      *
753      * @param type
754      *            the class that the underlying value must have.
755      * @return this JSON value.
756      * @throws JsonValueException
757      *             if the value is not the specified type.
758      */
759     public JsonValue expect(final Class<?> type) {
760         if (object != null && !type.isInstance(object)) {
761             throw new JsonValueException(this, "Expecting a " + type.getName());
762         }
763         return this;
764     }
765 
766     /**
767      * Returns the specified child value. If this JSON value is not a
768      * {@link List} or if no such child exists, then a JSON value containing a
769      * {@code null} is returned.
770      *
771      * @param index
772      *            index of child element value to return.
773      * @return the child value, or a JSON value containing {@code null}.
774      * @throws JsonValueException
775      *             if index is negative.
776      */
777     public JsonValue get(final int index) {
778         Object result = null;
779         if (index < 0) {
780             throw new JsonValueException(this, "List index out of range: " + index);
781         }
782         if (isList() && index >= 0) {
783             final List<Object> list = asList();
784             if (index < list.size()) {
785                 result = list.get(index);
786             }
787         }
788         return new JsonValue(result, pointer.child(index));
789     }
790 
791     /**
792      * Returns the specified child value with a pointer, relative to this value
793      * as root. If the specified child value does not exist, then {@code null}
794      * is returned.
795      *
796      * @param pointer
797      *            the JSON pointer identifying the child value to return.
798      * @return the child value, or {@code null} if no such value exists.
799      */
800     public JsonValue get(final JsonPointer pointer) {
801         JsonValue result = this;
802         for (final String token : pointer) {
803             final JsonValue member = result.get(token);
804             if (member.isNull() && !result.isDefined(token)) {
805                 return null; // undefined value yields null, not a JSON value containing null
806             }
807             result = member;
808         }
809         return result;
810     }
811 
812     /**
813      * Returns the specified item value. If no such member value exists, then a
814      * JSON value containing {@code null} is returned.
815      *
816      * @param key
817      *            the {@code Map} key or {@code List} index identifying the item
818      *            to return.
819      * @return a JSON value containing the value or {@code null}.
820      */
821     public JsonValue get(final String key) {
822         Object result = null;
823         if (isMap()) {
824             result = asMap().get(key);
825         } else if (isList()) {
826             final List<Object> list = asList();
827             final int index = toIndex(key);
828             if (index >= 0 && index < list.size()) {
829                 result = list.get(index);
830             }
831         }
832         return new JsonValue(result, pointer.child(key));
833     }
834 
835     /**
836      * Returns the raw Java object representing this JSON value.
837      *
838      * @return the raw Java object representing this JSON value.
839      */
840     public Object getObject() {
841         return object;
842     }
843 
844     /**
845      * Returns the pointer of the JSON value in its JSON structure.
846      *
847      * @return the pointer of the JSON value in its JSON structure.
848      */
849     public JsonPointer getPointer() {
850         return pointer;
851     }
852 
853     /**
854      * Returns {@code true} if the JSON value is a {@link Boolean}.
855      *
856      * @return {@code true} if the JSON value is a {@link Boolean}.
857      */
858     public boolean isBoolean() {
859         return (object != null && object instanceof Boolean);
860     }
861 
862     /**
863      * Returns {@code true} if this JSON value contains the specified item.
864      *
865      * @param key
866      *            the {@code Map} key or {@code List} index of the item to seek.
867      * @return {@code true} if this JSON value contains the specified member.
868      * @throws NullPointerException
869      *             if {@code key} is {@code null}.
870      */
871     public boolean isDefined(final String key) {
872         boolean result = false;
873         if (isMap()) {
874             result = asMap().containsKey(key);
875         } else if (isList()) {
876             final int index = toIndex(key);
877             result = (index >= 0 && index < asList().size());
878         }
879         return result;
880     }
881 
882     /**
883      * Returns {@code true} if the JSON value is a {@link Collection}.
884      *
885      * @return {@code true} if the JSON value is a {@link Collection}.
886      */
887     public boolean isCollection() {
888         return (object instanceof Collection);
889     }
890 
891     /**
892      * Returns {@code true} if the JSON value is a {@link List}.
893      *
894      * @return {@code true} if the JSON value is a {@link List}.
895      */
896     public boolean isList() {
897         return (object instanceof List);
898     }
899 
900     /**
901      * Returns {@code true} if the JSON value is a {@link Map}.
902      *
903      * @return {@code true} if the JSON value is a {@link Map}.
904      */
905     public boolean isMap() {
906         return (object instanceof Map);
907     }
908 
909     /**
910      * Returns {@code true} if the value is {@code null}.
911      *
912      * @return {@code true} if the value is {@code null}.
913      */
914     public boolean isNull() {
915         return (object == null);
916     }
917 
918     /**
919      * Returns {@code true} if the value is not {@code null}.
920      *
921      * @return {@code true} if the value is not {@code null}.
922      */
923     public boolean isNotNull() {
924         return !isNull();
925     }
926 
927     /**
928      * Returns {@code true} if the JSON value is a {@link Number}.
929      *
930      * @return {@code true} if the JSON value is a {@link Number}.
931      */
932     public boolean isNumber() {
933         return (object != null && object instanceof Number);
934     }
935 
936     /**
937      * Returns {@code true} if the JSON value is a {@link String}.
938      *
939      * @return {@code true} if the JSON value is a {@link String}.
940      */
941     public boolean isString() {
942         return (object != null && object instanceof String);
943     }
944 
945     /**
946      * Returns an iterator over the child values that this JSON value contains.
947      * If this value is a {@link Map}, then the order of the
948      * resulting child values is undefined. Calling the {@link Iterator#remove()}
949      * method of the returned iterator will throw a {@link UnsupportedOperationException}.
950      *
951      * @return an iterator over the child values that this JSON value contains.
952      */
953     @Override
954     public Iterator<JsonValue> iterator() {
955         if (isList()) { // optimize for list
956             return new Iterator<JsonValue>() {
957                 int cursor = 0;
958                 Iterator<Object> i = asList().iterator();
959 
960                 @Override
961                 public boolean hasNext() {
962                     return i.hasNext();
963                 }
964 
965                 @Override
966                 public JsonValue next() {
967                     final Object element = i.next();
968                     return new JsonValue(element, pointer.child(cursor++));
969                 }
970 
971                 @Override
972                 public void remove() {
973                     throw new UnsupportedOperationException();
974                 }
975             };
976         } else {
977             return new Iterator<JsonValue>() {
978                 Iterator<String> i = keys().iterator();
979 
980                 @Override
981                 public boolean hasNext() {
982                     return i.hasNext();
983                 }
984 
985                 @Override
986                 public JsonValue next() {
987                     return get(i.next());
988                 }
989 
990                 @Override
991                 public void remove() {
992                     throw new UnsupportedOperationException();
993                 }
994             };
995         }
996     }
997 
998     /**
999      * Returns the set of keys for this JSON value's child values. If this value
1000      * is a {@code Map}, then the order of the resulting keys is the same as the
1001      * underlying Map implementation. If there are no child values, this method
1002      * returns an empty set.
1003      *
1004      * @return the set of keys for this JSON value's child values.
1005      */
1006     public Set<String> keys() {
1007         if (isMap()) {
1008             return asMap().keySet();
1009         } else if (isList()) {
1010             return new AbstractSet<String>() {
1011                 final RangeSet range = new RangeSet(JsonValue.this.size()); // 0 through size-1 inclusive
1012 
1013                 @Override
1014                 public boolean contains(final Object o) {
1015                     boolean result = false;
1016                     if (o instanceof String) {
1017                         try {
1018                             result = range.contains(Integer.valueOf((String) o));
1019                         } catch (final NumberFormatException nfe) {
1020                             // ignore; yields false
1021                         }
1022                     }
1023                     return result;
1024                 }
1025 
1026                 @Override
1027                 public Iterator<String> iterator() {
1028                     return new Iterator<String>() {
1029                         Iterator<Integer> i = range.iterator();
1030 
1031                         @Override
1032                         public boolean hasNext() {
1033                             return i.hasNext();
1034                         }
1035 
1036                         @Override
1037                         public String next() {
1038                             return i.next().toString();
1039                         }
1040 
1041                         @Override
1042                         public void remove() {
1043                             throw new UnsupportedOperationException();
1044                         }
1045                     };
1046                 }
1047 
1048                 @Override
1049                 public int size() {
1050                     return range.size();
1051                 }
1052             };
1053         } else {
1054             return Collections.emptySet();
1055         }
1056     }
1057 
1058     /**
1059      * Sets the value of the specified child list element.
1060      *
1061      * @param index
1062      *            the {@code List} index identifying the child value to set.
1063      * @param object
1064      *            the Java value to assign to the list element.
1065      * @return this JSON value.
1066      * @throws JsonValueException
1067      *             if this JSON value is not a {@code List} or index is out of
1068      *             range.
1069      */
1070     public JsonValue put(final int index, final Object object) {
1071         final List<Object> list = required().asList();
1072         if (index < 0 || index > list.size()) {
1073             throw new JsonValueException(this, "List index out of range: " + index);
1074         } else if (index == list.size()) { // appending to end of list
1075             list.add(unwrap(object));
1076         } else { // replacing existing element
1077             list.set(index, unwrap(object));
1078         }
1079         return this;
1080     }
1081 
1082     /**
1083      * Sets the value identified by the specified pointer, relative to this
1084      * value as root. If doing so would require the creation of a new object or
1085      * list, a {@code JsonValueException} will be thrown.
1086      * <p>
1087      * NOTE: values may be added to a list using the reserved JSON pointer token
1088      * "-". For example, the pointer "/a/b/-" will add a new element to the list
1089      * referenced by "/a/b".
1090      *
1091      * @param pointer
1092      *            identifies the child value to set.
1093      * @param object
1094      *            the Java object value to set.
1095      * @return this JSON value.
1096      * @throws JsonValueException
1097      *             if the specified pointer is invalid.
1098      */
1099     public JsonValue put(final JsonPointer pointer, final Object object) {
1100         navigateToParentOf(pointer).required().putToken(pointer.leaf(), object);
1101         return this;
1102     }
1103 
1104     /**
1105      * Sets the value of the specified member.  
1106      * <p>
1107      * If setting a list element, the specified key must be parseable as an
1108      * unsigned base-10 integer and be less than or equal to the size of the
1109      * list.
1110      *
1111      * @param key
1112      *            the {@code Map} key or {@code List} index identifying the
1113      *            child value to set.
1114      * @param object
1115      *            the object value to assign to the member.
1116      * @return this JSON value.
1117      * @throws JsonValueException
1118      *             if this JSON value is not a {@code Map} or {@code List}.
1119      * @throws NullPointerException
1120      *             if {@code key} is {@code null}.
1121      */
1122     public JsonValue put(final String key, final Object object) {
1123         if (key == null) {
1124             throw new NullPointerException();
1125         } else if (isMap()) {
1126             asMap().put(key, unwrap(object));
1127         } else if (isList()) {
1128             put(toIndex(key), object);
1129         } else {
1130             throw new JsonValueException(this, "Expecting a Map or List");
1131         }
1132         return this;
1133     }
1134 
1135     /**
1136      * Sets the value identified by the specified pointer, relative to this
1137      * value as root. Missing parent objects or lists will be created on demand.
1138      * <p>
1139      * NOTE: values may be added to a list using the reserved JSON pointer token
1140      * "-". For example, the pointer "/a/b/-" will add a new element to the list
1141      * referenced by "/a/b".
1142      *
1143      * @param pointer
1144      *            identifies the child value to set.
1145      * @param object
1146      *            the Java object value to set.
1147      * @return this JSON value.
1148      * @throws JsonValueException
1149      *             if the specified pointer is invalid.
1150      */
1151     public JsonValue putPermissive(final JsonPointer pointer, final Object object) {
1152         navigateToParentOfPermissive(pointer).putToken(pointer.leaf(), object);
1153         return this;
1154     }
1155 
1156     /**
1157      * Removes the specified child value, shifting any subsequent elements to
1158      * the left. If the JSON value is not a {@code List}, calling this method
1159      * has no effect.
1160      *
1161      * @param index
1162      *            the {@code List} index identifying the child value to remove.
1163      */
1164     public void remove(final int index) {
1165         if (index >= 0 && isList()) {
1166             final List<Object> list = asList();
1167             if (index < list.size()) {
1168                 list.remove(index);
1169             }
1170         }
1171     }
1172 
1173     /**
1174      * Removes the specified child value with a pointer, relative to this value
1175      * as root. If the specified child value is not defined, calling this method
1176      * has no effect.
1177      *
1178      * @param pointer
1179      *            the JSON pointer identifying the child value to remove.
1180      */
1181     public void remove(final JsonPointer pointer) {
1182         navigateToParentOf(pointer).remove(pointer.leaf());
1183     }
1184 
1185     /**
1186      * Removes the specified child value. If the specified child value is not
1187      * defined, calling this method has no effect.
1188      *
1189      * @param key
1190      *            the {@code Map} key or {@code List} index identifying the
1191      *            child value to remove.
1192      */
1193     public void remove(final String key) {
1194         if (isMap()) {
1195             asMap().remove(key);
1196         } else if (isList()) {
1197             remove(toIndex(key));
1198         }
1199     }
1200 
1201     /**
1202      * Throws a {@code JsonValueException} if the JSON value is {@code null}.
1203      *
1204      * @return this JSON value.
1205      * @throws JsonValueException
1206      *             if the JSON value is {@code null}.
1207      */
1208     public JsonValue required() {
1209         if (object == null) {
1210             throw new JsonValueException(this, "Expecting a value");
1211         }
1212         return this;
1213     }
1214 
1215     /**
1216      * Sets the Java object representing this JSON value.
1217      * <p>
1218      * This method will automatically unwrap {@link JsonValue} objects.
1219      *
1220      * @param object
1221      *            the object to set.
1222      */
1223     public void setObject(final Object object) {
1224         this.object = object;
1225         if (object instanceof JsonValue) {
1226             final JsonValue jv = (JsonValue) object;
1227             this.object = jv.object;
1228         }
1229     }
1230 
1231     /**
1232      * Returns the number of values that this JSON value contains.
1233      *
1234      * @return the number of values that this JSON value contains.
1235      */
1236     public int size() {
1237         if (isMap()) {
1238             return asMap().size();
1239         } else if (isCollection()) {
1240             return asCollection().size();
1241         } else {
1242             return 0;
1243         }
1244     }
1245 
1246     /**
1247      * Returns a string representation of the JSON value. The result
1248      * resembles—but is not guaranteed to conform to—JSON syntax. This method
1249      * does not apply transformations to the value's children.
1250      *
1251      * @return a string representation of the JSON value.
1252      */
1253     @SuppressWarnings("unchecked")
1254     @Override
1255     public String toString() {
1256         final StringBuilder sb = new StringBuilder();
1257         if (isNull()) {
1258             sb.append("null");
1259         } else if (isMap()) {
1260             sb.append("{ ");
1261             final Map<Object, Object> map = (Map<Object, Object>) object;
1262             for (final Iterator<Object> i = map.keySet().iterator(); i.hasNext();) {
1263                 final Object key = i.next();
1264                 sb.append('"');
1265                 appendEscapedString(sb, key.toString());
1266                 sb.append("\": ");
1267                 sb.append(new JsonValue(map.get(key)).toString()); // recursion
1268                 if (i.hasNext()) {
1269                     sb.append(", ");
1270                 }
1271             }
1272             sb.append(" }");
1273         } else if (isCollection()) {
1274             sb.append("[ ");
1275             for (final Iterator<Object> i = ((Collection<Object>) object).iterator(); i.hasNext();) {
1276                 sb.append(new JsonValue(i.next()).toString()); // recursion
1277                 if (i.hasNext()) {
1278                     sb.append(", ");
1279                 }
1280             }
1281             sb.append(" ]");
1282         } else if (isString()) {
1283             sb.append('"');
1284             appendEscapedString(sb, object.toString());
1285             sb.append('"');
1286         } else {
1287             sb.append(object.toString());
1288         }
1289         return sb.toString();
1290     }
1291 
1292     /**
1293      * As per json.org a string is any Unicode character except " or \ or
1294      * control characters. Special characters will be escaped using a \ as
1295      * follows:
1296      * <ul>
1297      * <li> {@literal \ "} - double quote
1298      * <li> {@literal \ \} - back slash
1299      * <li> {@literal \ b} - backspace
1300      * <li> {@literal \ f} - form feed
1301      * <li> {@literal \ n} - new line
1302      * <li> {@literal \ r} - carriage return
1303      * <li> {@literal \ t} - tab
1304      * <li> {@literal \ u xxxx} - other control characters.
1305      * </ul>
1306      */
1307     private static void appendEscapedString(final StringBuilder sb, final String s) {
1308         final int size = s.length();
1309         for (int i = 0; i < size; i++) {
1310             final char c = s.charAt(i);
1311             switch (c) {
1312             // Escape characters which must be escaped.
1313             case '"':
1314                 sb.append("\\\"");
1315                 break;
1316             case '\\':
1317                 sb.append("\\\\");
1318                 break;
1319             // Escape common controls to the C equivalent to make them easier to read.
1320             case '\b':
1321                 sb.append("\\b");
1322                 break;
1323             case '\f':
1324                 sb.append("\\f");
1325                 break;
1326             case '\n':
1327                 sb.append("\\n");
1328                 break;
1329             case '\r':
1330                 sb.append("\\r");
1331                 break;
1332             case '\t':
1333                 sb.append("\\t");
1334                 break;
1335             default:
1336                 if (Character.isISOControl(c)) {
1337                     final String hex = Integer.toHexString(c).toUpperCase(Locale.ENGLISH);
1338                     final int hexPadding = 4 - hex.length();
1339                     sb.append("\\u");
1340                     for (int j = 0; j < hexPadding; j++) {
1341                         sb.append('0');
1342                     }
1343                     sb.append(hex);
1344                 } else {
1345                     sb.append(c);
1346                 }
1347             }
1348         }
1349     }
1350 
1351     private void addToken(final String token, final Object object) {
1352         if (isEndOfListToken(token) && isList()) {
1353             add(object);
1354         } else {
1355             add(token, object);
1356         }
1357     }
1358 
1359     private boolean isEndOfListToken(final String token) {
1360         return token.equals("-");
1361     }
1362 
1363     private boolean isIndexToken(final String token) {
1364         if (token.isEmpty()) {
1365             return false;
1366         } else {
1367             for (int i = 0; i < token.length(); i++) {
1368                 final char c = token.charAt(i);
1369                 if (!Character.isDigit(c)) {
1370                     return false;
1371                 }
1372             }
1373             return true;
1374         }
1375     }
1376 
1377     private JsonValue navigateToParentOf(final JsonPointer pointer) {
1378         JsonValue jv = this;
1379         final int size = pointer.size();
1380         for (int n = 0; n < size - 1; n++) {
1381             jv = jv.get(pointer.get(n));
1382             if (jv.isNull()) {
1383                 break;
1384             }
1385         }
1386         return jv;
1387     }
1388 
1389     private JsonValue navigateToParentOfPermissive(final JsonPointer pointer) {
1390         JsonValue jv = this;
1391         final int size = pointer.size();
1392         for (int n = 0; n < size - 1; n++) {
1393             final String token = pointer.get(n);
1394             final JsonValue next = jv.get(token);
1395             if (next.isNotNull()) {
1396                 jv = next;
1397             } else if (isIndexToken(token)) {
1398                 throw new JsonValueException(this, "Expecting a value");
1399             } else {
1400                 // Create the field based on the type of the next token.
1401                 final String nextToken = pointer.get(n + 1);
1402                 if (isEndOfListToken(nextToken)) {
1403                     jv.add(token, new ArrayList<>());
1404                     jv = jv.get(token);
1405                 } else if (isIndexToken(nextToken)) {
1406                     throw new JsonValueException(this, "Expecting a value");
1407                 } else {
1408                     jv.putPermissive(new JsonPointer(token), new LinkedHashMap<>());
1409                     jv = jv.get(token);
1410                 }
1411             }
1412         }
1413         return jv;
1414     }
1415 
1416     private void putToken(final String token, final Object object) {
1417         if (isEndOfListToken(token) && isList()) {
1418             add(object);
1419         } else {
1420             put(token, object);
1421         }
1422     }
1423 
1424     /**
1425      * Performs a deep comparison of this JSON value with another JSON value, and returns whether the two objects
1426      * are identical.  Fails fast in that a {@code false} is returned as soon as a difference is detected.
1427      * <p>
1428      *     <b>Note:</b> Only values recognisable as JSON primitives ({@link Map}, {@link List}, {@link Number},
1429      *     {@link Boolean}, {@link String} and {@code null}) are supported.
1430      * </p>
1431      * @param other another value.
1432      * @return whether the two objects are equal.
1433      * @throws NullPointerException if {@code other} is {@code null}.
1434      * @throws IllegalArgumentException if this or the {@code other} value contains non-JSON primitive values.
1435      */
1436     public boolean isEqualTo(JsonValue other) {
1437         return JsonPatch.isEqual(this, other);
1438     }
1439 
1440     /**
1441      * Performs a deep comparison of this JSON vlaue with another JSON value, and produces a
1442      * JSON Patch value, which contains the operations necessary to modify the current value
1443      * to arrive at the {@code target} value.
1444      *
1445      * @param target the intended target value.
1446      * @return the resulting JSON Patch value.
1447      * @throws NullPointerException if either of {@code original} or {@code target} are {@code null}.
1448      */
1449     public JsonValue diff(JsonValue target) {
1450         return JsonPatch.diff(this, target);
1451     }
1452 
1453     /**
1454      * Applies a set of modifications in a JSON patch value to the current object, resulting
1455      * in the intended target value. In the event of a failure, this method does not revert
1456      * any modifications applied up to the point of failure.
1457      *
1458      * @param patch the JSON Patch value, specifying the modifications to apply to the original value.
1459      * @throws JsonValueException if application of the patch failed.
1460      */
1461     public void patch(JsonValue patch) {
1462         JsonPatch.patch(this, patch);
1463     }
1464 }