001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.isis.viewer.restfulobjects.applib; 020 021import java.io.InputStream; 022import java.lang.reflect.Constructor; 023import java.math.BigDecimal; 024import java.math.BigInteger; 025import java.util.*; 026import java.util.Map.Entry; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029import com.google.common.base.Function; 030import com.google.common.base.Predicate; 031import com.google.common.collect.Iterables; 032import com.google.common.collect.Iterators; 033import com.google.common.collect.Lists; 034import com.google.common.collect.Maps; 035import org.codehaus.jackson.JsonNode; 036import org.codehaus.jackson.node.*; 037import org.joda.time.LocalTime; 038import org.joda.time.format.DateTimeFormatter; 039import org.joda.time.format.ISODateTimeFormat; 040import org.apache.isis.viewer.restfulobjects.applib.util.JsonNodeUtils; 041import org.apache.isis.viewer.restfulobjects.applib.util.PathNode; 042import org.apache.isis.viewer.restfulobjects.applib.util.UrlEncodingUtils; 043 044/** 045 * A wrapper around {@link JsonNode} that provides some additional helper 046 * methods. 047 */ 048public class JsonRepresentation { 049 050 private static final Pattern FORMAT_BIG_DECIMAL = Pattern.compile("big-decimal\\((\\d+),(\\d+)\\)"); 051 private static final Pattern FORMAT_BIG_INTEGER = Pattern.compile("big-integer\\((\\d+)\\)"); 052 053 public interface HasLinkToSelf { 054 public LinkRepresentation getSelf(); 055 } 056 057 public interface HasLinkToUp { 058 public LinkRepresentation getUp(); 059 } 060 061 public interface HasLinks { 062 public JsonRepresentation getLinks(); 063 } 064 065 public interface HasExtensions { 066 public JsonRepresentation getExtensions(); 067 } 068 069 private static Map<Class<?>, Function<JsonNode, ?>> REPRESENTATION_INSTANTIATORS = Maps.newHashMap(); 070 static { 071 REPRESENTATION_INSTANTIATORS.put(String.class, new Function<JsonNode, String>() { 072 @Override 073 public String apply(final JsonNode input) { 074 if (!input.isTextual()) { 075 throw new IllegalStateException("found node that is not a string " + input.toString()); 076 } 077 return input.getTextValue(); 078 } 079 }); 080 REPRESENTATION_INSTANTIATORS.put(JsonNode.class, new Function<JsonNode, JsonNode>() { 081 @Override 082 public JsonNode apply(final JsonNode input) { 083 return input; 084 } 085 }); 086 } 087 088 private static <T> Function<JsonNode, ?> representationInstantiatorFor(final Class<T> representationType) { 089 Function<JsonNode, ?> transformer = REPRESENTATION_INSTANTIATORS.get(representationType); 090 if (transformer == null) { 091 transformer = new Function<JsonNode, T>() { 092 @Override 093 public T apply(final JsonNode input) { 094 try { 095 final Constructor<T> constructor = representationType.getConstructor(JsonNode.class); 096 return constructor.newInstance(input); 097 } catch (final Exception e) { 098 throw new IllegalArgumentException("Conversions from JsonNode to " + representationType + " are not supported"); 099 } 100 } 101 102 }; 103 REPRESENTATION_INSTANTIATORS.put(representationType, transformer); 104 } 105 return transformer; 106 } 107 108 public static JsonRepresentation newMap(final String... keyValuePairs) { 109 final JsonRepresentation repr = new JsonRepresentation(new ObjectNode(JsonNodeFactory.instance)); 110 String key = null; 111 for (final String keyOrValue : keyValuePairs) { 112 if (key != null) { 113 repr.mapPut(key, keyOrValue); 114 key = null; 115 } else { 116 key = keyOrValue; 117 } 118 } 119 if (key != null) { 120 throw new IllegalArgumentException("must provide an even number of keys and values"); 121 } 122 return repr; 123 } 124 125 public static JsonRepresentation newArray() { 126 return newArray(0); 127 } 128 129 public static JsonRepresentation newArray(final int initialSize) { 130 final ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance); 131 for (int i = 0; i < initialSize; i++) { 132 arrayNode.addNull(); 133 } 134 return new JsonRepresentation(arrayNode); 135 } 136 137 protected final JsonNode jsonNode; 138 139 public JsonRepresentation(final JsonNode jsonNode) { 140 this.jsonNode = jsonNode; 141 } 142 143 public JsonNode asJsonNode() { 144 return jsonNode; 145 } 146 147 public int size() { 148 if (!isMap() && !isArray()) { 149 throw new IllegalStateException("not a map or an array"); 150 } 151 return jsonNode.size(); 152 } 153 154 /** 155 * Node is a value (nb: could be {@link #isNull() null}). 156 */ 157 public boolean isValue() { 158 return jsonNode.isValueNode(); 159 } 160 161 // /////////////////////////////////////////////////////////////////////// 162 // getRepresentation 163 // /////////////////////////////////////////////////////////////////////// 164 165 public JsonRepresentation getRepresentation(final String pathTemplate, final Object... args) { 166 final String pathStr = String.format(pathTemplate, args); 167 168 final JsonNode node = getNode(pathStr); 169 170 if (representsNull(node)) { 171 return null; 172 } 173 174 return new JsonRepresentation(node); 175 } 176 177 // /////////////////////////////////////////////////////////////////////// 178 // isArray, getArray, asArray 179 // /////////////////////////////////////////////////////////////////////// 180 181 public boolean isArray(final String path) { 182 return isArray(getNode(path)); 183 } 184 185 public boolean isArray() { 186 return isArray(asJsonNode()); 187 } 188 189 private boolean isArray(final JsonNode node) { 190 return !representsNull(node) && node.isArray(); 191 } 192 193 public JsonRepresentation getArray(final String path) { 194 return getArray(path, getNode(path)); 195 } 196 197 public JsonRepresentation asArray() { 198 return getArray(null, asJsonNode()); 199 } 200 201 private JsonRepresentation getArray(final String path, final JsonNode node) { 202 if (representsNull(node)) { 203 return null; 204 } 205 206 if (!isArray(node)) { 207 throw new IllegalArgumentException(formatExMsg(path, "is not an array")); 208 } 209 return new JsonRepresentation(node); 210 } 211 212 public JsonRepresentation getArrayEnsured(final String path) { 213 return getArrayEnsured(path, getNode(path)); 214 } 215 216 private JsonRepresentation getArrayEnsured(final String path, final JsonNode node) { 217 if (representsNull(node)) { 218 return null; 219 } 220 return new JsonRepresentation(node).ensureArray(); 221 } 222 223 // /////////////////////////////////////////////////////////////////////// 224 // isMap, getMap, asMap 225 // /////////////////////////////////////////////////////////////////////// 226 227 public boolean isMap(final String path) { 228 return isMap(getNode(path)); 229 } 230 231 public boolean isMap() { 232 return isMap(asJsonNode()); 233 } 234 235 private boolean isMap(final JsonNode node) { 236 return !representsNull(node) && !node.isArray() && !node.isValueNode(); 237 } 238 239 public JsonRepresentation getMap(final String path) { 240 return getMap(path, getNode(path)); 241 } 242 243 public JsonRepresentation asMap() { 244 return getMap(null, asJsonNode()); 245 } 246 247 private JsonRepresentation getMap(final String path, final JsonNode node) { 248 if (representsNull(node)) { 249 return null; 250 } 251 if (isArray(node) || node.isValueNode()) { 252 throw new IllegalArgumentException(formatExMsg(path, "is not a map")); 253 } 254 return new JsonRepresentation(node); 255 } 256 257 // /////////////////////////////////////////////////////////////////////// 258 // isNumber 259 // /////////////////////////////////////////////////////////////////////// 260 261 public boolean isNumber(final String path) { 262 return isNumber(getNode(path)); 263 } 264 265 public boolean isNumber() { 266 return isNumber(asJsonNode()); 267 } 268 269 private boolean isNumber(final JsonNode node) { 270 return !representsNull(node) && node.isValueNode() && node.isNumber(); 271 } 272 273 public Number asNumber() { 274 return getNumber(null, asJsonNode()); 275 } 276 277 private Number getNumber(final String path, final JsonNode node) { 278 if (representsNull(node)) { 279 return null; 280 } 281 checkValue(path, node, "a number"); 282 if (!node.isNumber()) { 283 throw new IllegalArgumentException(formatExMsg(path, "is not a number")); 284 } 285 return node.getNumberValue(); 286 } 287 288 289 // /////////////////////////////////////////////////////////////////////// 290 // isIntegralNumber, getIntegralNumber, asIntegralNumber 291 // /////////////////////////////////////////////////////////////////////// 292 293 /** 294 * Is a long, an int or a {@link BigInteger}. 295 */ 296 public boolean isIntegralNumber(final String path) { 297 return isIntegralNumber(getNode(path)); 298 } 299 300 /** 301 * Is a long, an int or a {@link BigInteger}. 302 */ 303 public boolean isIntegralNumber() { 304 return isIntegralNumber(asJsonNode()); 305 } 306 307 private boolean isIntegralNumber(final JsonNode node) { 308 return !representsNull(node) && node.isValueNode() && node.isIntegralNumber(); 309 } 310 311 312 // /////////////////////////////////////////////////////////////////////// 313 // getDate, asDate 314 // /////////////////////////////////////////////////////////////////////// 315 316 public final static DateTimeFormatter yyyyMMdd = ISODateTimeFormat.date().withZoneUTC(); 317 318 public java.util.Date getDate(final String path) { 319 return getDate(path, getNode(path)); 320 } 321 322 public java.util.Date asDate() { 323 return getDate(null, asJsonNode()); 324 } 325 326 private java.util.Date getDate(final String path, final JsonNode node) { 327 if (representsNull(node)) { 328 return null; 329 } 330 checkValue(path, node, "a date"); 331 if (!node.isTextual()) { 332 throw new IllegalArgumentException(formatExMsg(path, "is not a date")); 333 } 334 final String textValue = node.getTextValue(); 335 return new java.util.Date(yyyyMMdd.parseMillis(textValue)); 336 } 337 338 // /////////////////////////////////////////////////////////////////////// 339 // getDateTime, asDateTime 340 // /////////////////////////////////////////////////////////////////////// 341 342 public final static DateTimeFormatter yyyyMMddTHHmmssZ = ISODateTimeFormat.dateTimeNoMillis().withZoneUTC(); 343 344 public java.util.Date getDateTime(final String path) { 345 return getDateTime(path, getNode(path)); 346 } 347 348 public java.util.Date asDateTime() { 349 return getDateTime(null, asJsonNode()); 350 } 351 352 private java.util.Date getDateTime(final String path, final JsonNode node) { 353 if (representsNull(node)) { 354 return null; 355 } 356 checkValue(path, node, "a date-time"); 357 if (!node.isTextual()) { 358 throw new IllegalArgumentException(formatExMsg(path, "is not a date-time")); 359 } 360 final String textValue = node.getTextValue(); 361 return new java.util.Date(yyyyMMddTHHmmssZ.parseMillis(textValue)); 362 } 363 364 // /////////////////////////////////////////////////////////////////////// 365 // getTime, asTime 366 // /////////////////////////////////////////////////////////////////////// 367 368 public final static DateTimeFormatter _HHmmss = ISODateTimeFormat.timeNoMillis().withZoneUTC(); 369 370 public java.util.Date getTime(final String path) { 371 return getTime(path, getNode(path)); 372 } 373 374 public java.util.Date asTime() { 375 return getTime(null, asJsonNode()); 376 } 377 378 private java.util.Date getTime(final String path, final JsonNode node) { 379 if (representsNull(node)) { 380 return null; 381 } 382 checkValue(path, node, "a time"); 383 if (!node.isTextual()) { 384 throw new IllegalArgumentException(formatExMsg(path, "is not a time")); 385 } 386 final String textValue = node.getTextValue(); 387 final LocalTime localTime = _HHmmss.parseLocalTime(textValue + "Z"); 388 return new java.util.Date(localTime.getMillisOfDay()); 389 } 390 391 // /////////////////////////////////////////////////////////////////////// 392 // isBoolean, getBoolean, asBoolean 393 // /////////////////////////////////////////////////////////////////////// 394 395 public boolean isBoolean(final String path) { 396 return isBoolean(getNode(path)); 397 } 398 399 public boolean isBoolean() { 400 return isBoolean(asJsonNode()); 401 } 402 403 private boolean isBoolean(final JsonNode node) { 404 return !representsNull(node) && node.isValueNode() && node.isBoolean(); 405 } 406 407 /** 408 * Use {@link #isBoolean(String)} to check first, if required. 409 */ 410 public Boolean getBoolean(final String path) { 411 return getBoolean(path, getNode(path)); 412 } 413 414 /** 415 * Use {@link #isBoolean()} to check first, if required. 416 */ 417 public Boolean asBoolean() { 418 return getBoolean(null, asJsonNode()); 419 } 420 421 private Boolean getBoolean(final String path, final JsonNode node) { 422 if (representsNull(node)) { 423 return null; 424 } 425 checkValue(path, node, "a boolean"); 426 if (!node.isBoolean()) { 427 throw new IllegalArgumentException(formatExMsg(path, "is not a boolean")); 428 } 429 return node.getBooleanValue(); 430 } 431 432 // /////////////////////////////////////////////////////////////////////// 433 // isByte, getByte, asByte 434 // /////////////////////////////////////////////////////////////////////// 435 436 /** 437 * Use {@link #isIntegralNumber(String)} to test if number (it is not possible to check if a byte, however). 438 */ 439 public Byte getByte(final String path) { 440 final JsonNode node = getNode(path); 441 return getByte(path, node); 442 } 443 444 /** 445 * Use {@link #isIntegralNumber()} to test if number (it is not possible to check if a byte, however). 446 */ 447 public Byte asByte() { 448 return getByte(null, asJsonNode()); 449 } 450 451 private Byte getByte(final String path, final JsonNode node) { 452 if (representsNull(node)) { 453 return null; 454 } 455 checkValue(path, node, "an byte"); 456 if (!node.isNumber()) { 457 // there is no node.isByte() 458 throw new IllegalArgumentException(formatExMsg(path, "is not a number")); 459 } 460 return node.getNumberValue().byteValue(); 461 } 462 463 // /////////////////////////////////////////////////////////////////////// 464 // getShort, asShort 465 // /////////////////////////////////////////////////////////////////////// 466 467 /** 468 * Use {@link #isIntegralNumber(String)} to check if number (it is not possible to check if a short, however). 469 */ 470 public Short getShort(final String path) { 471 final JsonNode node = getNode(path); 472 return getShort(path, node); 473 } 474 475 /** 476 * Use {@link #isIntegralNumber()} to check if number (it is not possible to check if a short, however). 477 */ 478 public Short asShort() { 479 return getShort(null, asJsonNode()); 480 } 481 482 private Short getShort(final String path, final JsonNode node) { 483 if (representsNull(node)) { 484 return null; 485 } 486 checkValue(path, node, "an short"); 487 if (!node.isNumber()) { 488 // there is no node.isShort() 489 throw new IllegalArgumentException(formatExMsg(path, "is not a number")); 490 } 491 return node.getNumberValue().shortValue(); 492 } 493 494 495 // /////////////////////////////////////////////////////////////////////// 496 // getChar, asChar 497 // /////////////////////////////////////////////////////////////////////// 498 499 /** 500 * Use {@link #isString(String)} to check if string (it is not possible to check if a character, however). 501 */ 502 public Character getChar(final String path) { 503 final JsonNode node = getNode(path); 504 return getChar(path, node); 505 } 506 507 /** 508 * Use {@link #isString()} to check if string (it is not possible to check if a character, however). 509 */ 510 public Character asChar() { 511 return getChar(null, asJsonNode()); 512 } 513 514 private Character getChar(final String path, final JsonNode node) { 515 if (representsNull(node)) { 516 return null; 517 } 518 checkValue(path, node, "an short"); 519 if (!node.isTextual()) { 520 throw new IllegalArgumentException(formatExMsg(path, "is not textual")); 521 } 522 final String textValue = node.getTextValue(); 523 if(textValue == null || textValue.length() == 0) { 524 return null; 525 } 526 return textValue.charAt(0); 527 } 528 529 530 // /////////////////////////////////////////////////////////////////////// 531 // isInt, getInt, asInt 532 // /////////////////////////////////////////////////////////////////////// 533 534 public boolean isInt(final String path) { 535 return isInt(getNode(path)); 536 } 537 538 public boolean isInt() { 539 return isInt(asJsonNode()); 540 } 541 542 private boolean isInt(final JsonNode node) { 543 return !representsNull(node) && node.isValueNode() && node.isInt(); 544 } 545 546 /** 547 * Use {@link #isInt(String)} to check first, if required. 548 */ 549 public Integer getInt(final String path) { 550 final JsonNode node = getNode(path); 551 return getInt(path, node); 552 } 553 554 /** 555 * Use {@link #isInt()} to check first, if required. 556 */ 557 public Integer asInt() { 558 return getInt(null, asJsonNode()); 559 } 560 561 private Integer getInt(final String path, final JsonNode node) { 562 if (representsNull(node)) { 563 return null; 564 } 565 checkValue(path, node, "an int"); 566 if (!node.isInt()) { 567 throw new IllegalArgumentException(formatExMsg(path, "is not an int")); 568 } 569 return node.getIntValue(); 570 } 571 572 573 // /////////////////////////////////////////////////////////////////////// 574 // isLong, getLong, asLong 575 // /////////////////////////////////////////////////////////////////////// 576 577 public boolean isLong(final String path) { 578 return isLong(getNode(path)); 579 } 580 581 public boolean isLong() { 582 return isLong(asJsonNode()); 583 } 584 585 private boolean isLong(final JsonNode node) { 586 return !representsNull(node) && node.isValueNode() && node.isLong(); 587 } 588 589 /** 590 * Use {@link #isLong(String)} to check first, if required. 591 */ 592 public Long getLong(final String path) { 593 final JsonNode node = getNode(path); 594 return getLong(path, node); 595 } 596 597 /** 598 * Use {@link #isLong()} to check first, if required. 599 */ 600 public Long asLong() { 601 return getLong(null, asJsonNode()); 602 } 603 604 private Long getLong(final String path, final JsonNode node) { 605 if (representsNull(node)) { 606 return null; 607 } 608 checkValue(path, node, "a long"); 609 if(node.isInt()) { 610 return Long.valueOf(node.getIntValue()); 611 } 612 if(node.isLong()) { 613 return node.getLongValue(); 614 } 615 throw new IllegalArgumentException(formatExMsg(path, "is not a long")); 616 } 617 618 // /////////////////////////////////////////////////////////////////////// 619 // getFloat, asFloat 620 // /////////////////////////////////////////////////////////////////////// 621 622 /** 623 * Use {@link #isDecimal(String)} to test if a decimal value 624 */ 625 public Float getFloat(final String path) { 626 final JsonNode node = getNode(path); 627 return getFloat(path, node); 628 } 629 630 /** 631 * Use {@link #isNumber()} to test if number (it is not possible to check if a float, however). 632 */ 633 public Float asFloat() { 634 return getFloat(null, asJsonNode()); 635 } 636 637 private Float getFloat(final String path, final JsonNode node) { 638 if (representsNull(node)) { 639 return null; 640 } 641 checkValue(path, node, "a float"); 642 if (!node.isNumber()) { 643 throw new IllegalArgumentException(formatExMsg(path, "is not a number")); 644 } 645 return node.getNumberValue().floatValue(); 646 } 647 648 649 // /////////////////////////////////////////////////////////////////////// 650 // isDecimal, isDouble, getDouble, asDouble 651 // /////////////////////////////////////////////////////////////////////// 652 653 public boolean isDecimal(final String path) { 654 return isDecimal(getNode(path)); 655 } 656 657 public boolean isDecimal() { 658 return isDecimal(asJsonNode()); 659 } 660 661 /** 662 * @deprecated - use {@link #isDecimal(String)} 663 */ 664 @Deprecated 665 public boolean isDouble(final String path) { 666 return isDecimal(path); 667 } 668 669 /** 670 * @deprecated - use {@link #isDecimal()} 671 */ 672 @Deprecated 673 public boolean isDouble() { 674 return isDecimal(); 675 } 676 677 private boolean isDecimal(final JsonNode node) { 678 return !representsNull(node) && node.isValueNode() && node.isDouble(); 679 } 680 681 /** 682 * Use {@link #isDouble(String)} to check first, if required. 683 */ 684 public Double getDouble(final String path) { 685 final JsonNode node = getNode(path); 686 return getDouble(path, node); 687 } 688 689 /** 690 * Use {@link #isDouble()} to check first, if required. 691 */ 692 public Double asDouble() { 693 return getDouble(null, asJsonNode()); 694 } 695 696 private Double getDouble(final String path, final JsonNode node) { 697 if (representsNull(node)) { 698 return null; 699 } 700 checkValue(path, node, "a double"); 701 if (!node.isDouble()) { 702 throw new IllegalArgumentException(formatExMsg(path, "is not a double")); 703 } 704 return node.getDoubleValue(); 705 } 706 707 // /////////////////////////////////////////////////////////////////////// 708 // isBigInteger, getBigInteger, asBigInteger 709 // /////////////////////////////////////////////////////////////////////// 710 711 public boolean isBigInteger(final String path) { 712 return isBigInteger(getNode(path)); 713 } 714 715 public boolean isBigInteger() { 716 return isBigInteger(asJsonNode()); 717 } 718 719 private boolean isBigInteger(final JsonNode node) { 720 return !representsNull(node) && node.isValueNode() && (node.isBigInteger() || node.isLong() || node.isInt() || node.isTextual() && parseableAsBigInteger(node.getTextValue())); 721 } 722 723 private static boolean parseableAsBigInteger(String str) { 724 try { 725 new BigInteger(str); 726 return true; 727 } catch (Exception e) { 728 return false; 729 } 730 } 731 732 /** 733 * Use {@link #isBigInteger(String)} to check first, if required. 734 */ 735 public BigInteger getBigInteger(final String path) { 736 return getBigInteger(path, (String)null); 737 } 738 739 /** 740 * Use {@link #isBigInteger(String)} to check first, if required. 741 */ 742 public BigInteger getBigInteger(final String path, final String formatRequested) { 743 final JsonNode node; 744 final String format; 745 if(formatRequested != null) { 746 node = getNode(path); 747 format = formatRequested; 748 } else { 749 final NodeAndFormat nodeAndFormat = getNodeAndFormat(path); 750 node = nodeAndFormat.node; 751 format = nodeAndFormat.format; 752 } 753 return getBigInteger(path, format, node); 754 } 755 756 /** 757 * Use {@link #isBigInteger()} to check first, if required. 758 */ 759 public BigInteger asBigInteger() { 760 return asBigInteger(null); 761 } 762 763 public BigInteger asBigInteger(final String format) { 764 return getBigInteger(null, format, asJsonNode()); 765 } 766 767 private BigInteger getBigInteger(final String path, final String format, final JsonNode node) { 768 if (representsNull(node)) { 769 return null; 770 } 771 final String requiredType = "a biginteger"; 772 if(!isBigInteger(node)) { 773 throw new IllegalArgumentException(formatExMsg(path, "is not " + requiredType)); 774 } 775 checkValue(path, node, requiredType); 776 final BigInteger bigInteger = getBigInteger(path, node); 777 if(format != null) { 778 final Matcher matcher = FORMAT_BIG_INTEGER.matcher(format); 779 if(matcher.matches()) { 780 final int precision = Integer.parseInt(matcher.group(1)); 781 final BigInteger maxAllowed = BigInteger.TEN.pow(precision); 782 if(bigInteger.compareTo(maxAllowed) > 0) { 783 throw new IllegalArgumentException(String.format("Value '%s' larger than that allowed by format '%s'", bigInteger, format)); 784 } 785 } 786 } 787 return bigInteger; 788 } 789 790 private BigInteger getBigInteger(String path, JsonNode node) { 791 if (node.isBigInteger()) { 792 return node.getBigIntegerValue(); 793 } 794 if (node.isTextual()) { 795 return new BigInteger(node.getTextValue()); 796 } 797 if (node.isLong()) { 798 return BigInteger.valueOf(node.getLongValue()); 799 } 800 if (node.isInt()) { 801 return BigInteger.valueOf(node.getIntValue()); 802 } 803 throw new IllegalArgumentException(formatExMsg(path, "is not a biginteger, is not any other integral number, is not text parseable as a biginteger")); 804 } 805 806 // /////////////////////////////////////////////////////////////////////// 807 // isBigDecimal, getBigDecimal, asBigDecimal 808 // /////////////////////////////////////////////////////////////////////// 809 810 public boolean isBigDecimal(final String path) { 811 return isBigDecimal(getNode(path)); 812 } 813 814 public boolean isBigDecimal() { 815 return isBigDecimal(asJsonNode()); 816 } 817 818 private boolean isBigDecimal(final JsonNode node) { 819 return !representsNull(node) && node.isValueNode() && (node.isBigDecimal() || node.isDouble() || node.isLong() || node.isInt() || node.isBigInteger() || node.isTextual() && parseableAsBigDecimal(node.getTextValue())); 820 } 821 822 private static boolean parseableAsBigDecimal(String str) { 823 try { 824 new BigDecimal(str); 825 return true; 826 } catch (Exception e) { 827 return false; 828 } 829 } 830 831 /** 832 * Use {@link #isBigDecimal(String)} to check first, if required. 833 */ 834 public BigDecimal getBigDecimal(final String path) { 835 return getBigDecimal(path, (String)null); 836 } 837 838 /** 839 * Use {@link #isBigDecimal(String)} to check first, if required. 840 */ 841 public BigDecimal getBigDecimal(final String path, final String formatRequested) { 842 final JsonNode node; 843 final String format; 844 if(formatRequested != null) { 845 node = getNode(path); 846 format = formatRequested; 847 } else { 848 final NodeAndFormat nodeAndFormat = getNodeAndFormat(path); 849 node = nodeAndFormat.node; 850 format = nodeAndFormat.format; 851 } 852 return getBigDecimal(path, format, node); 853 } 854 855 /** 856 * Use {@link #isBigDecimal()} to check first, if required. 857 */ 858 public BigDecimal asBigDecimal() { 859 return asBigDecimal(null); 860 } 861 862 /** 863 * Use {@link #isBigDecimal()} to check first, if required. 864 */ 865 public BigDecimal asBigDecimal(String format) { 866 return getBigDecimal(null, format, asJsonNode()); 867 } 868 869 private BigDecimal getBigDecimal(final String path, final String format, final JsonNode node) { 870 if (representsNull(node)) { 871 return null; 872 } 873 final String requiredType = "a bigdecimal"; 874 if(!isBigDecimal(node)) { 875 throw new IllegalArgumentException(formatExMsg(path, "is not " + requiredType)); 876 } 877 checkValue(path, node, requiredType); 878 final BigDecimal bigDecimal = getBigDecimal(path, node); 879 if(format != null) { 880 final Matcher matcher = FORMAT_BIG_DECIMAL.matcher(format); 881 if(matcher.matches()) { 882 final int precision = Integer.parseInt(matcher.group(1)); 883 final int scale = Integer.parseInt(matcher.group(2)); 884 final BigDecimal maxAllowed = BigDecimal.TEN.pow(precision-scale); 885 if(bigDecimal.compareTo(maxAllowed) > 0) { 886 throw new IllegalArgumentException(String.format("Value '%s' larger than that allowed by format '%s'", bigDecimal, format)); 887 } 888 return bigDecimal.setScale(scale, BigDecimal.ROUND_HALF_EVEN); 889 } 890 } 891 return bigDecimal; 892 } 893 894 private BigDecimal getBigDecimal(String path, JsonNode node) { 895 if (node.isBigDecimal()) { 896 return node.getDecimalValue(); 897 } 898 if (node.isTextual()) { 899 return new BigDecimal(node.getTextValue()); 900 } 901 if (node.isLong()) { 902 return new BigDecimal(node.getLongValue()); 903 } 904 if (node.isDouble()) { 905 // there will be rounding errors, most likely 906 return new BigDecimal(node.getDoubleValue()); 907 } 908 if (node.isBigInteger()) { 909 return new BigDecimal(node.getBigIntegerValue()); 910 } 911 if (node.isInt()) { 912 return new BigDecimal(node.getIntValue()); 913 } 914 throw new IllegalArgumentException(formatExMsg(path, "is not a bigdecimal, is not any other numeric, is not text parseable as a bigdecimal")); 915 } 916 917 918 // /////////////////////////////////////////////////////////////////////// 919 // getString, isString, asString 920 // /////////////////////////////////////////////////////////////////////// 921 922 public boolean isString(final String path) { 923 return isString(getNode(path)); 924 } 925 926 public boolean isString() { 927 return isString(asJsonNode()); 928 } 929 930 private boolean isString(final JsonNode node) { 931 return !representsNull(node) && node.isValueNode() && node.isTextual(); 932 } 933 934 /** 935 * Use {@link #isString(String)} to check first, if required. 936 */ 937 public String getString(final String path) { 938 final JsonNode node = getNode(path); 939 return getString(path, node); 940 } 941 942 /** 943 * Use {@link #isString()} to check first, if required. 944 */ 945 public String asString() { 946 return getString(null, asJsonNode()); 947 } 948 949 private String getString(final String path, final JsonNode node) { 950 if (representsNull(node)) { 951 return null; 952 } 953 checkValue(path, node, "a string"); 954 if (!node.isTextual()) { 955 throw new IllegalArgumentException(formatExMsg(path, "is not a string")); 956 } 957 return node.getTextValue(); 958 } 959 960 public String asArg() { 961 if (isValue()) { 962 return asJsonNode().getValueAsText(); 963 } else { 964 return asJsonNode().toString(); 965 } 966 } 967 968 // /////////////////////////////////////////////////////////////////////// 969 // isLink, getLink, asLink 970 // /////////////////////////////////////////////////////////////////////// 971 972 public boolean isLink() { 973 return isLink(asJsonNode()); 974 } 975 976 public boolean isLink(final String path) { 977 return isLink(getNode(path)); 978 } 979 980 public boolean isLink(final JsonNode node) { 981 if (representsNull(node) || isArray(node) || node.isValueNode()) { 982 return false; 983 } 984 985 final LinkRepresentation link = new LinkRepresentation(node); 986 if (link.getHref() == null) { 987 return false; 988 } 989 return true; 990 } 991 992 /** 993 * Use {@link #isLink(String)} to check first, if required. 994 */ 995 public LinkRepresentation getLink(final String path) { 996 return getLink(path, getNode(path)); 997 } 998 999 /** 1000 * Use {@link #isLink()} to check first, if required. 1001 */ 1002 public LinkRepresentation asLink() { 1003 return getLink(null, asJsonNode()); 1004 } 1005 1006 private LinkRepresentation getLink(final String path, final JsonNode node) { 1007 if (representsNull(node)) { 1008 return null; 1009 } 1010 1011 if (isArray(node)) { 1012 throw new IllegalArgumentException(formatExMsg(path, "is an array that does not represent a link")); 1013 } 1014 if (node.isValueNode()) { 1015 throw new IllegalArgumentException(formatExMsg(path, "is a value that does not represent a link")); 1016 } 1017 1018 final LinkRepresentation link = new LinkRepresentation(node); 1019 if (link.getHref() == null) { 1020 throw new IllegalArgumentException(formatExMsg(path, "is a map that does not fully represent a link")); 1021 } 1022 return link; 1023 } 1024 1025 // /////////////////////////////////////////////////////////////////////// 1026 // getNull, isNull 1027 // /////////////////////////////////////////////////////////////////////// 1028 1029 public boolean isNull() { 1030 return isNull(asJsonNode()); 1031 } 1032 1033 /** 1034 * Indicates that the wrapped node has <tt>null</tt> value (ie 1035 * {@link JsonRepresentation#isNull()}), or returns <tt>null</tt> if there 1036 * was no node with the provided path. 1037 */ 1038 public Boolean isNull(final String path) { 1039 return isNull(getNode(path)); 1040 } 1041 1042 private Boolean isNull(final JsonNode node) { 1043 if (node == null || node.isMissingNode()) { 1044 // not exclude if node.isNull, cos that's the point of this. 1045 return null; 1046 } 1047 return node.isNull(); 1048 } 1049 1050 /** 1051 * Either returns a {@link JsonRepresentation} that indicates that the 1052 * wrapped node has <tt>null</tt> value (ie 1053 * {@link JsonRepresentation#isNull()}), or returns <tt>null</tt> if there 1054 * was no node with the provided path. 1055 * 1056 * <p> 1057 * Use {@link #isNull(String)} to check first, if required. 1058 */ 1059 public JsonRepresentation getNull(final String path) { 1060 return getNull(path, getNode(path)); 1061 } 1062 1063 /** 1064 * Either returns a {@link JsonRepresentation} that indicates that the 1065 * wrapped node has <tt>null</tt> value (ie 1066 * {@link JsonRepresentation#isNull()}), or returns <tt>null</tt> if there 1067 * was no node with the provided path. 1068 * 1069 * <p> 1070 * Use {@link #isNull()} to check first, if required. 1071 */ 1072 public JsonRepresentation asNull() { 1073 return getNull(null, asJsonNode()); 1074 } 1075 1076 private JsonRepresentation getNull(final String path, final JsonNode node) { 1077 if (node == null || node.isMissingNode()) { 1078 // exclude if node.isNull, cos that's the point of this. 1079 return null; 1080 } 1081 checkValue(path, node, "the null value"); 1082 if (!node.isNull()) { 1083 throw new IllegalArgumentException(formatExMsg(path, "is not the null value")); 1084 } 1085 return new JsonRepresentation(node); 1086 } 1087 1088 // /////////////////////////////////////////////////////////////////////// 1089 // mapValueAsLink 1090 // /////////////////////////////////////////////////////////////////////// 1091 1092 /** 1093 * Convert a representation that contains a single node representing a link 1094 * into a {@link LinkRepresentation}. 1095 */ 1096 public LinkRepresentation mapValueAsLink() { 1097 if (asJsonNode().size() != 1) { 1098 throw new IllegalStateException("does not represent link"); 1099 } 1100 final String linkPropertyName = asJsonNode().getFieldNames().next(); 1101 return getLink(linkPropertyName); 1102 } 1103 1104 // /////////////////////////////////////////////////////////////////////// 1105 // asInputStream 1106 // /////////////////////////////////////////////////////////////////////// 1107 1108 public InputStream asInputStream() { 1109 return JsonNodeUtils.asInputStream(jsonNode); 1110 } 1111 1112 // /////////////////////////////////////////////////////////////////////// 1113 // asArrayNode, asObjectNode 1114 // /////////////////////////////////////////////////////////////////////// 1115 1116 /** 1117 * Convert underlying representation into an array. 1118 */ 1119 protected ArrayNode asArrayNode() { 1120 if (!isArray()) { 1121 throw new IllegalStateException("does not represent array"); 1122 } 1123 return (ArrayNode) asJsonNode(); 1124 } 1125 1126 /** 1127 * Convert underlying representation into an object (map). 1128 */ 1129 protected ObjectNode asObjectNode() { 1130 if (!isMap()) { 1131 throw new IllegalStateException("does not represent map"); 1132 } 1133 return (ObjectNode) asJsonNode(); 1134 } 1135 1136 // /////////////////////////////////////////////////////////////////////// 1137 // asT 1138 // /////////////////////////////////////////////////////////////////////// 1139 1140 /** 1141 * Convenience to simply "downcast". 1142 * 1143 * <p> 1144 * In fact, the method creates a new instance of the specified type, which 1145 * shares the underlying {@link #jsonNode jsonNode}. 1146 */ 1147 public <T extends JsonRepresentation> T as(final Class<T> cls) { 1148 try { 1149 final Constructor<T> constructor = cls.getConstructor(JsonNode.class); 1150 return constructor.newInstance(jsonNode); 1151 } catch (final Exception e) { 1152 throw new RuntimeException(e); 1153 } 1154 } 1155 1156 // /////////////////////////////////////////////////////////////////////// 1157 // asUrlEncoded 1158 // /////////////////////////////////////////////////////////////////////// 1159 1160 public String asUrlEncoded() { 1161 return UrlEncodingUtils.urlEncode(asJsonNode()); 1162 } 1163 1164 // /////////////////////////////////////////////////////////////////////// 1165 // mutable (array) 1166 // /////////////////////////////////////////////////////////////////////// 1167 1168 public JsonRepresentation arrayAdd(final Object value) { 1169 if (!isArray()) { 1170 throw new IllegalStateException("does not represent array"); 1171 } 1172 asArrayNode().add(new POJONode(value)); 1173 return this; 1174 } 1175 1176 public JsonRepresentation arrayAdd(final JsonRepresentation value) { 1177 if (!isArray()) { 1178 throw new IllegalStateException("does not represent array"); 1179 } 1180 asArrayNode().add(value.asJsonNode()); 1181 return this; 1182 } 1183 1184 public JsonRepresentation arrayAdd(final String value) { 1185 if (!isArray()) { 1186 throw new IllegalStateException("does not represent array"); 1187 } 1188 asArrayNode().add(value); 1189 return this; 1190 } 1191 1192 public JsonRepresentation arrayAdd(final JsonNode value) { 1193 if (!isArray()) { 1194 throw new IllegalStateException("does not represent array"); 1195 } 1196 asArrayNode().add(value); 1197 return this; 1198 } 1199 1200 public JsonRepresentation arrayAdd(final long value) { 1201 if (!isArray()) { 1202 throw new IllegalStateException("does not represent array"); 1203 } 1204 asArrayNode().add(value); 1205 return this; 1206 } 1207 1208 public JsonRepresentation arrayAdd(final int value) { 1209 if (!isArray()) { 1210 throw new IllegalStateException("does not represent array"); 1211 } 1212 asArrayNode().add(value); 1213 return this; 1214 } 1215 1216 public JsonRepresentation arrayAdd(final double value) { 1217 if (!isArray()) { 1218 throw new IllegalStateException("does not represent array"); 1219 } 1220 asArrayNode().add(value); 1221 return this; 1222 } 1223 1224 public JsonRepresentation arrayAdd(final float value) { 1225 if (!isArray()) { 1226 throw new IllegalStateException("does not represent array"); 1227 } 1228 asArrayNode().add(value); 1229 return this; 1230 } 1231 1232 public JsonRepresentation arrayAdd(final boolean value) { 1233 if (!isArray()) { 1234 throw new IllegalStateException("does not represent array"); 1235 } 1236 asArrayNode().add(value); 1237 return this; 1238 } 1239 1240 public Iterable<JsonRepresentation> arrayIterable() { 1241 return arrayIterable(JsonRepresentation.class); 1242 } 1243 1244 public <T> Iterable<T> arrayIterable(final Class<T> requiredType) { 1245 return new Iterable<T>() { 1246 @Override 1247 public Iterator<T> iterator() { 1248 return arrayIterator(requiredType); 1249 } 1250 }; 1251 } 1252 1253 public Iterator<JsonRepresentation> arrayIterator() { 1254 return arrayIterator(JsonRepresentation.class); 1255 } 1256 1257 public <T> Iterator<T> arrayIterator(final Class<T> requiredType) { 1258 ensureIsAnArrayAtLeastAsLargeAs(0); 1259 final Function<JsonNode, ?> transformer = representationInstantiatorFor(requiredType); 1260 final ArrayNode arrayNode = (ArrayNode) jsonNode; 1261 final Iterator<JsonNode> iterator = arrayNode.iterator(); 1262 // necessary to do in two steps 1263 final Function<JsonNode, T> typedTransformer = asT(transformer); 1264 return Iterators.transform(iterator, typedTransformer); 1265 } 1266 1267 @SuppressWarnings("unchecked") 1268 private static <T> Function<JsonNode, T> asT(final Function<JsonNode, ?> transformer) { 1269 return (Function<JsonNode, T>) transformer; 1270 } 1271 1272 public JsonRepresentation arrayGet(final int i) { 1273 ensureIsAnArrayAtLeastAsLargeAs(i+1); 1274 return new JsonRepresentation(jsonNode.get(i)); 1275 } 1276 1277 public JsonRepresentation arraySetElementAt(final int i, final JsonRepresentation objectRepr) { 1278 ensureIsAnArrayAtLeastAsLargeAs(i+1); 1279 if (objectRepr.isArray()) { 1280 throw new IllegalArgumentException("Representation being set cannot be an array"); 1281 } 1282 // can safely downcast because *this* representation is an array 1283 final ArrayNode arrayNode = (ArrayNode) jsonNode; 1284 arrayNode.set(i, objectRepr.asJsonNode()); 1285 return this; 1286 } 1287 1288 private void ensureIsAnArrayAtLeastAsLargeAs(final int i) { 1289 if (!jsonNode.isArray()) { 1290 throw new IllegalStateException("Is not an array"); 1291 } 1292 if (i > size()) { 1293 throw new IndexOutOfBoundsException("array has only " + size() + " elements"); 1294 } 1295 } 1296 1297 // /////////////////////////////////////////////////////////////////////// 1298 // mutable (map) 1299 // /////////////////////////////////////////////////////////////////////// 1300 1301 public boolean mapHas(final String key) { 1302 if (!isMap()) { 1303 throw new IllegalStateException("does not represent map"); 1304 } 1305 ObjectNode node = asObjectNode(); 1306 1307 final String[] paths = key.split("\\."); 1308 for (int i = 0; i < paths.length; i++) { 1309 final String path = paths[i]; 1310 final boolean has = node.has(path); 1311 if (!has) { 1312 return false; 1313 } 1314 if (i + 1 < paths.length) { 1315 // not on last 1316 final JsonNode subNode = node.get(path); 1317 if (!subNode.isObject()) { 1318 return false; 1319 } 1320 node = (ObjectNode) subNode; 1321 } 1322 } 1323 return true; 1324 } 1325 1326 public JsonRepresentation mapPut(final String key, final List<Object> value) { 1327 if (!isMap()) { 1328 throw new IllegalStateException("does not represent map"); 1329 } 1330 if (value == null) { 1331 return this; 1332 } 1333 final JsonRepresentation array = JsonRepresentation.newArray(); 1334 for (final Object v : value) { 1335 array.arrayAdd(v); 1336 } 1337 mapPut(key, array); 1338 return this; 1339 } 1340 1341 public JsonRepresentation mapPut(final String key, final Object value) { 1342 if (!isMap()) { 1343 throw new IllegalStateException("does not represent map"); 1344 } 1345 final Path path = Path.parse(key); 1346 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1347 node.put(path.getTail(), value != null ? new POJONode(value) : NullNode.getInstance()); 1348 return this; 1349 } 1350 1351 public JsonRepresentation mapPut(final String key, final JsonRepresentation value) { 1352 if (!isMap()) { 1353 throw new IllegalStateException("does not represent map"); 1354 } 1355 if (value == null) { 1356 return this; 1357 } 1358 final Path path = Path.parse(key); 1359 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1360 node.put(path.getTail(), value.asJsonNode()); 1361 return this; 1362 } 1363 1364 public JsonRepresentation mapPut(final String key, final String value) { 1365 if (!isMap()) { 1366 throw new IllegalStateException("does not represent map"); 1367 } 1368 if (value == null) { 1369 return this; 1370 } 1371 final Path path = Path.parse(key); 1372 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1373 node.put(path.getTail(), value); 1374 return this; 1375 } 1376 1377 public JsonRepresentation mapPut(final String key, final JsonNode value) { 1378 if (!isMap()) { 1379 throw new IllegalStateException("does not represent map"); 1380 } 1381 if (value == null) { 1382 return this; 1383 } 1384 final Path path = Path.parse(key); 1385 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1386 node.put(path.getTail(), value); 1387 return this; 1388 } 1389 1390 public JsonRepresentation mapPut(final String key, final byte value) { 1391 return mapPut(key, (int)value); 1392 } 1393 1394 public JsonRepresentation mapPut(final String key, final Byte value) { 1395 return value != null ? mapPut(key, value.byteValue()) : mapPut(key, (Object) value); 1396 } 1397 1398 public JsonRepresentation mapPut(final String key, final short value) { 1399 return mapPut(key, (int)value); 1400 } 1401 1402 public JsonRepresentation mapPut(final String key, final Short value) { 1403 return value != null ? mapPut(key, value.shortValue()) : mapPut(key, (Object) value); 1404 } 1405 1406 public JsonRepresentation mapPut(final String key, final int value) { 1407 if (!isMap()) { 1408 throw new IllegalStateException("does not represent map"); 1409 } 1410 final Path path = Path.parse(key); 1411 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1412 node.put(path.getTail(), value); 1413 return this; 1414 } 1415 1416 public JsonRepresentation mapPut(final String key, final Integer value) { 1417 return value != null ? mapPut(key, value.intValue()) : mapPut(key, (Object) value); 1418 } 1419 1420 public JsonRepresentation mapPut(final String key, final long value) { 1421 if (!isMap()) { 1422 throw new IllegalStateException("does not represent map"); 1423 } 1424 final Path path = Path.parse(key); 1425 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1426 node.put(path.getTail(), value); 1427 return this; 1428 } 1429 1430 public JsonRepresentation mapPut(final String key, final Long value) { 1431 return value != null ? mapPut(key, value.longValue()) : mapPut(key, (Object) value); 1432 } 1433 1434 public JsonRepresentation mapPut(final String key, final float value) { 1435 if (!isMap()) { 1436 throw new IllegalStateException("does not represent map"); 1437 } 1438 final Path path = Path.parse(key); 1439 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1440 node.put(path.getTail(), value); 1441 return this; 1442 } 1443 1444 public JsonRepresentation mapPut(final String key, final Float value) { 1445 return value != null ? mapPut(key, value.floatValue()) : mapPut(key, (Object) value); 1446 } 1447 1448 public JsonRepresentation mapPut(final String key, final double value) { 1449 if (!isMap()) { 1450 throw new IllegalStateException("does not represent map"); 1451 } 1452 final Path path = Path.parse(key); 1453 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1454 node.put(path.getTail(), value); 1455 return this; 1456 } 1457 1458 public JsonRepresentation mapPut(final String key, final Double value) { 1459 return value != null ? mapPut(key, value.doubleValue()) : mapPut(key, (Object) value); 1460 } 1461 1462 public JsonRepresentation mapPut(final String key, final boolean value) { 1463 if (!isMap()) { 1464 throw new IllegalStateException("does not represent map"); 1465 } 1466 final Path path = Path.parse(key); 1467 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1468 node.put(path.getTail(), value); 1469 return this; 1470 } 1471 1472 public JsonRepresentation mapPut(final String key, final Boolean value) { 1473 return value != null ? mapPut(key, value.booleanValue()) : mapPut(key, (Object) value); 1474 } 1475 1476 public JsonRepresentation mapPut(final String key, final char value) { 1477 return mapPut(key, ""+value); 1478 } 1479 1480 public JsonRepresentation mapPut(final String key, final Character value) { 1481 return value != null ? mapPut(key, value.charValue()) : mapPut(key, (Object) value); 1482 } 1483 1484 public JsonRepresentation mapPut(final String key, final BigInteger value) { 1485 if (!isMap()) { 1486 throw new IllegalStateException("does not represent map"); 1487 } 1488 final Path path = Path.parse(key); 1489 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1490 if (value != null) { 1491 node.put(path.getTail(), value.toString()); 1492 } else { 1493 node.put(path.getTail(), NullNode.getInstance()); 1494 } 1495 return this; 1496 } 1497 1498 public JsonRepresentation mapPut(Iterable<Entry<String, JsonRepresentation>> entries) { 1499 for (Entry<String, JsonRepresentation> entry : entries) { 1500 mapPut(entry); 1501 } 1502 return this; 1503 } 1504 1505 public JsonRepresentation mapPut(Entry<String, JsonRepresentation> entry) { 1506 mapPut(entry.getKey(), entry.getValue()); 1507 return this; 1508 } 1509 1510 public JsonRepresentation mapPut(final String key, final BigDecimal value) { 1511 if (!isMap()) { 1512 throw new IllegalStateException("does not represent map"); 1513 } 1514 final Path path = Path.parse(key); 1515 final ObjectNode node = JsonNodeUtils.walkNodeUpTo(asObjectNode(), path.getHead()); 1516 if (value != null) { 1517 node.put(path.getTail(), value.toString()); 1518 } else { 1519 node.put(path.getTail(), NullNode.getInstance()); 1520 } 1521 return this; 1522 } 1523 1524 private static class Path { 1525 private final List<String> head; 1526 private final String tail; 1527 1528 private Path(final List<String> head, final String tail) { 1529 this.head = Collections.unmodifiableList(head); 1530 this.tail = tail; 1531 } 1532 1533 public List<String> getHead() { 1534 return head; 1535 } 1536 1537 public String getTail() { 1538 return tail; 1539 } 1540 1541 public static Path parse(final String pathStr) { 1542 final List<String> keyList = Lists.newArrayList(Arrays.asList(pathStr.split("\\."))); 1543 if (keyList.size() == 0) { 1544 throw new IllegalArgumentException(String.format("Malformed path '%s'", pathStr)); 1545 } 1546 final String tail = keyList.remove(keyList.size() - 1); 1547 return new Path(keyList, tail); 1548 } 1549 } 1550 1551 public Iterable<Map.Entry<String, JsonRepresentation>> mapIterable() { 1552 ensureIsAMap(); 1553 return new Iterable<Map.Entry<String, JsonRepresentation>>() { 1554 1555 @Override 1556 public Iterator<Entry<String, JsonRepresentation>> iterator() { 1557 return mapIterator(); 1558 } 1559 }; 1560 } 1561 1562 public Iterator<Map.Entry<String, JsonRepresentation>> mapIterator() { 1563 ensureIsAMap(); 1564 return Iterators.transform(jsonNode.getFields(), MAP_ENTRY_JSON_NODE_TO_JSON_REPRESENTATION); 1565 } 1566 1567 private void ensureIsAMap() { 1568 if (!jsonNode.isObject()) { 1569 throw new IllegalStateException("Is not a map"); 1570 } 1571 } 1572 1573 private final static Function<Entry<String, JsonNode>, Entry<String, JsonRepresentation>> MAP_ENTRY_JSON_NODE_TO_JSON_REPRESENTATION = new Function<Entry<String, JsonNode>, Entry<String, JsonRepresentation>>() { 1574 1575 @Override 1576 public Entry<String, JsonRepresentation> apply(final Entry<String, JsonNode> input) { 1577 return new Map.Entry<String, JsonRepresentation>() { 1578 1579 @Override 1580 public String getKey() { 1581 return input.getKey(); 1582 } 1583 1584 @Override 1585 public JsonRepresentation getValue() { 1586 return new JsonRepresentation(input.getValue()); 1587 } 1588 1589 @Override 1590 public JsonRepresentation setValue(final JsonRepresentation value) { 1591 final JsonNode setValue = input.setValue(value.asJsonNode()); 1592 return new JsonRepresentation(setValue); 1593 } 1594 }; 1595 } 1596 }; 1597 1598 // /////////////////////////////////////////////////////////////////////// 1599 // helpers 1600 // /////////////////////////////////////////////////////////////////////// 1601 1602 /** 1603 * A reciprocal of the behaviour of the automatic dereferencing of arrays 1604 * that occurs when there is only a single instance. 1605 * 1606 * @see #toJsonNode(List) 1607 */ 1608 public JsonRepresentation ensureArray() { 1609 if (jsonNode.isArray()) { 1610 return this; 1611 } 1612 final JsonRepresentation arrayRepr = JsonRepresentation.newArray(); 1613 arrayRepr.arrayAdd(jsonNode); 1614 return arrayRepr; 1615 } 1616 1617 private JsonNode getNode(final String path) { 1618 return getNodeAndFormat(path).node; 1619 } 1620 1621 private static class NodeAndFormat { 1622 JsonNode node; 1623 String format; 1624 NodeAndFormat(JsonNode jsonNode, String format) { 1625 node = jsonNode; 1626 this.format = format; 1627 } 1628 } 1629 1630 /** 1631 * Walks the path to the specified node, and also returns the value of 'format' in the parent node if present. 1632 */ 1633 private NodeAndFormat getNodeAndFormat(final String path) { 1634 JsonNode jsonNode = this.jsonNode; 1635 final List<String> keys = PathNode.split(path); 1636 String format = null; 1637 for (final String key : keys) { 1638 final PathNode pathNode = PathNode.parse(key); 1639 if (!pathNode.getKey().isEmpty()) { 1640 // grab format (if present) before moving down the path 1641 format = getFormatValueIfAnyFrom(jsonNode); 1642 jsonNode = jsonNode.path(pathNode.getKey()); 1643 } else { 1644 // pathNode is criteria only; don't change jsonNode 1645 } 1646 if (jsonNode.isNull()) { 1647 return new NodeAndFormat(jsonNode, format); 1648 } 1649 if (!pathNode.hasCriteria()) { 1650 continue; 1651 } 1652 if (!jsonNode.isArray()) { 1653 return new NodeAndFormat(NullNode.getInstance(), format); 1654 } 1655 // grab format (if present) before moving down the path 1656 format = getFormatValueIfAnyFrom(jsonNode); 1657 jsonNode = matching(jsonNode, pathNode); 1658 if (jsonNode.isNull()) { 1659 return new NodeAndFormat(jsonNode, format); 1660 } 1661 } 1662 return new NodeAndFormat(jsonNode, format); 1663 } 1664 1665 private String getFormatValueIfAnyFrom(JsonNode jsonNode) { 1666 String format; 1667 final JsonNode formatNode = jsonNode.get("format"); 1668 format = formatNode != null && formatNode.isTextual()? formatNode.getTextValue(): null; 1669 return format; 1670 } 1671 1672 private JsonNode matching(final JsonNode jsonNode, final PathNode pathNode) { 1673 final JsonRepresentation asList = new JsonRepresentation(jsonNode); 1674 final Iterable<JsonNode> filtered = Iterables.filter(asList.arrayIterable(JsonNode.class), new Predicate<JsonNode>() { 1675 @Override 1676 public boolean apply(final JsonNode input) { 1677 return pathNode.matches(new JsonRepresentation(input)); 1678 } 1679 }); 1680 final List<JsonNode> matching = Lists.newArrayList(filtered); 1681 return toJsonNode(matching); 1682 } 1683 1684 private static JsonNode toJsonNode(final List<JsonNode> matching) { 1685 switch (matching.size()) { 1686 case 0: 1687 return NullNode.getInstance(); 1688 case 1: 1689 return matching.get(0); 1690 default: 1691 final ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance); 1692 arrayNode.addAll(matching); 1693 return arrayNode; 1694 } 1695 } 1696 1697 private static void checkValue(final String path, final JsonNode node, final String requiredType) { 1698 if (node.isValueNode()) { 1699 return; 1700 } 1701 throw new IllegalArgumentException(formatExMsg(path, "is not " + requiredType)); 1702 } 1703 1704 private static boolean representsNull(final JsonNode node) { 1705 return node == null || node.isMissingNode() || node.isNull(); 1706 } 1707 1708 private static String formatExMsg(final String pathIfAny, final String errorText) { 1709 final StringBuilder buf = new StringBuilder(); 1710 if (pathIfAny != null) { 1711 buf.append("'").append(pathIfAny).append("' "); 1712 } 1713 buf.append(errorText); 1714 return buf.toString(); 1715 } 1716 1717 1718 // /////////////////////////////////////////////////////////////////////// 1719 // equals and hashcode 1720 // /////////////////////////////////////////////////////////////////////// 1721 1722 @Override 1723 public int hashCode() { 1724 final int prime = 31; 1725 int result = 1; 1726 result = prime * result + ((jsonNode == null) ? 0 : jsonNode.hashCode()); 1727 return result; 1728 } 1729 1730 @Override 1731 public boolean equals(Object obj) { 1732 if (this == obj) 1733 return true; 1734 if (obj == null) 1735 return false; 1736 if (getClass() != obj.getClass()) 1737 return false; 1738 JsonRepresentation other = (JsonRepresentation) obj; 1739 if (jsonNode == null) { 1740 if (other.jsonNode != null) 1741 return false; 1742 } else if (!jsonNode.equals(other.jsonNode)) 1743 return false; 1744 return true; 1745 } 1746 1747 // /////////////////////////////////////////////////////////////////////// 1748 // toString 1749 // /////////////////////////////////////////////////////////////////////// 1750 1751 @Override 1752 public String toString() { 1753 return jsonNode.toString(); 1754 } 1755 1756 1757 1758} 1759