001/* 002 * Copyright 2012 Mark Slater 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 009 */ 010 011package argo.jdom; 012 013import java.util.List; 014import java.util.Map; 015 016import static argo.jdom.JsonNodeDoesNotMatchPathElementsException.jsonNodeDoesNotMatchPathElementsException; 017 018/** 019 * <p>A node (leaf or otherwise) in a JSON document.</p> 020 * <p/> 021 * <p>Supplies methods for examining the node, and also examining and navigating the hierarchy at and below this node. 022 * Methods for navigating the hierarchy are of the form {@code getXXXValue(Object... pathElements)}.</p> 023 * <p/> 024 * <p>For example, {@link #getStringValue(Object...)} takes a series of {@code String}s and 025 * {@code Integer}s as its argument which tell it how to navigate down a hierarchy to a particular JSON string. 026 * The {@code String}s tell it to select a field with the given name from an object, and the {@code Integer}s 027 * tell it to select an element with the given index from an array.</p> If no field of that name exists, or the field 028 * exists, but it isn't a JSON string, an {@code IllegalArgumentException} is thrown.</p> 029 * <p/> 030 * <p>Methods for examining the hierarchy work on the same principal as the 031 * {@code getXXXValue(Object... pathElements)} methods, but return a {@code boolean} indicating whether 032 * or not the element at the given path exists and is of the type specified, for example, 033 * {@code getStringValue("my field")} returns {@code true} if the node has a field called 034 * "{@code my field}", and its value is a JSON string.</p> 035 */ 036public abstract class JsonNode { 037 038 // Only extensible by classes in this package 039 JsonNode() { 040 } 041 042 public abstract JsonNodeType getType(); 043 044 public abstract boolean hasText(); 045 046 /** 047 * @return the text associated with this node 048 * @throws IllegalStateException if hasText() returns false, indicating this type of node doesn't have text. 049 */ 050 public abstract String getText(); 051 052 public abstract boolean hasFields(); 053 054 /** 055 * Gets the fields associated with this node as a map of name to value. Note that JSON permits 056 * duplicated keys in an object, though in practice this is rare, and in this case, this method 057 * will return a map containing a single entry of each unique key. 058 * 059 * @return the fields associated with this node 060 * @throws IllegalStateException if hasFields() returns false, indicating this type of node doesn't support fields. 061 */ 062 public abstract Map<JsonStringNode, JsonNode> getFields(); 063 064 /** 065 * Gets the fields associated with this node as a list of {@code JsonFields}. This method allows 066 * the retrieval of all fields in an object even when the fields have duplicate keys. This method 067 * also preserves the order of the fields. 068 * 069 * @return the fields associated with this node 070 * @throws IllegalStateException if hasFields() returns false, indicating this type of node doesn't support fields. 071 */ 072 public abstract List<JsonField> getFieldList(); 073 074 public abstract boolean hasElements(); 075 076 /** 077 * @return the elements associated with this node 078 * @throws IllegalStateException if hasElements() returns false, indicating this type of node doesn't support elements. 079 */ 080 public abstract List<JsonNode> getElements(); 081 082 /** 083 * Determines whether the node at the given path exists. 084 * 085 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 086 * @return whether a JSON node exists at the path given. 087 */ 088 public boolean isNode(final Object... pathElements) { 089 return JsonNodeSelectors.anyNode(pathElements).matches(this); 090 } 091 092 /** 093 * Gets a {@code JsonNode} by navigating the hierarchy below this node. 094 * 095 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 096 * @return the {@code JsonNode} at the path given. 097 * @throws IllegalArgumentException if there is no node at the given path. 098 */ 099 public JsonNode getNode(final Object... pathElements) { 100 return wrapExceptionsFor(JsonNodeSelectors.anyNode(pathElements), this, pathElements); 101 } 102 103 /** 104 * Determines whether the node at the given path exists and is a root type - either a JSON object or JSON array. 105 * 106 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 107 * @return whether a JSON root node exists at the path given. 108 */ 109 public boolean isRootNode(final Object... pathElements) { 110 return JsonNodeSelectors.anyRootNode(pathElements).matches(this); 111 } 112 113 /** 114 * Gets a {@code JsonRootNode} by navigating the hierarchy below this node. 115 * 116 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 117 * @return the {@code JsonRootNode} at the path given. 118 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON root node. 119 */ 120 public JsonRootNode getRootNode(final Object... pathElements) { 121 return wrapExceptionsFor(JsonNodeSelectors.anyRootNode(pathElements), this, pathElements); 122 } 123 124 /** 125 * Determines whether the node at the given path exists and is a JSON boolean. 126 * 127 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 128 * @return whether a JSON boolean exists at the path given. 129 */ 130 public final boolean isBooleanValue(final Object... pathElements) { 131 return JsonNodeSelectors.aBooleanNode(pathElements).matches(this); 132 } 133 134 /** 135 * Gets a {@code Boolean} by navigating the hierarchy below this node. 136 * 137 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 138 * @return the {@code Boolean} at the path given. 139 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON boolean. 140 */ 141 public final Boolean getBooleanValue(final Object... pathElements) { 142 return wrapExceptionsFor(JsonNodeSelectors.aBooleanNode(pathElements), this, pathElements); 143 } 144 145 /** 146 * Determines whether the node at the given path exists and is a JSON boolean or a JSON null. 147 * 148 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 149 * @return whether a JSON boolean or a JSON null exists at the path given. 150 */ 151 public final boolean isNullableBooleanValue(final Object... pathElements) { 152 return JsonNodeSelectors.aNullableBooleanNode(pathElements).matches(this); 153 } 154 155 /** 156 * Gets a {@code Boolean} or {@code null} by navigating the hierarchy below this node. 157 * 158 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 159 * @return the {@code Boolean} at the path given, or null, if there is a JSON null at the path given. 160 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON boolean or a JSON null. 161 */ 162 public final Boolean getNullableBooleanValue(final Object... pathElements) { 163 return wrapExceptionsFor(JsonNodeSelectors.aNullableBooleanNode(pathElements), this, pathElements); 164 } 165 166 /** 167 * Determines whether the node at the given path exists and is a JSON string. 168 * 169 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 170 * @return whether a JSON string exists at the path given. 171 */ 172 public final boolean isStringValue(final Object... pathElements) { 173 return JsonNodeSelectors.aStringNode(pathElements).matches(this); 174 } 175 176 /** 177 * Gets a {@code String} by navigating the hierarchy below this node. 178 * 179 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 180 * @return the {@code String} at the path given 181 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON string. 182 */ 183 public final String getStringValue(final Object... pathElements) { 184 return wrapExceptionsFor(JsonNodeSelectors.aStringNode(pathElements), this, pathElements); 185 } 186 187 /** 188 * Determines whether the node at the given path exists and is a JSON string or a JSON null. 189 * 190 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 191 * @return whether a JSON string or a JSON null exists at the path given. 192 */ 193 public final boolean isNullableStringValue(final Object... pathElements) { 194 return JsonNodeSelectors.aNullableStringNode(pathElements).matches(this); 195 } 196 197 /** 198 * Gets a {@code String} or {@code null} by navigating the hierarchy below this node. 199 * 200 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 201 * @return the {@code String} at the path given, or null, if there is a JSON null at the path given. 202 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON string or a JSON null. 203 */ 204 public final String getNullableStringValue(final Object... pathElements) { 205 return wrapExceptionsFor(JsonNodeSelectors.aNullableStringNode(pathElements), this, pathElements); 206 } 207 208 /** 209 * Determines whether the node at the given path exists and is a JSON number. 210 * 211 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 212 * @return whether a JSON number exists at the path given. 213 */ 214 public final boolean isNumberValue(final Object... pathElements) { 215 return JsonNodeSelectors.aNumberNode(pathElements).matches(this); 216 } 217 218 /** 219 * Gets a numeric {@code String} by navigating the hierarchy below this node. 220 * 221 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 222 * @return the numeric {@code String} at the path given. 223 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON number. 224 */ 225 public final String getNumberValue(final Object... pathElements) { 226 return wrapExceptionsFor(JsonNodeSelectors.aNumberNode(pathElements), this, pathElements); 227 } 228 229 /** 230 * Determines whether the node at the given path exists and is a JSON number or a JSON null. 231 * 232 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 233 * @return whether a JSON number or a JSON null exists at the path given. 234 */ 235 public final boolean isNullableNumberNode(final Object... pathElements) { 236 return JsonNodeSelectors.aNullableNumberNode(pathElements).matches(this); 237 } 238 239 /** 240 * Gets a numeric {@code String} or {@code null} by navigating the hierarchy below this node. 241 * 242 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 243 * @return the numeric {@code String} at the path given, or null, if there is a JSON null at the path given. 244 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON number or a JSON null. 245 */ 246 public final String getNullableNumberValue(final Object... pathElements) { 247 return wrapExceptionsFor(JsonNodeSelectors.aNullableNumberNode(pathElements), this, pathElements); 248 } 249 250 /** 251 * Determines whether the node at the given path exists and is a JSON null. 252 * 253 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 254 * @return whether a JSON null exists at the path given. 255 */ 256 public final boolean isNullNode(final Object... pathElements) { 257 return JsonNodeSelectors.aNullNode(pathElements).matches(this); 258 } 259 260 /** 261 * Gets a {@code JsonNode} representing null by navigating the hierarchy below this node. 262 * 263 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 264 * @return a {@code JsonNode} representing null. 265 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON null. 266 */ 267 public final JsonNode getNullNode(final Object... pathElements) { 268 return wrapExceptionsFor(JsonNodeSelectors.aNullNode(pathElements), this, pathElements); 269 } 270 271 /** 272 * Determines whether the node at the given path exists and is a JSON object. 273 * 274 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 275 * @return whether a JSON object exists at the path given. 276 */ 277 public final boolean isObjectNode(final Object... pathElements) { 278 return JsonNodeSelectors.anObjectNode(pathElements).matches(this); 279 } 280 281 /** 282 * Gets a {@code Map} of {@code String} field names to {@code JsonNode}s, representing a JSON object, by navigating the hierarchy below this node. 283 * 284 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 285 * @return a {@code Map} of {@code String} field names to {@code JsonNode}s representing a JSON object. 286 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON object. 287 */ 288 public final Map<JsonStringNode, JsonNode> getObjectNode(final Object... pathElements) { 289 return wrapExceptionsFor(JsonNodeSelectors.anObjectNode(pathElements), this, pathElements); 290 } 291 292 /** 293 * Determines whether the node at the given path exists and is a JSON object or a JSON null. 294 * 295 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 296 * @return whether a JSON object or a JSON null exists at the path given. 297 */ 298 public final boolean isNullableObjectNode(final Object... pathElements) { 299 return JsonNodeSelectors.aNullableObjectNode(pathElements).matches(this); 300 } 301 302 /** 303 * Gets a {@code Map} of {@code String} field names to {@code JsonNode}s, representing a JSON object, or {@code null} by navigating the hierarchy below this node. 304 * 305 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 306 * @return a {@code Map} of {@code String} field names to {@code JsonNode}s representing a JSON object at the path given, or null, if there is a JSON null at the path given. 307 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON object or a JSON null. 308 */ 309 public final Map<JsonStringNode, JsonNode> getNullableObjectNode(final Object... pathElements) { 310 return wrapExceptionsFor(JsonNodeSelectors.aNullableObjectNode(pathElements), this, pathElements); 311 } 312 313 /** 314 * Determines whether the node at the given path exists and is a JSON array. 315 * 316 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 317 * @return whether a JSON array exists at the path given. 318 */ 319 public final boolean isArrayNode(final Object... pathElements) { 320 return JsonNodeSelectors.anArrayNode(pathElements).matches(this); 321 } 322 323 /** 324 * Gets a {@code List} of {@code JsonNode}s, representing a JSON array, by navigating the hierarchy below this node. 325 * 326 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 327 * @return a {@code List} of {@code JsonNode}s representing a JSON array. 328 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON array. 329 */ 330 public final List<JsonNode> getArrayNode(final Object... pathElements) { 331 return wrapExceptionsFor(JsonNodeSelectors.anArrayNode(pathElements), this, pathElements); 332 } 333 334 /** 335 * Determines whether the node at the given path exists and is a JSON array or a JSON null. 336 * 337 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 338 * @return whether a JSON array or a JSON null exists at the path given. 339 */ 340 public final boolean isNullableArrayNode(final Object... pathElements) { 341 return JsonNodeSelectors.aNullableArrayNode(pathElements).matches(this); 342 } 343 344 /** 345 * Gets a {@code List} of {@code JsonNode}s, representing a JSON array, or {@code null} by navigating the hierarchy below this node. 346 * 347 * @param pathElements a series of {@code String}s, representing the names of fields on objects, and {@code Integer}s, representing elements of arrays indicating how to navigate from this node. 348 * @return a {@code List} of {@code JsonNode}s representing a JSON array, or null, if there is a JSON null at the path given. 349 * @throws IllegalArgumentException if there is no node at the given path, or the node at the given path is not a JSON array or a JSON null. 350 */ 351 public final List<JsonNode> getNullableArrayNode(final Object... pathElements) { 352 return wrapExceptionsFor(JsonNodeSelectors.aNullableArrayNode(pathElements), this, pathElements); 353 } 354 355 private <T, V extends JsonNode> T wrapExceptionsFor(final JsonNodeSelector<V, T> value, final V node, final Object[] pathElements) throws JsonNodeDoesNotMatchPathElementsException { 356 try { 357 return value.getValue(node); 358 } catch (JsonNodeDoesNotMatchChainedJsonNodeSelectorException e) { 359 throw jsonNodeDoesNotMatchPathElementsException(e, pathElements, JsonNodeFactories.array(node)); 360 } 361 } 362 363}