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("uid", 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("uid", "bjensen"), field("age", 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("uid", "bjensen"), 155 * field("roles", array("sales", "marketing")))); 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("uid", "bjensen"), field("age", 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}