001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyrighted [year] [name of copyright owner]".
013 *
014 * Copyright © 2010–2011 ApexIdentity Inc. All rights reserved.
015 * Portions Copyrighted 2011-2015 ForgeRock AS.
016 */
017
018package org.forgerock.json;
019
020import java.io.File;
021import java.net.MalformedURLException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URL;
025import java.nio.charset.Charset;
026import java.nio.charset.IllegalCharsetNameException;
027import java.nio.charset.UnsupportedCharsetException;
028import java.util.AbstractMap;
029import java.util.AbstractSet;
030import java.util.ArrayList;
031import java.util.Arrays;
032import java.util.Collection;
033import java.util.Collections;
034import java.util.Iterator;
035import java.util.LinkedHashMap;
036import java.util.LinkedHashSet;
037import java.util.List;
038import java.util.Locale;
039import java.util.Map;
040import java.util.Set;
041import java.util.UUID;
042import java.util.regex.Pattern;
043import java.util.regex.PatternSyntaxException;
044
045import org.forgerock.util.Function;
046import org.forgerock.util.RangeSet;
047import org.forgerock.util.Utils;
048
049/**
050 * Represents a value in a JSON object model structure. JSON values are
051 * represented with standard Java objects: {@link String}, {@link Number},
052 * {@link Map}, {@link List}, {@link Boolean} and {@code null}.
053 * <p>
054 * A JSON value may have one or more transformers associated with it.
055 * Transformers apply transformations to the JSON value upon construction, and
056 * upon members as they are retrieved. Transformers are applied iteratively, in
057 * the sequence they appear within the list. If a transformer affects the value,
058 * then all transformers are re-applied, in sequence. This repeats until the
059 * value is no longer affected. Transformers are inherited by and applied to
060 * member values.
061 */
062public class JsonValue implements Cloneable, Iterable<JsonValue> {
063
064    /**
065     * Returns a mutable JSON array containing the provided objects. This method
066     * is provided as a convenience method for constructing JSON arrays. Example
067     * usage:
068     *
069     * <pre>
070     * JsonValue value = json(array(1, 2, 3));
071     * </pre>
072     *
073     * @param objects
074     *            The array elements.
075     * @return A JSON array.
076     */
077    public static List<Object> array(final Object... objects) {
078        return new ArrayList<>(Arrays.asList(objects));
079    }
080
081    /**
082     * Returns a mutable JSON set containing the provided objects. This method
083     * is provided as a convenience method for constructing JSON set. Example
084     * usage:
085     *
086     * <pre>
087     * JsonValue value = json(set(1, 2, 3));
088     * </pre>
089     *
090     * @param objects
091     *            The set elements.
092     * @return A JSON set.
093     */
094    public static Set<Object> set(final Object... objects) {
095        return new LinkedHashSet<>(Arrays.asList(objects));
096    }
097
098    /**
099     * Returns a JSON field for inclusion in a JSON object using
100     * {@link #object(java.util.Map.Entry...) object} only if its value is not {@code null}.
101     * Example usage:
102     *
103     * <pre>
104     * JsonValue value = json(object(fieldIfNotNull(&quot;uid&quot;, getUid()));
105     * </pre>
106     * <p>
107     * Note: This feature depends on the {@link #object(java.util.Map.Entry...)} method that
108     * checks if the entry is not {@code null} before including it into the map.
109     * </p>
110     *
111     * @param key
112     *            The JSON field name.
113     * @param value
114     *            The JSON field value (may be {@code null}).
115     * @return The JSON field for inclusion in a JSON object or {@code null}.
116     * @see #object(java.util.Map.Entry...)
117     */
118    public static Map.Entry<String, Object> fieldIfNotNull(final String key, final Object value) {
119        return value != null ? field(key, value) : null;
120    }
121
122    /**
123     * Returns a JSON field for inclusion in a JSON object using
124     * {@link #object(java.util.Map.Entry...) object}. This method is provided
125     * as a convenience method for constructing JSON objects. Example usage:
126     *
127     * <pre>
128     * JsonValue value = json(object(field(&quot;uid&quot;, &quot;bjensen&quot;), field(&quot;age&quot;, 30)));
129     * </pre>
130     *
131     * @param key
132     *            The JSON field name.
133     * @param value
134     *            The JSON field value.
135     * @return The JSON field for inclusion in a JSON object.
136     */
137    public static Map.Entry<String, Object> field(final String key, final Object value) {
138        return new AbstractMap.SimpleImmutableEntry<>(key, value);
139    }
140
141    /**
142     * Returns a JSON value whose content is the provided object. This method is
143     * provided as a convenience method for constructing JSON objects, instead
144     * of using {@link #JsonValue(Object)}. Example usage:
145     *
146     * <pre>
147     * JsonValue value =
148     *         json(object(field(&quot;uid&quot;, &quot;bjensen&quot;),
149     *                     field(&quot;roles&quot;, array(&quot;sales&quot;, &quot;marketing&quot;))));
150     * </pre>
151     *
152     * @param object
153     *            the Java object representing the JSON value.
154     * @return The JSON value.
155     */
156    public static JsonValue json(final Object object) {
157        return object instanceof JsonValue ? (JsonValue) object : new JsonValue(object);
158    }
159
160    /**
161     * Returns a JSON object comprised of the provided JSON
162     * {@link #field(String, Object) fields}. This method is provided as a
163     * convenience method for constructing JSON objects. Example usage:
164     *
165     * <pre>
166     * JsonValue value = json(object(field(&quot;uid&quot;, &quot;bjensen&quot;), field(&quot;age&quot;, 30)));
167     * </pre>
168     *
169     * @param fields
170     *            The list of {@link #field(String, Object) fields} to include
171     *            in the JSON object. {@code null} elements are allowed, but are
172     *            not included in the returned map (this makes it easier to
173     *            include optional elements).
174     * @return The JSON object.
175     */
176    @SuppressWarnings({ "unchecked", "rawtypes" })
177    public static Object object(final Map.Entry... fields) {
178        final Map<String, Object> object = new LinkedHashMap<>(fields.length);
179        for (final Map.Entry<String, Object> field : fields) {
180            if (field != null) {
181                object.put(field.getKey(), field.getValue());
182            }
183        }
184        return object;
185    }
186
187    /**
188     * Returns {@code true} if the values are === equal.
189     */
190    private static boolean eq(final Object o1, final Object o2) {
191        return (o1 == o2 || (o1 != null && o1.equals(o2)));
192    }
193
194    /**
195     * Returns the key as an list index value. If the string does not represent
196     * a valid list index value, then {@code -1} is returned.
197     *
198     * @param key
199     *            the key to be converted into an list index value.
200     * @return the converted index value, or {@code -1} if invalid.
201     */
202    private static int toIndex(final String key) {
203        int result;
204        try {
205            result = Integer.parseInt(key);
206        } catch (final NumberFormatException nfe) {
207            result = -1;
208        }
209        return (result >= 0 ? result : -1);
210    }
211
212    /** The Java object representing this JSON value. */
213    private Object object;
214
215    /** The pointer to the value within a JSON structure. */
216    private JsonPointer pointer;
217
218    /** Transformers to apply to the value; are inherited by its members. */
219    private final ArrayList<JsonTransformer> transformers = new ArrayList<>(0);
220
221    /**
222     * Constructs a JSON value object with a given object. This constructor will
223     * automatically unwrap {@link JsonValue} objects.
224     *
225     * @param object
226     *            the Java object representing the JSON value.
227     */
228    public JsonValue(final Object object) {
229        this(object, null, null);
230    }
231
232    /**
233     * Constructs a JSON value object with a given object and transformers. This
234     * constructor will automatically unwrap {@link JsonValue} objects.
235     *
236     * @param object
237     *            the Java object representing the JSON value.
238     * @param transformers
239     *            a list of transformers to apply the value and its members.
240     * @throws JsonException
241     *             if a transformer failed during value initialization.
242     */
243    public JsonValue(final Object object, final Collection<? extends JsonTransformer> transformers) {
244        this(object, null, transformers);
245    }
246
247    /**
248     * Constructs a JSON value object with a given object and pointer. This
249     * constructor will automatically unwrap {@link JsonValue} objects.
250     *
251     * @param object
252     *            the Java object representing the JSON value.
253     * @param pointer
254     *            the pointer to the value in a JSON structure.
255     */
256    public JsonValue(final Object object, final JsonPointer pointer) {
257        this(object, pointer, null);
258    }
259
260    /**
261     * Constructs a JSON value object with given object, pointer and
262     * transformers.
263     *
264     * @param object
265     *            the Java object representing the JSON value.
266     * @param pointer
267     *            the pointer to the value in a JSON structure.
268     * @param transformers
269     *            a list of transformers to apply the value and its members.
270     * @throws JsonException
271     *             if a transformer failed during value initialization.
272     */
273    public JsonValue(final Object object, final JsonPointer pointer,
274            final Collection<? extends JsonTransformer> transformers) {
275        this.object = object;
276        this.pointer = pointer;
277        if (object instanceof JsonValue) {
278            final JsonValue jv = (JsonValue) object;
279            this.object = jv.object;
280            if (pointer == null) {
281                this.pointer = jv.pointer;
282            }
283            if (transformers == null) {
284                this.transformers.addAll(jv.transformers);
285            }
286        }
287        if (transformers != null) {
288            this.transformers.addAll(transformers);
289        }
290        if (this.pointer == null) {
291            this.pointer = new JsonPointer();
292        }
293        if (this.transformers.size() > 0) {
294            applyTransformers();
295        }
296    }
297
298    /**
299     * Adds the specified value to the list. Adding a value to a list shifts any
300     * existing elements at or above the specified index to the right by one.
301     *
302     * @param index
303     *            the {@code List} index of the value to add.
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} or index is out of
309     *             range.
310     */
311    public JsonValue add(final int index, final Object object) {
312        final List<Object> list = required().asList();
313        if (index < 0 || index > list.size()) {
314            throw new JsonValueException(this, "List index out of range: " + index);
315        }
316        list.add(index, object);
317        return this;
318    }
319
320    /**
321     * Adds the value identified by the specified pointer, relative to this
322     * value as root. If doing so would require the creation of a new object or
323     * list, a {@code JsonValueException} will be thrown.
324     * <p>
325     * NOTE: values may be added to a list using the reserved JSON pointer token
326     * "-". For example, the pointer "/a/b/-" will add a new element to the list
327     * referenced by "/a/b".
328     *
329     * @param pointer
330     *            identifies the child value to add.
331     * @param object
332     *            the Java object value to add.
333     * @return this JSON value.
334     * @throws JsonValueException
335     *             if the specified pointer is invalid.
336     */
337    public JsonValue add(final JsonPointer pointer, final Object object) {
338        navigateToParentOf(pointer).required().addToken(pointer.leaf(), object);
339        return this;
340    }
341
342    /**
343     * Adds the specified value to a set or to the end of the list. This is method is
344     * equivalent to the following code:
345     *
346     * <pre>
347     * add(size(), object);
348     * </pre>
349     *
350     * for lists, and
351     *
352     * <pre>
353     * asSet().add(object);
354     * </pre>
355     *
356     * for sets.
357     *
358     * @param object
359     *            the java object to add.
360     * @return this JSON value.
361     * @throws JsonValueException
362     *             if this JSON value is not a {@code Set} or a {@code List}.
363     */
364    public JsonValue add(final Object object) {
365        if (isList()) {
366            return add(size(), object);
367        } else if (isSet()) {
368            required().asSet().add(object);
369            return this;
370        } else {
371            throw new JsonValueException(this, "Expecting a Set or List");
372        }
373    }
374
375    /**
376     * Adds the specified value.
377     * <p>
378     * If adding to a list value, the specified key must be parseable as an
379     * unsigned base-10 integer and be less than or equal to the list size.
380     * Adding a value to a list shifts any existing elements at or above the
381     * specified index to the right by one.
382     *
383     * @param key
384     *            the {@code Map} key or {@code List} index to add.
385     * @param object
386     *            the Java object to add.
387     * @return this JSON value.
388     * @throws JsonValueException
389     *             if not a {@code Map} or {@code List}, the {@code Map} key
390     *             already exists, or the {@code List} index is out of range.
391     */
392    public JsonValue add(final String key, final Object object) {
393        if (isMap()) {
394            final Map<String, Object> map = asMap();
395            if (map.containsKey(key)) {
396                throw new JsonValueException(this, "Map key " + key + " already exists");
397            }
398            map.put(key, object);
399        } else if (isList()) {
400            add(toIndex(key), object);
401        } else {
402            throw new JsonValueException(this, "Expecting a Map or List");
403        }
404        return this;
405    }
406
407    /**
408     * Adds the value identified by the specified pointer, relative to this
409     * value as root. Missing parent objects or lists will be created on demand.
410     * <p>
411     * NOTE: values may be added to a list using the reserved JSON pointer token
412     * "-". For example, the pointer "/a/b/-" will add a new element to the list
413     * referenced by "/a/b".
414     *
415     * @param pointer
416     *            identifies the child value to add.
417     * @param object
418     *            the Java object value to add.
419     * @return this JSON value.
420     * @throws JsonValueException
421     *             if the specified pointer is invalid.
422     */
423    public JsonValue addPermissive(final JsonPointer pointer, final Object object) {
424        navigateToParentOfPermissive(pointer).addToken(pointer.leaf(), object);
425        return this;
426    }
427
428    /**
429     * Applies all of the transformations to the value. If a transformer affects
430     * the value, then all transformers are re-applied. This repeats until the
431     * value is no longer affected.
432     * <p>
433     * This method has an absurdly high upper-limit of {@link Integer#MAX_VALUE}
434     * iterations, beyond which a {@code JsonException} will be thrown.
435     *
436     * @throws JsonException
437     *             if there was a failure applying transformation(s)
438     */
439    public void applyTransformers() {
440        Object object = this.object;
441        for (int n = 0; n < Integer.MAX_VALUE; n++) {
442            boolean affected = false;
443            for (final JsonTransformer transformer : transformers) {
444                transformer.transform(this);
445                if (!eq(object, this.object)) { // transformer affected the value
446                    object = this.object; // note the new value for next iteration
447                    affected = true;
448                    break; // reiterate all transformers
449                }
450            }
451            if (!affected) { // full iteration of transformers without affecting value
452                return; // success
453            }
454        }
455        throw new JsonException("Transformer iteration overflow");
456    }
457
458    /**
459     * Returns the JSON value as a {@link Boolean} object. If the value is
460     * {@code null}, this method returns {@code null}.
461     *
462     * @return the boolean value.
463     * @throws JsonValueException
464     *             if the JSON value is not a boolean type.
465     */
466    public Boolean asBoolean() {
467        return (object == null ? null : (Boolean) (expect(Boolean.class).object));
468    }
469
470    /**
471     * Returns the JSON string value as a character set used for byte
472     * encoding/decoding. If the JSON value is {@code null}, this method returns
473     * {@code null}.
474     *
475     * @return the character set represented by the string value.
476     * @throws JsonValueException
477     *             if the JSON value is not a string or the character set
478     *             specified is invalid.
479     */
480    public Charset asCharset() {
481        try {
482            return (object == null ? null : Charset.forName(asString()));
483        } catch (final IllegalCharsetNameException | UnsupportedCharsetException e) {
484            throw new JsonValueException(this, e);
485        }
486    }
487
488    /**
489     * Returns the JSON value as a {@link Double} object. This may involve
490     * rounding. If the JSON value is {@code null}, this method returns
491     * {@code null}.
492     *
493     * @return the double-precision floating point value.
494     * @throws JsonValueException
495     *             if the JSON value is not a number.
496     */
497    public Double asDouble() {
498        return (object == null ? null : Double.valueOf(asNumber().doubleValue()));
499    }
500
501    /**
502     * Returns the JSON string value as an enum constant of the specified enum
503     * type. The string value and enum constants are compared, ignoring case
504     * considerations. If the JSON value is {@code null}, this method returns
505     * {@code null}.
506     *
507     * @param <T>
508     *            the enum type sub-class.
509     * @param type
510     *            the enum type to match constants with the value.
511     * @return the enum constant represented by the string value.
512     * @throws IllegalArgumentException
513     *             if {@code type} does not represent an enum type. or
514     *             if the JSON value does not match any of the enum's constants.
515     * @throws NullPointerException
516     *             if {@code type} is {@code null}.
517     */
518    public <T extends Enum<T>> T asEnum(final Class<T> type) {
519        return Utils.asEnum(asString(), type);
520    }
521
522    /**
523     * Returns the JSON string value as a {@code File} object. If the JSON value
524     * is {@code null}, this method returns {@code null}.
525     *
526     * @return a file represented by the string value.
527     * @throws JsonValueException
528     *             if the JSON value is not a string.
529     */
530    public File asFile() {
531        final String string = asString();
532        return (string != null ? new File(string) : null);
533    }
534
535    /**
536     * Returns the JSON value as an {@link Integer} object. This may involve
537     * rounding or truncation. If the JSON value is {@code null}, this method
538     * returns {@code null}.
539     *
540     * @return the integer value.
541     * @throws JsonValueException
542     *             if the JSON value is not a number.
543     */
544    public Integer asInteger() {
545        return (object == null ? null : Integer.valueOf(asNumber().intValue()));
546    }
547
548    /**
549     * Returns the JSON value as a {@link Collection} object. If the JSON value is
550     * {@code null}, this method returns {@code null}.
551     *
552     * @return the collection value, or {@code null} if no value.
553     * @throws JsonValueException
554     *             if the JSON value is not a {@code Collection}.
555     */
556    public Collection<Object> asCollection() {
557        return asCollection(Object.class);
558    }
559
560    /**
561     * Returns the JSON value as a {@link List} object. If the JSON value is
562     * {@code null}, this method returns {@code null}.
563     *
564     * @return the list value, or {@code null} if no value.
565     * @throws JsonValueException
566     *             if the JSON value is not a {@code List}.
567     */
568    public List<Object> asList() {
569        return asList(Object.class);
570    }
571
572    /**
573     * Returns the JSON value as a {@link Set} object. If the JSON value is
574     * {@code null}, this method returns {@code null}.
575     *
576     * @return the set value, or {@code null} if no value.
577     * @throws JsonValueException
578     *             if the JSON value is not a {@code Set}.
579     */
580    public Set<Object> asSet() {
581        return asSet(Object.class);
582    }
583
584    /**
585     * Returns the JSON value as a {@link Collection} containing objects of the
586     * specified type. If the value is {@code null}, this method returns
587     * {@code null}. If any of the elements of the collection are not {@code null} and
588     * not of the specified type, {@code JsonValueException} is thrown.
589     *
590     * @param <E>
591     *            the type of elements in this collection
592     * @param type
593     *            the type of object that all elements are expected to be.
594     * @return the collection value, or {@code null} if no value.
595     * @throws JsonValueException
596     *             if the JSON value is not a {@code Collection} or contains an
597     *             unexpected type.
598     * @throws NullPointerException
599     *             if {@code type} is {@code null}.
600     */
601    @SuppressWarnings("unchecked")
602    public <E> Collection<E> asCollection(final Class<E> type) {
603        if (object != null) {
604            expect(Collection.class);
605            if (type != Object.class) {
606                final Collection<Object> coll = (Collection<Object>) this.object;
607                for (final Object element : coll) {
608                    if (element != null && !type.isInstance(element)) {
609                        throw new JsonValueException(this, "Expecting a Collection of " + type.getName()
610                                + " elements");
611                    }
612                }
613            }
614        }
615        return (Collection<E>) object;
616    }
617
618    /**
619     * Returns the JSON value as a {@link List} containing objects of the
620     * specified type. If the value is {@code null}, this method returns
621     * {@code null}. If any of the elements of the list are not {@code null} and
622     * not of the specified type, {@code JsonValueException} is thrown.
623     *
624     * @param <E>
625     *            the type of elements in this list
626     * @param type
627     *            the type of object that all elements are expected to be.
628     * @return the list value, or {@code null} if no value.
629     * @throws JsonValueException
630     *             if the JSON value is not a {@code List}, not a {@code Set},
631     *             or contains an unexpected type.
632     * @throws NullPointerException
633     *             if {@code type} is {@code null}.
634     */
635    @SuppressWarnings("unchecked")
636    public <E> List<E> asList(final Class<E> type) {
637        if (object != null) {
638            if (isSet()) {
639                return new ArrayList<>(asSet(type));
640            }
641            expect(List.class);
642            if (type != Object.class) {
643                final List<Object> list = (List<Object>) this.object;
644                for (final Object element : list) {
645                    if (element != null && !type.isInstance(element)) {
646                        throw new JsonValueException(this, "Expecting a List of " + type.getName()
647                                + " elements");
648                    }
649                }
650            }
651        }
652        return (List<E>) object;
653    }
654
655    /**
656     * Returns the JSON value as a {@link Set} containing objects of the
657     * specified type. If the value is {@code null}, this method returns
658     * {@code null}. If any of the elements of the set are not {@code null} and
659     * not of the specified type, {@code JsonValueException} is thrown.  If
660     * called on an object which wraps a List, this method will drop duplicates
661     * performing element comparisons using equals/hashCode.
662     *
663     * @param <E>
664     *            the type of elements in this set
665     * @param type
666     *            the type of object that all elements are expected to be.
667     * @return the set value, or {@code null} if no value.
668     * @throws JsonValueException
669     *             if the JSON value is not a {@code Set}, not a {@code List},
670     *             or contains an unexpected type.
671     * @throws NullPointerException
672     *             if {@code type} is {@code null}.
673     */
674    @SuppressWarnings("unchecked")
675    public <E> Set<E> asSet(final Class<E> type) {
676        if (object != null) {
677            if (isList()) {
678                return new LinkedHashSet<>(asList(type));
679            }
680            expect(Set.class);
681            if (type != Object.class) {
682                final Set<Object> list = (Set<Object>) this.object;
683                for (final Object element : list) {
684                    if (element != null && !type.isInstance(element)) {
685                        throw new JsonValueException(this, "Expecting a Set of " + type.getName()
686                                + " elements");
687                    }
688                }
689            }
690        }
691        return (Set<E>) object;
692    }
693
694    /**
695     * Returns the JSON value as a {@link List} containing objects whose type
696     * (and value) is specified by a transformation function. If the value is
697     * {@code null}, this method returns {@code null}. It is up to to the
698     * transformation function to transform/enforce source types of the elements
699     * in the Json source collection.  If any of the elements of the list are not of
700     * the appropriate type, or the type-transformation cannot occur,
701     * the exception specified by the transformation function is thrown.
702     *
703     * @param <V>
704     *            the type of elements in this list
705     * @param <E>
706     *            the type of exception thrown by the transformation function
707     * @param transformFunction
708     *            a {@link Function} to transform an element of the JsonValue list
709     *            to the desired type
710     * @return the list value, or {@code null} if no value.
711     * @throws E
712     *             if the JSON value is not a {@code List}, not a {@code Set}, contains an
713     *             unexpected type, or contains an element that cannot be transformed
714     * @throws NullPointerException
715     *             if {@code transformFunction} is {@code null}.
716     */
717    @SuppressWarnings("unchecked")
718    public <V, E extends Exception> List<V> asList(final Function<JsonValue, V, E> transformFunction) throws E {
719        if (object != null) {
720            if (isSet()) {
721                return new ArrayList<>(asSet(transformFunction));
722            }
723            expect(List.class);
724            final int size = size();
725            final List<V> list = new ArrayList<>(size);
726            for (int i = 0; i < size; i++) {
727                list.add(transformFunction.apply(get(i)));
728            }
729            return list;
730        }
731        return (List<V>) object;
732    }
733
734    /**
735     * Returns the JSON value as a {@link Set} containing objects whose type
736     * (and value) is specified by a transformation function. If the value is
737     * {@code null}, this method returns {@code null}. It is up to to the
738     * transformation function to transform/enforce source types of the elements
739     * in the Json source collection.  If called on an object which wraps a List,
740     * this method will drop duplicates performing element comparisons using
741     * equals/hashCode. If any of the elements of the collection are not of
742     * the appropriate type, or the type-transformation cannot occur, the
743     * exception specified by the transformation function is thrown.
744     *
745     * @param <V>
746     *            the type of elements in this set
747     * @param <E>
748     *            the type of exception thrown by the transformation function
749     * @param transformFunction
750     *            a {@link Function} to transform an element of the JsonValue set
751     *            to the desired type
752     * @return the list value, or {@code null} if no value.
753     * @throws E
754     *             if the JSON value is not a {@code Set}, contains an
755     *             unexpected type, or contains an element that cannot be
756     *             transformed
757     * @throws NullPointerException
758     *             if {@code transformFunction} is {@code null}.
759     */
760    @SuppressWarnings("unchecked")
761    public <V, E extends Exception> Set<V> asSet(final Function<JsonValue, V, E> transformFunction) throws E {
762        if (object != null) {
763            if (isList()) {
764                return new LinkedHashSet<>(asList(transformFunction));
765            }
766            expect(Set.class);
767            final int size = size();
768            final Set<V> set = new LinkedHashSet<>(size);
769            for (JsonValue element : this) {
770                set.add(transformFunction.apply(element));
771            }
772            return set;
773        }
774        return (Set<V>) object;
775    }
776
777    /**
778     * Returns the JSON value as a {@link Long} object. This may involve
779     * rounding or truncation. If the JSON value is {@code null}, this method
780     * returns {@code null}.
781     *
782     * @return the long integer value.
783     * @throws JsonValueException
784     *             if the JSON value is not a number.
785     */
786    public Long asLong() {
787        return (object == null ? null : Long.valueOf(asNumber().longValue()));
788    }
789
790    /**
791     * Returns the JSON value as a {@code Map} object. If the JSON value is
792     * {@code null}, this method returns {@code null}.
793     *
794     * @return the map value, or {@code null} if no value.
795     * @throws JsonValueException
796     *             if the JSON value is not a {@code Map}.
797     */
798    @SuppressWarnings("unchecked")
799    public Map<String, Object> asMap() {
800        return (object == null ? null : (Map<String, Object>) (expect(Map.class).object));
801    }
802
803    /**
804     * Returns the JSON value as a {@link Map} containing objects of the
805     * specified type. If the value is {@code null}, this method returns
806     * {@code null}. If any of the values of the map are not {@code null} and
807     * not of the specified type, {@code JsonValueException} is thrown.
808     *
809     * @param <V>
810     *            the type of values in this map
811     * @param type
812     *            the type of object that all values are expected to be.
813     * @return the map value, or {@code null} if no value.
814     * @throws JsonValueException
815     *             if the JSON value is not a {@code Map} or contains an
816     *             unexpected type.
817     * @throws NullPointerException
818     *             if {@code type} is {@code null}.
819     */
820    @SuppressWarnings("unchecked")
821    public <V> Map<String, V> asMap(final Class<V> type) {
822        if (object != null) {
823            expect(Map.class);
824            if (type != Object.class) {
825                final Map<String, Object> map = (Map<String, Object>) this.object;
826                for (final Object element : map.values()) {
827                    if (element != null && !type.isInstance(element)) {
828                        throw new JsonValueException(this, "Expecting a Map of " + type.getName()
829                                + " elements");
830                    }
831                }
832            }
833        }
834        return (Map<String, V>) object;
835    }
836    /**
837     * Returns the JSON value as a {@link Map} containing a collection of
838     * objects of the specified type. If the value is {@code null}, this method
839     * returns {@code null}. If any of the values of the map are not {@code null} and
840     * not of the specified type, {@code JsonValueException} is thrown.
841     *
842     * @param <E>
843     *            the type of elements in the collection
844     * @param elementType
845     *            the type of object that all collection elements are
846     *            expected to be.
847     * @return the map value, or {@code null} if no value.
848     * @throws JsonValueException
849     *             if the JSON value is not a {@code Map} or contains an
850     *             unexpected type.
851     * @throws NullPointerException
852     *             if {@code type} is {@code null}.
853     */
854    @SuppressWarnings("unchecked")
855    public <E> Map<String, List<E>> asMapOfList(final Class<E> elementType) {
856        if (object != null) {
857            expect(Map.class);
858            if (elementType != Object.class) {
859                final Map<String, Object> map = (Map<String, Object>) this.object;
860                for (final Object value : map.values()) {
861                    if (value != null && !(value instanceof List)) {
862                        throw new JsonValueException(this, "Expecting a Map of List values");
863                    }
864                    final List<?> list = (List<?>) value;
865                    for (final Object element : list) {
866                        if (element != null && !elementType.isInstance(element)) {
867                            throw new JsonValueException(this, "Expecting a Map of Lists with "
868                                    + elementType.getName() + " elements");
869                        }
870                    }
871                }
872            }
873        }
874        return (Map<String, List<E>>) object;
875    }
876
877    /**
878     * Returns the JSON value as a {@code Number} object. If the JSON value is
879     * {@code null}, this method returns {@code null}.
880     *
881     * @return the numeric value.
882     * @throws JsonValueException
883     *             if the JSON value is not a number.
884     */
885    public Number asNumber() {
886        return (object == null ? null : (Number) (expect(Number.class).object));
887    }
888
889    /**
890     * Returns the JSON string value as a regular expression pattern. If the
891     * JSON value is {@code null}, this method returns {@code null}.
892     *
893     * @return the compiled regular expression pattern.
894     * @throws JsonValueException
895     *             if the pattern is not a string or the value is not a valid
896     *             regular expression pattern.
897     */
898    public Pattern asPattern() {
899        try {
900            return (object == null ? null : Pattern.compile(asString()));
901        } catch (final PatternSyntaxException pse) {
902            throw new JsonValueException(this, pse);
903        }
904    }
905
906    /**
907     * Returns the JSON string value as a JSON pointer. If the JSON value is
908     * {@code null}, this method returns {@code null}.
909     *
910     * @return the JSON pointer represented by the JSON value string.
911     * @throws JsonValueException
912     *             if the JSON value is not a string or valid JSON pointer.
913     */
914    public JsonPointer asPointer() {
915        try {
916            return (object == null ? null : new JsonPointer(asString()));
917        } catch (final JsonException je) {
918            throw (je instanceof JsonValueException ? je : new JsonValueException(this, je));
919        }
920    }
921
922    /**
923     * Returns the JSON value as a {@code String} object. If the JSON value is
924     * {@code null}, this method returns {@code null}.
925     *
926     * @return the string value.
927     * @throws JsonValueException
928     *             if the JSON value is not a string.
929     */
930    public String asString() {
931        return (object == null ? null : (String) (expect(String.class).object));
932    }
933
934    /**
935     * Returns the JSON string value as a uniform resource identifier. If the
936     * JSON value is {@code null}, this method returns {@code null}.
937     *
938     * @return the URI represented by the string value.
939     * @throws JsonValueException
940     *             if the given string violates URI syntax.
941     */
942    public URI asURI() {
943        try {
944            return (object == null ? null : new URI(asString()));
945        } catch (final URISyntaxException use) {
946            throw new JsonValueException(this, use);
947        }
948    }
949
950    /**
951     * Returns the JSON string value as a uniform resource locator. If the
952     * JSON value is {@code null}, this method returns {@code null}.
953     *
954     * @return the URL represented by the string value.
955     * @throws JsonValueException
956     *             if the given string violates URL syntax.
957     */
958    public URL asURL() {
959        try {
960            return (object == null ? null : new URL(asString()));
961        } catch (final MalformedURLException mue) {
962            throw new JsonValueException(this, mue);
963        }
964    }
965
966    /**
967     * Returns the JSON string value as a universally unique identifier (UUID).
968     * If the JSON value is {@code null}, this method returns {@code null}.
969     *
970     * @return the UUID represented by the JSON value string.
971     * @throws JsonValueException
972     *             if the JSON value is not a string or valid UUID.
973     */
974    public UUID asUUID() {
975        try {
976            return (object == null ? null : UUID.fromString(asString()));
977        } catch (final IllegalArgumentException iae) {
978            throw new JsonValueException(this, iae);
979        }
980    }
981
982    /**
983     * Returns a subclass of JsonValue that records which keys are accessed in this {@link JsonValue} and its children.
984     * Call #verifyAllKeysAccessed() to verify that all keys were accessed. The returned JsonValue provides an
985     * immutable view on the monitored underlying JsonValue.
986     *
987     * @return a JsonValue monitoring which properties are accessed
988     * @see #verifyAllKeysAccessed()
989     */
990    public JsonValue recordKeyAccesses() {
991        return new JsonValueKeyAccessChecker(this);
992    }
993
994    /**
995     * Verifies that all keys in this {@link JsonValue} and its children have been accessed. #recordKeyAccesses() must
996     * have been called before, otherwise this method will do nothing.
997     *
998     * @see #recordKeyAccesses()
999     */
1000    public void verifyAllKeysAccessed() {
1001    }
1002
1003    /**
1004     * Removes all child values from this JSON value, if it has any.
1005     */
1006    public void clear() {
1007        if (isMap()) {
1008            asMap().clear();
1009        } else if (isCollection()) {
1010            asCollection().clear();
1011        }
1012    }
1013
1014    /**
1015     * Returns a shallow copy of this JSON value. If this JSON value contains a
1016     * {@code Map}, a {@code Set}, or a {@code List} object, the returned JSON
1017     * value will contain a shallow copy of the original contained object.
1018     * <p>
1019     * The new value's members can be modified without affecting the original
1020     * value. Modifying the member's members will almost certainly affect the
1021     * original value. To avoid this, use the {@link #copy} method to return a
1022     * deep copy of the JSON value.
1023     * <p>
1024     * This method does not traverse the value's members, nor will it apply any
1025     * transformations.
1026     *
1027     * @return a shallow copy of this JSON value.
1028     */
1029    @Override
1030    public JsonValue clone() {
1031        final JsonValue result = new JsonValue(this.object, this.pointer);
1032        result.transformers.addAll(this.transformers); // avoid re-applying transformers
1033        if (isMap()) {
1034            result.object = new LinkedHashMap<>(this.asMap());
1035        } else if (isList()) {
1036            result.object = new ArrayList<>(this.asList());
1037        } else if (isSet()) {
1038            result.object = new LinkedHashSet<>(this.asSet());
1039        }
1040        return result;
1041    }
1042
1043    /**
1044     * Returns {@code true} this JSON value contains an item with the specified
1045     * value.
1046     *
1047     * @param object
1048     *            the object to seek within this JSON value.
1049     * @return {@code true} if this value contains the specified member value.
1050     */
1051    public boolean contains(final Object object) {
1052        boolean result = false;
1053        if (isMap()) {
1054            result = asMap().containsValue(object);
1055        } else if (isCollection()) {
1056            result = asCollection().contains(object);
1057        }
1058        return result;
1059    }
1060
1061    /**
1062     * Returns a deep copy of this JSON value.
1063     * <p>
1064     * This method applies all transformations while traversing the values's
1065     * members and their members, and so on. Consequently, the returned copy
1066     * does not include the transformers from this value.
1067     * <p>
1068     * Note: This method is recursive, and currently has no ability to detect or
1069     * correct for structures containing cyclic references. Processing such a
1070     * structure will result in a {@link StackOverflowError} being thrown.
1071     *
1072     * @return a deep copy of this JSON value.
1073     */
1074    public JsonValue copy() {
1075        // TODO: track original values to resolve cyclic references
1076        final JsonValue result = new JsonValue(object, pointer); // start with shallow copy
1077        if (this.isMap()) {
1078            final Map<String, Object> map = new LinkedHashMap<>(size());
1079            for (final String key : keys()) {
1080                map.put(key, this.get(key).copy().getObject()); // recursion
1081            }
1082            result.object = map;
1083        } else if (isList()) {
1084            final ArrayList<Object> list = new ArrayList<>(size());
1085            for (final JsonValue element : this) {
1086                list.add(element.copy().getObject()); // recursion
1087            }
1088            result.object = list;
1089        } else if (isSet()) {
1090            final Set<Object> set = new LinkedHashSet<>(size());
1091            for (final JsonValue element : this) {
1092                set.add(element.copy().getObject()); // recursion
1093            }
1094            result.object = set;
1095        }
1096        return result;
1097    }
1098
1099    /**
1100     * Defaults the JSON value to the specified value if it is currently
1101     * {@code null}.
1102     *
1103     * @param object
1104     *            the object to default to.
1105     * @return this JSON value or a new JSON value containing the default value.
1106     */
1107    public JsonValue defaultTo(final Object object) {
1108        return (this.object != null ? this : new JsonValue(object, this.pointer, this.transformers));
1109    }
1110
1111    /**
1112     * Called to enforce that the JSON value is of a particular type. A value of
1113     * {@code null} is allowed.
1114     *
1115     * @param type
1116     *            the class that the underlying value must have.
1117     * @return this JSON value.
1118     * @throws JsonValueException
1119     *             if the value is not the specified type.
1120     */
1121    public JsonValue expect(final Class<?> type) {
1122        if (object != null && !type.isInstance(object)) {
1123            throw new JsonValueException(this, "Expecting a " + type.getName());
1124        }
1125        return this;
1126    }
1127
1128    /**
1129     * Returns the specified child value. If this JSON value is not a
1130     * {@link List} or if no such child exists, then a JSON value containing a
1131     * {@code null} is returned.
1132     *
1133     * @param index
1134     *            index of child element value to return.
1135     * @return the child value, or a JSON value containing {@code null}.
1136     * @throws JsonValueException
1137     *             if index is negative.
1138     * @throws JsonException
1139     *             if a transformer failed to transform the child value.
1140     */
1141    public JsonValue get(final int index) {
1142        Object result = null;
1143        if (index < 0) {
1144            throw new JsonValueException(this, "List index out of range: " + index);
1145        }
1146        if (isList() && index >= 0) {
1147            final List<Object> list = asList();
1148            if (index < list.size()) {
1149                result = list.get(index);
1150            }
1151        }
1152        return new JsonValue(result, pointer.child(index), transformers);
1153    }
1154
1155    /**
1156     * Returns the specified child value with a pointer, relative to this value
1157     * as root. If the specified child value does not exist, then {@code null}
1158     * is returned.
1159     *
1160     * @param pointer
1161     *            the JSON pointer identifying the child value to return.
1162     * @return the child value, or {@code null} if no such value exists.
1163     * @throws JsonException
1164     *             if a transformer failed to transform the resulting value.
1165     */
1166    public JsonValue get(final JsonPointer pointer) {
1167        JsonValue result = this;
1168        for (final String token : pointer) {
1169            final JsonValue member = result.get(token);
1170            if (member.isNull() && !result.isDefined(token)) {
1171                return null; // undefined value yields null, not a JSON value containing null
1172            }
1173            result = member;
1174        }
1175        return result;
1176    }
1177
1178    /**
1179     * Returns the specified item value. If no such member value exists, then a
1180     * JSON value containing {@code null} is returned.
1181     *
1182     * @param key
1183     *            the {@code Map} key or {@code List} index identifying the item
1184     *            to return.
1185     * @return a JSON value containing the value or {@code null}.
1186     * @throws JsonException
1187     *             if a transformer failed to transform the child value.
1188     */
1189    public JsonValue get(final String key) {
1190        Object result = null;
1191        if (isMap()) {
1192            result = asMap().get(key);
1193        } else if (isList()) {
1194            final List<Object> list = asList();
1195            final int index = toIndex(key);
1196            if (index >= 0 && index < list.size()) {
1197                result = list.get(index);
1198            }
1199        }
1200        return new JsonValue(result, pointer.child(key), transformers);
1201    }
1202
1203    /**
1204     * Returns the raw Java object representing this JSON value.
1205     *
1206     * @return the raw Java object representing this JSON value.
1207     */
1208    public Object getObject() {
1209        return object;
1210    }
1211
1212    /**
1213     * Returns the pointer of the JSON value in its JSON structure.
1214     *
1215     * @return the pointer of the JSON value in its JSON structure.
1216     */
1217    public JsonPointer getPointer() {
1218        return pointer;
1219    }
1220
1221    /**
1222     * Returns the JSON value's list of transformers. This list is modifiable.
1223     * Child values inherit the list when they are constructed. If any
1224     * transformers are added to the list, call the {@link #applyTransformers()}
1225     * method to apply them to the current value.
1226     *
1227     * @return the JSON value's list of transformers.
1228     */
1229    public List<JsonTransformer> getTransformers() {
1230        return transformers;
1231    }
1232
1233    /**
1234     * Returns {@code true} if the JSON value is a {@link Boolean}.
1235     *
1236     * @return {@code true} if the JSON value is a {@link Boolean}.
1237     */
1238    public boolean isBoolean() {
1239        return (object != null && object instanceof Boolean);
1240    }
1241
1242    /**
1243     * Returns {@code true} if this JSON value contains the specified item.
1244     *
1245     * @param key
1246     *            the {@code Map} key or {@code List} index of the item to seek.
1247     * @return {@code true} if this JSON value contains the specified member.
1248     * @throws NullPointerException
1249     *             if {@code key} is {@code null}.
1250     */
1251    public boolean isDefined(final String key) {
1252        boolean result = false;
1253        if (isMap()) {
1254            result = asMap().containsKey(key);
1255        } else if (isList()) {
1256            final int index = toIndex(key);
1257            result = (index >= 0 && index < asList().size());
1258        }
1259        return result;
1260    }
1261
1262    /**
1263     * Returns {@code true} if the JSON value is a {@link Collection}.
1264     *
1265     * @return {@code true} if the JSON value is a {@link Collection}.
1266     */
1267    public boolean isCollection() {
1268        return (object instanceof Collection);
1269    }
1270
1271    /**
1272     * Returns {@code true} if the JSON value is a {@link List}.
1273     *
1274     * @return {@code true} if the JSON value is a {@link List}.
1275     */
1276    public boolean isList() {
1277        return (object instanceof List);
1278    }
1279
1280    /**
1281     * Returns {@code true} if the JSON value is a {@link Set}.
1282     *
1283     * @return {@code true} if the JSON value is a {@link Set}.
1284     */
1285    public boolean isSet() {
1286        return (object instanceof Set);
1287    }
1288
1289    /**
1290     * Returns {@code true} if the JSON value is a {@link Map}.
1291     *
1292     * @return {@code true} if the JSON value is a {@link Map}.
1293     */
1294    public boolean isMap() {
1295        return (object instanceof Map);
1296    }
1297
1298    /**
1299     * Returns {@code true} if the value is {@code null}.
1300     *
1301     * @return {@code true} if the value is {@code null}.
1302     */
1303    public boolean isNull() {
1304        return (object == null);
1305    }
1306
1307    /**
1308     * Returns {@code true} if the value is not {@code null}.
1309     *
1310     * @return {@code true} if the value is not {@code null}.
1311     */
1312    public boolean isNotNull() {
1313        return !isNull();
1314    }
1315
1316    /**
1317     * Returns {@code true} if the JSON value is a {@link Number}.
1318     *
1319     * @return {@code true} if the JSON value is a {@link Number}.
1320     */
1321    public boolean isNumber() {
1322        return (object != null && object instanceof Number);
1323    }
1324
1325    /**
1326     * Returns {@code true} if the JSON value is a {@link String}.
1327     *
1328     * @return {@code true} if the JSON value is a {@link String}.
1329     */
1330    public boolean isString() {
1331        return (object != null && object instanceof String);
1332    }
1333
1334    /**
1335     * Returns an iterator over the child values that this JSON value contains.
1336     * If this value is a {@link Map} or a {@link Set}, then the order of the
1337     * resulting child values is undefined. Calling the {@link Iterator#remove()}
1338     * method of the returned iterator will throw a {@link UnsupportedOperationException}.
1339     * <p>
1340     * Note: calls to the {@code next()} method may throw the runtime
1341     * {@link JsonException} if any transformers fail to execute.
1342     *
1343     * @return an iterator over the child values that this JSON value contains.
1344     */
1345    @Override
1346    public Iterator<JsonValue> iterator() {
1347        if (isList()) { // optimize for list
1348            return new Iterator<JsonValue>() {
1349                int cursor = 0;
1350                Iterator<Object> i = asList().iterator();
1351
1352                @Override
1353                public boolean hasNext() {
1354                    return i.hasNext();
1355                }
1356
1357                @Override
1358                public JsonValue next() {
1359                    final Object element = i.next();
1360                    return new JsonValue(element, pointer.child(cursor++), transformers);
1361                }
1362
1363                @Override
1364                public void remove() {
1365                    throw new UnsupportedOperationException();
1366                }
1367            };
1368        } else if (isSet()) {
1369            return new Iterator<JsonValue>() {
1370                Iterator<Object> i = asSet().iterator();
1371
1372                @Override
1373                public boolean hasNext() {
1374                    return i.hasNext();
1375                }
1376
1377                @Override
1378                public JsonValue next() {
1379                    final Object element = i.next();
1380                    return new JsonValue(element, pointer.child(String.valueOf(object)), transformers);
1381                }
1382
1383                @Override
1384                public void remove() {
1385                    throw new UnsupportedOperationException();
1386                }
1387            };
1388        } else {
1389            return new Iterator<JsonValue>() {
1390                Iterator<String> i = keys().iterator();
1391
1392                @Override
1393                public boolean hasNext() {
1394                    return i.hasNext();
1395                }
1396
1397                @Override
1398                public JsonValue next() {
1399                    return get(i.next());
1400                }
1401
1402                @Override
1403                public void remove() {
1404                    throw new UnsupportedOperationException();
1405                }
1406            };
1407        }
1408    }
1409
1410    /**
1411     * Returns the set of keys for this JSON value's child values. If this value
1412     * is a {@code Map}, then the order of the resulting keys is the same as the
1413     * underlying Map implementation. If there are no child values, this method
1414     * returns an empty set.
1415     *
1416     * @return the set of keys for this JSON value's child values.
1417     */
1418    public Set<String> keys() {
1419        if (isMap()) {
1420            return asMap().keySet();
1421        } else if (isList()) {
1422            return new AbstractSet<String>() {
1423                final RangeSet range = new RangeSet(JsonValue.this.size()); // 0 through size-1 inclusive
1424
1425                @Override
1426                public boolean contains(final Object o) {
1427                    boolean result = false;
1428                    if (o instanceof String) {
1429                        try {
1430                            result = range.contains(Integer.valueOf((String) o));
1431                        } catch (final NumberFormatException nfe) {
1432                            // ignore; yields false
1433                        }
1434                    }
1435                    return result;
1436                }
1437
1438                @Override
1439                public Iterator<String> iterator() {
1440                    return new Iterator<String>() {
1441                        Iterator<Integer> i = range.iterator();
1442
1443                        @Override
1444                        public boolean hasNext() {
1445                            return i.hasNext();
1446                        }
1447
1448                        @Override
1449                        public String next() {
1450                            return i.next().toString();
1451                        }
1452
1453                        @Override
1454                        public void remove() {
1455                            throw new UnsupportedOperationException();
1456                        }
1457                    };
1458                }
1459
1460                @Override
1461                public int size() {
1462                    return range.size();
1463                }
1464            };
1465        } else {
1466            return Collections.emptySet();
1467        }
1468    }
1469
1470    /**
1471     * Sets the value of the specified child list element.
1472     *
1473     * @param index
1474     *            the {@code List} index identifying the child value to set.
1475     * @param object
1476     *            the Java value to assign to the list element.
1477     * @return this JSON value.
1478     * @throws JsonValueException
1479     *             if this JSON value is not a {@code List} or index is out of
1480     *             range.
1481     */
1482    public JsonValue put(final int index, final Object object) {
1483        final List<Object> list = required().asList();
1484        if (index < 0 || index > list.size()) {
1485            throw new JsonValueException(this, "List index out of range: " + index);
1486        } else if (index == list.size()) { // appending to end of list
1487            list.add(object);
1488        } else { // replacing existing element
1489            list.set(index, object);
1490        }
1491        return this;
1492    }
1493
1494    /**
1495     * Sets the value identified by the specified pointer, relative to this
1496     * value as root. If doing so would require the creation of a new object or
1497     * list, a {@code JsonValueException} will be thrown.
1498     * <p>
1499     * NOTE: values may be added to a list using the reserved JSON pointer token
1500     * "-". For example, the pointer "/a/b/-" will add a new element to the list
1501     * referenced by "/a/b".
1502     *
1503     * @param pointer
1504     *            identifies the child value to set.
1505     * @param object
1506     *            the Java object value to set.
1507     * @return this JSON value.
1508     * @throws JsonValueException
1509     *             if the specified pointer is invalid.
1510     */
1511    public JsonValue put(final JsonPointer pointer, final Object object) {
1512        navigateToParentOf(pointer).required().putToken(pointer.leaf(), object);
1513        return this;
1514    }
1515
1516    /**
1517     * Sets the value of the specified member.  
1518     * <p>
1519     * If setting a list element, the specified key must be parseable as an
1520     * unsigned base-10 integer and be less than or equal to the size of the
1521     * list.
1522     *
1523     * @param key
1524     *            the {@code Map} key or {@code List} index identifying the
1525     *            child value to set.
1526     * @param object
1527     *            the object value to assign to the member.
1528     * @return this JSON value.
1529     * @throws JsonValueException
1530     *             if this JSON value is not a {@code Map} or {@code List}.
1531     * @throws NullPointerException
1532     *             if {@code key} is {@code null}.
1533     */
1534    public JsonValue put(final String key, final Object object) {
1535        if (key == null) {
1536            throw new NullPointerException();
1537        } else if (isMap()) {
1538            asMap().put(key, object);
1539        } else if (isList()) {
1540            put(toIndex(key), object);
1541        } else {
1542            throw new JsonValueException(this, "Expecting a Map or List");
1543        }
1544        return this;
1545    }
1546
1547    /**
1548     * Sets the value identified by the specified pointer, relative to this
1549     * value as root. Missing parent objects or lists will be created on demand.
1550     * <p>
1551     * NOTE: values may be added to a list using the reserved JSON pointer token
1552     * "-". For example, the pointer "/a/b/-" will add a new element to the list
1553     * referenced by "/a/b".
1554     *
1555     * @param pointer
1556     *            identifies the child value to set.
1557     * @param object
1558     *            the Java object value to set.
1559     * @return this JSON value.
1560     * @throws JsonValueException
1561     *             if the specified pointer is invalid.
1562     */
1563    public JsonValue putPermissive(final JsonPointer pointer, final Object object) {
1564        navigateToParentOfPermissive(pointer).putToken(pointer.leaf(), object);
1565        return this;
1566    }
1567
1568    /**
1569     * Removes the specified child value, shifting any subsequent elements to
1570     * the left. If the JSON value is not a {@code List}, calling this method
1571     * has no effect.
1572     *
1573     * @param index
1574     *            the {@code List} index identifying the child value to remove.
1575     */
1576    public void remove(final int index) {
1577        if (index >= 0 && isList()) {
1578            final List<Object> list = asList();
1579            if (index < list.size()) {
1580                list.remove(index);
1581            }
1582        }
1583    }
1584
1585    /**
1586     * Removes the specified child value with a pointer, relative to this value
1587     * as root. If the specified child value is not defined, calling this method
1588     * has no effect.
1589     *
1590     * @param pointer
1591     *            the JSON pointer identifying the child value to remove.
1592     */
1593    public void remove(final JsonPointer pointer) {
1594        navigateToParentOf(pointer).remove(pointer.leaf());
1595    }
1596
1597    /**
1598     * Removes the specified child value. If the specified child value is not
1599     * defined, calling this method has no effect.
1600     *
1601     * @param key
1602     *            the {@code Map} key or {@code List} index identifying the
1603     *            child value to remove.
1604     */
1605    public void remove(final String key) {
1606        if (isMap()) {
1607            asMap().remove(key);
1608        } else if (isList()) {
1609            remove(toIndex(key));
1610        }
1611    }
1612
1613    /**
1614     * Throws a {@code JsonValueException} if the JSON value is {@code null}.
1615     *
1616     * @return this JSON value.
1617     * @throws JsonValueException
1618     *             if the JSON value is {@code null}.
1619     */
1620    public JsonValue required() {
1621        if (object == null) {
1622            throw new JsonValueException(this, "Expecting a value");
1623        }
1624        return this;
1625    }
1626
1627    /**
1628     * Sets the Java object representing this JSON value. Does not apply
1629     * transformers to the new value.
1630     * <p>
1631     * This method will automatically unwrap {@link JsonValue} objects.
1632     * Transformers are inherited from the wrapped value. This value's pointer
1633     * remains unaffected.
1634     *
1635     * @param object
1636     *            the object to set.
1637     */
1638    public void setObject(final Object object) {
1639        this.object = object;
1640        if (object instanceof JsonValue) {
1641            final JsonValue jv = (JsonValue) object;
1642            this.object = jv.object;
1643            this.transformers.addAll(jv.transformers);
1644        }
1645    }
1646
1647    /**
1648     * Returns the number of values that this JSON value contains.
1649     *
1650     * @return the number of values that this JSON value contains.
1651     */
1652    public int size() {
1653        if (isMap()) {
1654            return asMap().size();
1655        } else if (isCollection()) {
1656            return asCollection().size();
1657        } else {
1658            return 0;
1659        }
1660    }
1661
1662    /**
1663     * Returns a string representation of the JSON value. The result
1664     * resembles—but is not guaranteed to conform to—JSON syntax. This method
1665     * does not apply transformations to the value's children.
1666     *
1667     * @return a string representation of the JSON value.
1668     */
1669    @SuppressWarnings("unchecked")
1670    @Override
1671    public String toString() {
1672        final StringBuilder sb = new StringBuilder();
1673        if (isNull()) {
1674            sb.append("null");
1675        } else if (isMap()) {
1676            sb.append("{ ");
1677            final Map<Object, Object> map = (Map<Object, Object>) object;
1678            for (final Iterator<Object> i = map.keySet().iterator(); i.hasNext();) {
1679                final Object key = i.next();
1680                sb.append('"');
1681                appendEscapedString(sb, key.toString());
1682                sb.append("\": ");
1683                sb.append(new JsonValue(map.get(key)).toString()); // recursion
1684                if (i.hasNext()) {
1685                    sb.append(", ");
1686                }
1687            }
1688            sb.append(" }");
1689        } else if (isCollection()) {
1690            sb.append("[ ");
1691            for (final Iterator<Object> i = ((Collection<Object>) object).iterator(); i.hasNext();) {
1692                sb.append(new JsonValue(i.next()).toString()); // recursion
1693                if (i.hasNext()) {
1694                    sb.append(", ");
1695                }
1696            }
1697            sb.append(" ]");
1698        } else if (isString()) {
1699            sb.append('"');
1700            appendEscapedString(sb, object.toString());
1701            sb.append('"');
1702        } else {
1703            sb.append(object.toString());
1704        }
1705        return sb.toString();
1706    }
1707
1708    /**
1709     * As per json.org a string is any Unicode character except " or \ or
1710     * control characters. Special characters will be escaped using a \ as
1711     * follows:
1712     * <ul>
1713     * <li> {@literal \ "} - double quote
1714     * <li> {@literal \ \} - back slash
1715     * <li> {@literal \ b} - backspace
1716     * <li> {@literal \ f} - form feed
1717     * <li> {@literal \ n} - new line
1718     * <li> {@literal \ r} - carriage return
1719     * <li> {@literal \ t} - tab
1720     * <li> {@literal \ u xxxx} - other control characters.
1721     * </ul>
1722     */
1723    private static void appendEscapedString(final StringBuilder sb, final String s) {
1724        final int size = s.length();
1725        for (int i = 0; i < size; i++) {
1726            final char c = s.charAt(i);
1727            switch (c) {
1728            // Escape characters which must be escaped.
1729            case '"':
1730                sb.append("\\\"");
1731                break;
1732            case '\\':
1733                sb.append("\\\\");
1734                break;
1735            // Escape common controls to the C equivalent to make them easier to read.
1736            case '\b':
1737                sb.append("\\b");
1738                break;
1739            case '\f':
1740                sb.append("\\f");
1741                break;
1742            case '\n':
1743                sb.append("\\n");
1744                break;
1745            case '\r':
1746                sb.append("\\r");
1747                break;
1748            case '\t':
1749                sb.append("\\t");
1750                break;
1751            default:
1752                if (Character.isISOControl(c)) {
1753                    final String hex = Integer.toHexString(c).toUpperCase(Locale.ENGLISH);
1754                    final int hexPadding = 4 - hex.length();
1755                    sb.append("\\u");
1756                    for (int j = 0; j < hexPadding; j++) {
1757                        sb.append('0');
1758                    }
1759                    sb.append(hex);
1760                } else {
1761                    sb.append(c);
1762                }
1763            }
1764        }
1765    }
1766
1767    private void addToken(final String token, final Object object) {
1768        if (isEndOfListToken(token) && isList()) {
1769            add(object);
1770        } else {
1771            add(token, object);
1772        }
1773    }
1774
1775    private boolean isEndOfListToken(final String token) {
1776        return token.equals("-");
1777    }
1778
1779    private boolean isIndexToken(final String token) {
1780        if (token.isEmpty()) {
1781            return false;
1782        } else {
1783            for (int i = 0; i < token.length(); i++) {
1784                final char c = token.charAt(i);
1785                if (!Character.isDigit(c)) {
1786                    return false;
1787                }
1788            }
1789            return true;
1790        }
1791    }
1792
1793    private JsonValue navigateToParentOf(final JsonPointer pointer) {
1794        JsonValue jv = this;
1795        final int size = pointer.size();
1796        for (int n = 0; n < size - 1; n++) {
1797            jv = jv.get(pointer.get(n));
1798            if (jv.isNull()) {
1799                break;
1800            }
1801        }
1802        return jv;
1803    }
1804
1805    private JsonValue navigateToParentOfPermissive(final JsonPointer pointer) {
1806        JsonValue jv = this;
1807        final int size = pointer.size();
1808        for (int n = 0; n < size - 1; n++) {
1809            final String token = pointer.get(n);
1810            final JsonValue next = jv.get(token);
1811            if (next.isNotNull()) {
1812                jv = next;
1813            } else if (isIndexToken(token)) {
1814                throw new JsonValueException(this, "Expecting a value");
1815            } else {
1816                // Create the field based on the type of the next token.
1817                final String nextToken = pointer.get(n + 1);
1818                if (isEndOfListToken(nextToken)) {
1819                    jv.add(token, new ArrayList<>());
1820                    jv = jv.get(token);
1821                } else if (isIndexToken(nextToken)) {
1822                    throw new JsonValueException(this, "Expecting a value");
1823                } else {
1824                    jv.add(token, new LinkedHashMap<>());
1825                    jv = jv.get(token);
1826                }
1827            }
1828        }
1829        return jv;
1830    }
1831
1832    private void putToken(final String token, final Object object) {
1833        if (isEndOfListToken(token) && isList()) {
1834            add(object);
1835        } else {
1836            put(token, object);
1837        }
1838    }
1839}