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