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-2015 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 * Encodes a reference token into a string value suitable to expressing in a JSON 088 * pointer string value. 089 * 090 * @param value the reference token value to be encoded. 091 * @return the encode reference token value. 092 */ 093 private String encode(String value) { 094 try { 095 return new URI(null, null, null, null, value).toASCIIString().substring(1).replaceAll("/", "%2F"); 096 } catch (URISyntaxException use) { // shouldn't happen 097 throw new IllegalStateException(use.getMessage()); 098 } 099 } 100 101 /** 102 * Decodes a reference token into a string value that the pointer maintains. 103 * 104 * @param value the reference token value to decode. 105 * @return the decoded reference token value. 106 * @throws JsonException if the reference token value is malformed. 107 */ 108 private String decode(String value) { 109 try { 110 return new URI("#" + value).getFragment(); 111 } catch (URISyntaxException use) { 112 throw new JsonException(use.getMessage()); 113 } 114 } 115 116 /** 117 * Returns the number of reference tokens in the pointer. 118 * 119 * @return the number of reference tokens in the pointer. 120 */ 121 public int size() { 122 return tokens.length; 123 } 124 125 /** 126 * Returns the reference token at the specified position. 127 * 128 * @param index the index of the reference token to return. 129 * @return the reference token at the specified position. 130 * @throws IndexOutOfBoundsException if the index is out of range. 131 */ 132 public String get(int index) { 133 if (index < 0 || index >= tokens.length) { 134 throw new IndexOutOfBoundsException(); 135 } 136 return tokens[index]; 137 } 138 139 /** 140 * Returns a newly allocated array of strings, containing the pointer's reference tokens. 141 * No references to the array are maintained by the pointer. Hence, the caller is free to 142 * modify it. 143 * 144 * @return a newly allocated array of strings, containing the pointer's reference tokens. 145 */ 146 public String[] toArray() { 147 return Arrays.copyOf(tokens, tokens.length); 148 } 149 150 /** 151 * Returns a pointer to the parent of the JSON value identified by this JSON pointer, 152 * or {@code null} if the pointer has no parent JSON value (i.e. references document root). 153 * 154 * @return a pointer to the parent of of this JSON pointer. Can be null. 155 */ 156 public JsonPointer parent() { 157 JsonPointer parent = null; 158 if (this.tokens.length > 0) { 159 parent = new JsonPointer(); 160 parent.tokens = Arrays.copyOf(this.tokens, this.tokens.length - 1); 161 } 162 return parent; 163 } 164 165 /** 166 * Returns a pointer containing all but the first reference token contained 167 * in this pointer, or {@code /} if this pointer contains less than 2 168 * reference tokens. 169 * <p> 170 * This method yields the following results: <blockquote> 171 * <table cellpadding=1 cellspacing=0 summary="Examples illustrating usage of relativePointer"> 172 * <tr> 173 * <th>Input</th> 174 * <th>Output</th> 175 * </tr> 176 * <tr> 177 * <td align=left>/</td> 178 * <td align=left><tt>/</tt></td> 179 * </tr> 180 * <tr> 181 * <td align=left>/a</td> 182 * <td align=left><tt>/</tt></td> 183 * </tr> 184 * <tr> 185 * <td align=left>/a/b</td> 186 * <td align=left>/b</td> 187 * </tr> 188 * <tr> 189 * <td align=left>/a/b/c</td> 190 * <td align=left>/b/c</td> 191 * </tr> 192 * </table> 193 * </blockquote> 194 * 195 * @return A pointer containing all but the first reference token contained 196 * in this pointer. 197 */ 198 public JsonPointer relativePointer() { 199 return tokens.length > 0 ? relativePointer(tokens.length - 1) : this; 200 } 201 202 /** 203 * Returns a pointer containing the last {@code sz} reference tokens 204 * contained in this pointer. 205 * <p> 206 * This method yields the following results: <blockquote> 207 * <table cellpadding=1 cellspacing=0 summary="Examples illustrating usage of relativePointer"> 208 * <tr> 209 * <th>Input</th> 210 * <th>sz</th> 211 * <th>Output</th> 212 * </tr> 213 * <tr> 214 * <td align=left>/a/b/c</td> 215 * <td align=center>0</td> 216 * <td align=left>/</td> 217 * </tr> 218 * <tr> 219 * <td align=left>/a/b/c</td> 220 * <td align=center>1</td> 221 * <td align=left>/c</td> 222 * </tr> 223 * <tr> 224 * <td align=left>/a/b/c</td> 225 * <td align=center>2</td> 226 * <td align=left>/b/c</td> 227 * </tr> 228 * <tr> 229 * <td align=left>/a/b/c</td> 230 * <td align=center>3</td> 231 * <td align=left>/a/b/c</td> 232 * </tr> 233 * </table> 234 * </blockquote> 235 * 236 * @param sz 237 * The number of trailing reference tokens to retain. 238 * @return A pointer containing the last {@code sz} reference tokens 239 * contained in this pointer. 240 * @throws IndexOutOfBoundsException 241 * If {@code sz} is negative or greater than {@code size()}. 242 */ 243 public JsonPointer relativePointer(int sz) { 244 int length = tokens.length; 245 if (sz < 0 || sz > length) { 246 throw new IndexOutOfBoundsException(); 247 } else if (sz == length) { 248 return this; 249 } else if (sz == 0) { 250 return new JsonPointer(); 251 } else { 252 JsonPointer relativePointer = new JsonPointer(); 253 relativePointer.tokens = Arrays.copyOfRange(tokens, length - sz, length); 254 return relativePointer; 255 } 256 } 257 258 /** 259 * Returns the last (leaf) reference token of the JSON pointer, or {@code null} if the 260 * pointer contains no reference tokens (i.e. references document root). 261 * 262 * @return the last (leaf) reference token of the JSON pointer if it exists, {@code null} otherwise 263 */ 264 public String leaf() { 265 return tokens.length > 0 ? tokens[tokens.length - 1] : null; 266 } 267 268 /** 269 * Returns a new JSON pointer, which identifies a specified child member of the 270 * object identified by this pointer. 271 * 272 * @param child the name of the child member to identify. 273 * @return the child JSON pointer. 274 * @throws NullPointerException if {@code child} is {@code null}. 275 */ 276 public JsonPointer child(String child) { 277 if (child == null) { 278 throw new NullPointerException(); 279 } 280 JsonPointer pointer = new JsonPointer(); 281 pointer.tokens = Arrays.copyOf(this.tokens, this.tokens.length + 1); 282 pointer.tokens[pointer.tokens.length - 1] = child; 283 return pointer; 284 } 285 286 /** 287 * Returns a new JSON pointer, which identifies a specified child element of the 288 * array identified by this pointer. 289 * 290 * @param child the index of the child element to identify. 291 * @return the child JSON pointer. 292 * @throws IndexOutOfBoundsException if {@code child} is less than zero. 293 */ 294 public JsonPointer child(int child) { 295 if (child < 0) { 296 throw new IndexOutOfBoundsException(); 297 } 298 return child(Integer.toString(child)); 299 } 300 301 /** 302 * Returns {@code true} if this pointer identifies the root value of a JSON 303 * structure. More specifically, it returns {@code true} if this pointer 304 * does not contain any reference tokens (i.e. {@code size() == 0}). 305 * 306 * @return {@code true} if this pointer identifies the root value of a JSON 307 * structure. 308 */ 309 public boolean isEmpty() { 310 return size() == 0; 311 } 312 313 /** 314 * Returns an iterator over the pointer's reference tokens. 315 * 316 * @return an iterator over the pointer's reference tokens. 317 */ 318 @Override 319 public Iterator<String> iterator() { 320 return new Iterator<String>() { 321 int cursor = 0; 322 @Override 323 public boolean hasNext() { 324 return cursor < tokens.length; 325 } 326 @Override 327 public String next() { 328 if (cursor >= tokens.length) { 329 throw new NoSuchElementException(); 330 } 331 return tokens[cursor++]; 332 } 333 @Override 334 public void remove() { 335 throw new UnsupportedOperationException(); 336 } 337 }; 338 } 339 340 /** 341 * Returns the JSON pointer string value. 342 * 343 * @return the JSON pointer string value. 344 */ 345 @Override 346 public String toString() { 347 final StringBuilder sb = new StringBuilder(); 348 for (String token : tokens) { 349 sb.append('/').append(encode(token)); 350 } 351 if (sb.length() == 0) { 352 sb.append('/'); 353 } 354 return sb.toString(); 355 } 356 357 /** 358 * Compares the specified object with this pointer for equality. Returns {@code true} if 359 * and only if the specified object is also a JSON pointer, both pointers have the same 360 * size, and all corresponding pairs of reference tokens in the two pointers are equal. 361 * 362 * @param o the object to be compared for equality with this pointer. 363 * @return {@code true} if the specified object is equal to this pointer. 364 */ 365 @Override 366 public boolean equals(Object o) { 367 return o instanceof JsonPointer 368 && ((JsonPointer) o).size() == size() 369 && Arrays.equals(tokens, ((JsonPointer) o).tokens); 370 } 371 372 /** 373 * Returns the hash code value for this pointer. 374 * 375 * @return the hash code value for this pointer. 376 */ 377 @Override 378 public int hashCode() { 379 return Arrays.hashCode(tokens); 380 } 381}