1 /* 2 * The contents of this file are subject to the terms of the Common Development and 3 * Distribution License (the License). You may not use this file except in compliance with the 4 * License. 5 * 6 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 7 * specific language governing permission and limitations under the License. 8 * 9 * When distributing Covered Software, include this CDDL Header Notice in each file and include 10 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 11 * Header, with the fields enclosed by brackets [] replaced by your own identifying 12 * information: "Portions Copyrighted [year] [name of copyright owner]". 13 * 14 * Copyright 2011-2016 ForgeRock AS. 15 */ 16 17 package org.forgerock.json; 18 19 import java.net.URI; 20 import java.net.URISyntaxException; 21 import java.util.ArrayList; 22 import java.util.Arrays; 23 import java.util.Iterator; 24 import java.util.NoSuchElementException; 25 26 /** 27 * Identifies a specific value within a JSON structure. Conforms with 28 * <a href="http://tools.ietf.org/html/draft-pbryan-zyp-json-pointer-02">draft-pbryan-zip-json-pointer-02</a>. 29 */ 30 public class JsonPointer implements Iterable<String> { 31 32 /** The reference tokens that make-up the JSON pointer. */ 33 private String[] tokens = new String[0]; 34 35 /** 36 * Constructs a JSON pointer, identifying the root value of a JSON structure. 37 */ 38 public JsonPointer() { 39 // empty tokens represents pointer to root value 40 } 41 42 /** 43 * Constructs a JSON pointer, identifying the specified pointer value. 44 * 45 * @param pointer a string containing the JSON pointer of the value to identify. 46 * @throws JsonException if the pointer is malformed. 47 */ 48 public JsonPointer(String pointer) { 49 String[] split = pointer.split("/", -1); 50 int length = split.length; 51 ArrayList<String> list = new ArrayList<>(length); 52 for (int n = 0; n < length; n++) { 53 if (n == 0 && split[n].length() == 0) { 54 continue; // leading slash ignored 55 } else if (n == length - 1 && split[n].length() == 0) { 56 continue; // trailing slash ignored 57 } else { 58 list.add(decode(split[n])); 59 } 60 } 61 tokens = list.toArray(tokens); 62 } 63 64 /** 65 * Constructs a JSON pointer from an array of reference tokens. 66 * 67 * @param tokens an array of string reference tokens. 68 */ 69 public JsonPointer(String... tokens) { 70 this.tokens = Arrays.copyOf(tokens, tokens.length); 71 } 72 73 /** 74 * Constructs a JSON pointer from an iterable collection of reference tokens. 75 * 76 * @param iterable an iterable collection of reference tokens. 77 */ 78 public JsonPointer(Iterable<String> iterable) { 79 ArrayList<String> list = new ArrayList<>(); 80 for (String element : iterable) { 81 list.add(element); 82 } 83 tokens = list.toArray(tokens); 84 } 85 86 /** 87 * Constructs a JSON pointer, identifying the specified pointer value. 88 * 89 * @param pointer a string containing the JSON pointer of the value to identify. 90 * @return The new JSON pointer 91 * @throws JsonException if the pointer is malformed. 92 */ 93 public static JsonPointer ptr(final String pointer) { 94 return new JsonPointer(pointer); 95 } 96 97 /** 98 * Constructs a JSON pointer from an array of reference tokens. 99 * 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 }