1 package com.fasterxml.jackson.databind.node; 2 3 import java.io.IOException; 4 import java.io.OutputStream; 5 import java.math.BigDecimal; 6 import java.math.BigInteger; 7 8 import com.fasterxml.jackson.core.*; 9 import com.fasterxml.jackson.core.base.ParserMinimalBase; 10 import com.fasterxml.jackson.core.util.JacksonFeatureSet; 11 import com.fasterxml.jackson.databind.JsonNode; 12 13 /** 14 * Facade over {@link JsonNode} that implements {@link JsonParser} to allow 15 * accessing contents of JSON tree in alternate form (stream of tokens). 16 * Useful when a streaming source is expected by code, such as data binding 17 * functionality. 18 */ 19 public class TreeTraversingParser extends ParserMinimalBase 20 { 21 /* 22 /********************************************************** 23 /* Configuration 24 /********************************************************** 25 */ 26 27 protected ObjectCodec _objectCodec; 28 29 /** 30 * Traversal context within tree 31 */ 32 protected NodeCursor _nodeCursor; 33 34 /* 35 /********************************************************** 36 /* State 37 /********************************************************** 38 */ 39 40 /** 41 * Flag that indicates whether parser is closed or not. Gets 42 * set when parser is either closed by explicit call 43 * ({@link #close}) or when end-of-input is reached. 44 */ 45 protected boolean _closed; 46 47 /* 48 /********************************************************** 49 /* Life-cycle 50 /********************************************************** 51 */ 52 TreeTraversingParser(JsonNode n)53 public TreeTraversingParser(JsonNode n) { this(n, null); } 54 TreeTraversingParser(JsonNode n, ObjectCodec codec)55 public TreeTraversingParser(JsonNode n, ObjectCodec codec) 56 { 57 super(0); 58 _objectCodec = codec; 59 _nodeCursor = new NodeCursor.RootCursor(n, null); 60 } 61 62 @Override setCodec(ObjectCodec c)63 public void setCodec(ObjectCodec c) { 64 _objectCodec = c; 65 } 66 67 @Override getCodec()68 public ObjectCodec getCodec() { 69 return _objectCodec; 70 } 71 72 @Override version()73 public Version version() { 74 return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION; 75 } 76 77 @Override getReadCapabilities()78 public JacksonFeatureSet<StreamReadCapability> getReadCapabilities() { 79 // Defaults are fine 80 return DEFAULT_READ_CAPABILITIES; 81 } 82 83 /* 84 /********************************************************** 85 /* Closeable implementation 86 /********************************************************** 87 */ 88 89 @Override close()90 public void close() throws IOException 91 { 92 if (!_closed) { 93 _closed = true; 94 _nodeCursor = null; 95 _currToken = null; 96 } 97 } 98 99 /* 100 /********************************************************** 101 /* Public API, traversal 102 /********************************************************** 103 */ 104 105 @Override nextToken()106 public JsonToken nextToken() throws IOException, JsonParseException 107 { 108 _currToken = _nodeCursor.nextToken(); 109 if (_currToken == null) { 110 _closed = true; // if not already set 111 return null; 112 } 113 switch (_currToken) { 114 case START_OBJECT: 115 _nodeCursor = _nodeCursor.startObject(); 116 break; 117 case START_ARRAY: 118 _nodeCursor = _nodeCursor.startArray(); 119 break; 120 case END_OBJECT: 121 case END_ARRAY: 122 _nodeCursor = _nodeCursor.getParent(); 123 default: 124 } 125 return _currToken; 126 } 127 128 // default works well here: 129 //public JsonToken nextValue() throws IOException 130 131 @Override skipChildren()132 public JsonParser skipChildren() throws IOException 133 { 134 if (_currToken == JsonToken.START_OBJECT) { 135 _nodeCursor = _nodeCursor.getParent(); 136 _currToken = JsonToken.END_OBJECT; 137 } else if (_currToken == JsonToken.START_ARRAY) { 138 _nodeCursor = _nodeCursor.getParent(); 139 _currToken = JsonToken.END_ARRAY; 140 } 141 return this; 142 } 143 144 @Override isClosed()145 public boolean isClosed() { 146 return _closed; 147 } 148 149 /* 150 /********************************************************** 151 /* Public API, token accessors 152 /********************************************************** 153 */ 154 getCurrentName()155 @Override public String getCurrentName() { 156 NodeCursor crsr = _nodeCursor; 157 if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { 158 crsr = crsr.getParent(); 159 } 160 return (crsr == null) ? null : crsr.getCurrentName(); 161 } 162 overrideCurrentName(String name)163 @Override public void overrideCurrentName(String name) { 164 NodeCursor crsr = _nodeCursor; 165 if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { 166 crsr = crsr.getParent(); 167 } 168 if (crsr != null) { 169 crsr.overrideCurrentName(name); 170 } 171 } 172 173 @Override getParsingContext()174 public JsonStreamContext getParsingContext() { 175 return _nodeCursor; 176 } 177 178 @Override getTokenLocation()179 public JsonLocation getTokenLocation() { 180 return JsonLocation.NA; 181 } 182 183 @Override getCurrentLocation()184 public JsonLocation getCurrentLocation() { 185 return JsonLocation.NA; 186 } 187 188 /* 189 /********************************************************** 190 /* Public API, access to textual content 191 /********************************************************** 192 */ 193 194 @Override getText()195 public String getText() 196 { 197 if (_closed) { 198 return null; 199 } 200 // need to separate handling a bit... 201 switch (_currToken) { 202 case FIELD_NAME: 203 return _nodeCursor.getCurrentName(); 204 case VALUE_STRING: 205 return currentNode().textValue(); 206 case VALUE_NUMBER_INT: 207 case VALUE_NUMBER_FLOAT: 208 return String.valueOf(currentNode().numberValue()); 209 case VALUE_EMBEDDED_OBJECT: 210 JsonNode n = currentNode(); 211 if (n != null && n.isBinary()) { 212 // this will convert it to base64 213 return n.asText(); 214 } 215 default: 216 return (_currToken == null) ? null : _currToken.asString(); 217 } 218 } 219 220 @Override getTextCharacters()221 public char[] getTextCharacters() throws IOException, JsonParseException { 222 return getText().toCharArray(); 223 } 224 225 @Override getTextLength()226 public int getTextLength() throws IOException, JsonParseException { 227 return getText().length(); 228 } 229 230 @Override getTextOffset()231 public int getTextOffset() throws IOException, JsonParseException { 232 return 0; 233 } 234 235 @Override hasTextCharacters()236 public boolean hasTextCharacters() { 237 // generally we do not have efficient access as char[], hence: 238 return false; 239 } 240 241 /* 242 /********************************************************** 243 /* Public API, typed non-text access 244 /********************************************************** 245 */ 246 247 //public byte getByteValue() throws IOException 248 249 @Override getNumberType()250 public NumberType getNumberType() throws IOException { 251 JsonNode n = currentNumericNode(); 252 return (n == null) ? null : n.numberType(); 253 } 254 255 @Override getBigIntegerValue()256 public BigInteger getBigIntegerValue() throws IOException 257 { 258 return currentNumericNode().bigIntegerValue(); 259 } 260 261 @Override getDecimalValue()262 public BigDecimal getDecimalValue() throws IOException { 263 return currentNumericNode().decimalValue(); 264 } 265 266 @Override getDoubleValue()267 public double getDoubleValue() throws IOException { 268 return currentNumericNode().doubleValue(); 269 } 270 271 @Override getFloatValue()272 public float getFloatValue() throws IOException { 273 return (float) currentNumericNode().doubleValue(); 274 } 275 276 @Override getIntValue()277 public int getIntValue() throws IOException { 278 final NumericNode node = (NumericNode) currentNumericNode(); 279 if (!node.canConvertToInt()) { 280 reportOverflowInt(); 281 } 282 return node.intValue(); 283 } 284 285 @Override getLongValue()286 public long getLongValue() throws IOException { 287 final NumericNode node = (NumericNode) currentNumericNode(); 288 if (!node.canConvertToLong()) { 289 reportOverflowLong(); 290 } 291 return node.longValue(); 292 } 293 294 @Override getNumberValue()295 public Number getNumberValue() throws IOException { 296 return currentNumericNode().numberValue(); 297 } 298 299 @Override getEmbeddedObject()300 public Object getEmbeddedObject() 301 { 302 if (!_closed) { 303 JsonNode n = currentNode(); 304 if (n != null) { 305 if (n.isPojo()) { 306 return ((POJONode) n).getPojo(); 307 } 308 if (n.isBinary()) { 309 return ((BinaryNode) n).binaryValue(); 310 } 311 } 312 } 313 return null; 314 } 315 316 @Override isNaN()317 public boolean isNaN() { 318 if (!_closed) { 319 JsonNode n = currentNode(); 320 if (n instanceof NumericNode) { 321 return ((NumericNode) n).isNaN(); 322 } 323 } 324 return false; 325 } 326 327 /* 328 /********************************************************** 329 /* Public API, typed binary (base64) access 330 /********************************************************** 331 */ 332 333 @Override getBinaryValue(Base64Variant b64variant)334 public byte[] getBinaryValue(Base64Variant b64variant) 335 throws IOException, JsonParseException 336 { 337 // Multiple possibilities... 338 JsonNode n = currentNode(); 339 if (n != null) { 340 // [databind#2096]: although `binaryValue()` works for real binary node 341 // and embedded "POJO" node, coercion from TextNode may require variant, so: 342 if (n instanceof TextNode) { 343 return ((TextNode) n).getBinaryValue(b64variant); 344 } 345 return n.binaryValue(); 346 } 347 // otherwise return null to mark we have no binary content 348 return null; 349 } 350 351 352 @Override readBinaryValue(Base64Variant b64variant, OutputStream out)353 public int readBinaryValue(Base64Variant b64variant, OutputStream out) 354 throws IOException, JsonParseException 355 { 356 byte[] data = getBinaryValue(b64variant); 357 if (data != null) { 358 out.write(data, 0, data.length); 359 return data.length; 360 } 361 return 0; 362 } 363 364 /* 365 /********************************************************** 366 /* Internal methods 367 /********************************************************** 368 */ 369 currentNode()370 protected JsonNode currentNode() { 371 if (_closed || _nodeCursor == null) { 372 return null; 373 } 374 return _nodeCursor.currentNode(); 375 } 376 currentNumericNode()377 protected JsonNode currentNumericNode() 378 throws JsonParseException 379 { 380 JsonNode n = currentNode(); 381 if (n == null || !n.isNumber()) { 382 JsonToken t = (n == null) ? null : n.asToken(); 383 throw _constructError("Current token ("+t+") not numeric, cannot use numeric value accessors"); 384 } 385 return n; 386 } 387 388 @Override _handleEOF()389 protected void _handleEOF() throws JsonParseException { 390 _throwInternal(); // should never get called 391 } 392 } 393