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