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("uid", 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("uid", "bjensen"), field("age", 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("uid", "bjensen"),
114 * field("roles", array("sales", "marketing"))));
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("uid", "bjensen"), field("age", 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<String, Object> 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 }