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 2011-2016 ForgeRock AS.
015 */
016
017package org.forgerock.json;
018
019import java.net.URI;
020import java.net.URISyntaxException;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Iterator;
024import java.util.NoSuchElementException;
025
026/**
027 * Identifies a specific value within a JSON structure. Conforms with
028 * <a href="http://tools.ietf.org/html/draft-pbryan-zyp-json-pointer-02">draft-pbryan-zip-json-pointer-02</a>.
029 */
030public class JsonPointer implements Iterable<String> {
031
032    /** The reference tokens that make-up the JSON pointer. */
033    private String[] tokens = new String[0];
034
035    /**
036     * Constructs a JSON pointer, identifying the root value of a JSON structure.
037     */
038    public JsonPointer() {
039        // empty tokens represents pointer to root value
040    }
041
042    /**
043     * Constructs a JSON pointer, identifying the specified pointer value.
044     *
045     * @param pointer a string containing the JSON pointer of the value to identify.
046     * @throws JsonException if the pointer is malformed.
047     */
048    public JsonPointer(String pointer) {
049        String[] split = pointer.split("/", -1);
050        int length = split.length;
051        ArrayList<String> list = new ArrayList<>(length);
052        for (int n = 0; n < length; n++) {
053            if (n == 0 && split[n].length() == 0) {
054                continue; // leading slash ignored
055            } else if (n == length - 1 && split[n].length() == 0) {
056                continue; // trailing slash ignored
057            } else {
058                list.add(decode(split[n]));
059            }
060        }
061        tokens = list.toArray(tokens);
062    }
063
064    /**
065     * Constructs a JSON pointer from an array of reference tokens.
066     *
067     * @param tokens an array of string reference tokens.
068     */
069    public JsonPointer(String... tokens) {
070        this.tokens = Arrays.copyOf(tokens, tokens.length);
071    }
072
073    /**
074     * Constructs a JSON pointer from an iterable collection of reference tokens.
075     *
076     * @param iterable an iterable collection of reference tokens.
077     */
078    public JsonPointer(Iterable<String> iterable) {
079        ArrayList<String> list = new ArrayList<>();
080        for (String element : iterable) {
081            list.add(element);
082        }
083        tokens = list.toArray(tokens);
084    }
085
086    /**
087     * Constructs a JSON pointer, identifying the specified pointer value.
088     *
089     * @param pointer a string containing the JSON pointer of the value to identify.
090     * @return The new JSON pointer
091     * @throws JsonException if the pointer is malformed.
092     */
093    public static JsonPointer ptr(final String pointer) {
094        return new JsonPointer(pointer);
095    }
096
097    /**
098     * Constructs a JSON pointer from an array of reference tokens.
099     *
100     * @param tokens an array of string reference tokens.
101     * @return The new json pointer
102     */
103    public static JsonPointer ptr(final String... tokens) {
104        return new JsonPointer(tokens);
105    }
106
107    /**
108     * Constructs a JSON pointer from an iterable collection of reference tokens.
109     *
110     * @param iterable an iterable collection of reference tokens.
111     * @return The new json pointer
112     */
113    public static JsonPointer ptr(final Iterable<String> iterable) {
114        return new JsonPointer(iterable);
115    }
116
117    /**
118     * Encodes a reference token into a string value suitable to expressing in a JSON
119     * pointer string value.
120     *
121     * @param value the reference token value to be encoded.
122     * @return the encode reference token value.
123     */
124    private String encode(String value) {
125        try {
126            return new URI(null, null, null, null, value).toASCIIString().substring(1).replaceAll("/", "%2F");
127        } catch (URISyntaxException use) { // shouldn't happen
128            throw new IllegalStateException(use.getMessage());
129        }
130    }
131
132    /**
133     * Decodes a reference token into a string value that the pointer maintains.
134     *
135     * @param value the reference token value to decode.
136     * @return the decoded reference token value.
137     * @throws JsonException if the reference token value is malformed.
138     */
139    private String decode(String value) {
140        try {
141            return new URI("#" + value).getFragment();
142        } catch (URISyntaxException use) {
143            throw new JsonException(use.getMessage());
144        }
145    }
146
147    /**
148     * Returns the number of reference tokens in the pointer.
149     *
150     * @return the number of reference tokens in the pointer.
151     */
152    public int size() {
153        return tokens.length;
154    }
155
156    /**
157     * Returns the reference token at the specified position.
158     *
159     * @param index the index of the reference token to return.
160     * @return the reference token at the specified position.
161     * @throws IndexOutOfBoundsException if the index is out of range.
162     */
163    public String get(int index) {
164        if (index < 0 || index >= tokens.length) {
165            throw new IndexOutOfBoundsException();
166        }
167        return tokens[index];
168    }
169
170    /**
171     * Returns a newly allocated array of strings, containing the pointer's reference tokens.
172     * No references to the array are maintained by the pointer. Hence, the caller is free to
173     * modify it.
174     *
175     * @return a newly allocated array of strings, containing the pointer's reference tokens.
176     */
177    public String[] toArray() {
178        return Arrays.copyOf(tokens, tokens.length);
179    }
180
181    /**
182     * Returns a pointer to the parent of the JSON value identified by this JSON pointer,
183     * or {@code null} if the pointer has no parent JSON value (i.e. references document root).
184     *
185     * @return a pointer to the parent of of this JSON pointer. Can be null.
186     */
187    public JsonPointer parent() {
188        JsonPointer parent = null;
189        if (this.tokens.length > 0) {
190            parent = new JsonPointer();
191            parent.tokens = Arrays.copyOf(this.tokens, this.tokens.length - 1);
192        }
193        return parent;
194    }
195
196    /**
197     * Returns a pointer containing all but the first reference token contained
198     * in this pointer, or {@code /} if this pointer contains less than 2
199     * reference tokens.
200     * <p>
201     * This method yields the following results: <blockquote>
202     * <table cellpadding=1 cellspacing=0 summary="Examples illustrating usage of relativePointer">
203     * <tr>
204     * <th>Input</th>
205     * <th>Output</th>
206     * </tr>
207     * <tr>
208     * <td align=left>/</td>
209     * <td align=left><tt>/</tt></td>
210     * </tr>
211     * <tr>
212     * <td align=left>/a</td>
213     * <td align=left><tt>/</tt></td>
214     * </tr>
215     * <tr>
216     * <td align=left>/a/b</td>
217     * <td align=left>/b</td>
218     * </tr>
219     * <tr>
220     * <td align=left>/a/b/c</td>
221     * <td align=left>/b/c</td>
222     * </tr>
223     * </table>
224     * </blockquote>
225     *
226     * @return A pointer containing all but the first reference token contained
227     *         in this pointer.
228     */
229    public JsonPointer relativePointer() {
230        return tokens.length > 0 ? relativePointer(tokens.length - 1) : this;
231    }
232
233    /**
234     * Returns a pointer containing the last {@code sz} reference tokens
235     * contained in this pointer.
236     * <p>
237     * This method yields the following results: <blockquote>
238     * <table cellpadding=1 cellspacing=0 summary="Examples illustrating usage of relativePointer">
239     * <tr>
240     * <th>Input</th>
241     * <th>sz</th>
242     * <th>Output</th>
243     * </tr>
244     * <tr>
245     * <td align=left>/a/b/c</td>
246     * <td align=center>0</td>
247     * <td align=left>/</td>
248     * </tr>
249     * <tr>
250     * <td align=left>/a/b/c</td>
251     * <td align=center>1</td>
252     * <td align=left>/c</td>
253     * </tr>
254     * <tr>
255     * <td align=left>/a/b/c</td>
256     * <td align=center>2</td>
257     * <td align=left>/b/c</td>
258     * </tr>
259     * <tr>
260     * <td align=left>/a/b/c</td>
261     * <td align=center>3</td>
262     * <td align=left>/a/b/c</td>
263     * </tr>
264     * </table>
265     * </blockquote>
266     *
267     * @param sz
268     *            The number of trailing reference tokens to retain.
269     * @return A pointer containing the last {@code sz} reference tokens
270     *         contained in this pointer.
271     * @throws IndexOutOfBoundsException
272     *             If {@code sz} is negative or greater than {@code size()}.
273     */
274    public JsonPointer relativePointer(int sz) {
275        int length = tokens.length;
276        if (sz < 0 || sz > length) {
277            throw new IndexOutOfBoundsException();
278        } else if (sz == length) {
279            return this;
280        } else if (sz == 0) {
281            return new JsonPointer();
282        } else {
283            JsonPointer relativePointer = new JsonPointer();
284            relativePointer.tokens = Arrays.copyOfRange(tokens, length - sz, length);
285            return relativePointer;
286        }
287    }
288
289    /**
290     * Returns the last (leaf) reference token of the JSON pointer, or {@code null} if the
291     * pointer contains no reference tokens (i.e. references document root).
292     *
293     * @return the last (leaf) reference token of the JSON pointer if it exists, {@code null} otherwise
294     */
295    public String leaf() {
296        return tokens.length > 0 ? tokens[tokens.length - 1] : null;
297    }
298
299    /**
300     * Returns a new JSON pointer, which identifies a specified child member of the
301     * object identified by this pointer.
302     *
303     * @param child the name of the child member to identify.
304     * @return the child JSON pointer.
305     * @throws NullPointerException if {@code child} is {@code null}.
306     */
307    public JsonPointer child(String child) {
308        if (child == null) {
309            throw new NullPointerException();
310        }
311        JsonPointer pointer = new JsonPointer();
312        pointer.tokens = Arrays.copyOf(this.tokens, this.tokens.length + 1);
313        pointer.tokens[pointer.tokens.length - 1] = child;
314        return pointer;
315    }
316
317    /**
318     * Returns a new JSON pointer, which identifies a specified child element of the
319     * array identified by this pointer.
320     *
321     * @param child the index of the child element to identify.
322     * @return the child JSON pointer.
323     * @throws IndexOutOfBoundsException if {@code child} is less than zero.
324     */
325    public JsonPointer child(int child) {
326        if (child < 0) {
327            throw new IndexOutOfBoundsException();
328        }
329        return child(Integer.toString(child));
330    }
331
332    /**
333     * Returns {@code true} if this pointer identifies the root value of a JSON
334     * structure. More specifically, it returns {@code true} if this pointer
335     * does not contain any reference tokens (i.e. {@code size() == 0}).
336     *
337     * @return {@code true} if this pointer identifies the root value of a JSON
338     *         structure.
339     */
340    public boolean isEmpty() {
341        return size() == 0;
342    }
343
344    /**
345     * Returns an iterator over the pointer's reference tokens.
346     *
347     * @return an iterator over the pointer's reference tokens.
348     */
349    @Override
350    public Iterator<String> iterator() {
351        return new Iterator<String>() {
352            int cursor = 0;
353            @Override
354            public boolean hasNext() {
355                return cursor < tokens.length;
356            }
357            @Override
358            public String next() {
359                if (cursor >= tokens.length) {
360                    throw new NoSuchElementException();
361                }
362                return tokens[cursor++];
363            }
364            @Override
365            public void remove() {
366                throw new UnsupportedOperationException();
367            }
368        };
369    }
370
371    /**
372     * Returns the JSON pointer string value.
373     *
374     * @return the JSON pointer string value.
375     */
376    @Override
377    public String toString() {
378        final StringBuilder sb = new StringBuilder();
379        for (String token : tokens) {
380            sb.append('/').append(encode(token));
381        }
382        if (sb.length() == 0) {
383            sb.append('/');
384        }
385        return sb.toString();
386    }
387
388    /**
389     * Compares the specified object with this pointer for equality. Returns {@code true} if
390     * and only if the specified object is also a JSON pointer, both pointers have the same
391     * size, and all corresponding pairs of reference tokens in the two pointers are equal.
392     *
393     * @param o the object to be compared for equality with this pointer.
394     * @return {@code true} if the specified object is equal to this pointer.
395     */
396    @Override
397    public boolean equals(Object o) {
398        return o instanceof JsonPointer
399                && ((JsonPointer) o).size() == size()
400                && Arrays.equals(tokens, ((JsonPointer) o).tokens);
401    }
402
403    /**
404     * Returns the hash code value for this pointer.
405     *
406     * @return the hash code value for this pointer.
407     */
408    @Override
409    public int hashCode() {
410        return Arrays.hashCode(tokens);
411    }
412}