package com.fasterxml.jackson.databind.node;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserMinimalBase;

import com.fasterxml.jackson.databind.JsonNode;

Facade over JsonNode that implements JsonParser to allow accessing contents of JSON tree in alternate form (stream of tokens). Useful when a streaming source is expected by code, such as data binding functionality.
/** * Facade over {@link JsonNode} that implements {@link JsonParser} to allow * accessing contents of JSON tree in alternate form (stream of tokens). * Useful when a streaming source is expected by code, such as data binding * functionality. */
public class TreeTraversingParser extends ParserMinimalBase { /* /********************************************************** /* Configuration /********************************************************** */ protected ObjectCodec _objectCodec;
Traversal context within tree
/** * Traversal context within tree */
protected NodeCursor _nodeCursor; /* /********************************************************** /* State /********************************************************** */
Sometimes parser needs to buffer a single look-ahead token; if so, it'll be stored here. This is currently used for handling
/** * Sometimes parser needs to buffer a single look-ahead token; if so, * it'll be stored here. This is currently used for handling */
protected JsonToken _nextToken;
Flag needed to handle recursion into contents of child Array/Object nodes.
/** * Flag needed to handle recursion into contents of child * Array/Object nodes. */
protected boolean _startContainer;
Flag that indicates whether parser is closed or not. Gets set when parser is either closed by explicit call (close) or when end-of-input is reached.
/** * Flag that indicates whether parser is closed or not. Gets * set when parser is either closed by explicit call * ({@link #close}) or when end-of-input is reached. */
protected boolean _closed; /* /********************************************************** /* Life-cycle /********************************************************** */ public TreeTraversingParser(JsonNode n) { this(n, null); } public TreeTraversingParser(JsonNode n, ObjectCodec codec) { super(0); _objectCodec = codec; if (n.isArray()) { _nextToken = JsonToken.START_ARRAY; _nodeCursor = new NodeCursor.ArrayCursor(n, null); } else if (n.isObject()) { _nextToken = JsonToken.START_OBJECT; _nodeCursor = new NodeCursor.ObjectCursor(n, null); } else { // value node _nodeCursor = new NodeCursor.RootCursor(n, null); } } @Override public void setCodec(ObjectCodec c) { _objectCodec = c; } @Override public ObjectCodec getCodec() { return _objectCodec; } @Override public Version version() { return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION; } /* /********************************************************** /* Closeable implementation /********************************************************** */ @Override public void close() throws IOException { if (!_closed) { _closed = true; _nodeCursor = null; _currToken = null; } } /* /********************************************************** /* Public API, traversal /********************************************************** */ @Override public JsonToken nextToken() throws IOException, JsonParseException { if (_nextToken != null) { _currToken = _nextToken; _nextToken = null; return _currToken; } // are we to descend to a container child? if (_startContainer) { _startContainer = false; // minor optimization: empty containers can be skipped if (!_nodeCursor.currentHasChildren()) { _currToken = (_currToken == JsonToken.START_OBJECT) ? JsonToken.END_OBJECT : JsonToken.END_ARRAY; return _currToken; } _nodeCursor = _nodeCursor.iterateChildren(); _currToken = _nodeCursor.nextToken(); if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { _startContainer = true; } return _currToken; } // No more content? if (_nodeCursor == null) { _closed = true; // if not already set return null; } // Otherwise, next entry from current cursor _currToken = _nodeCursor.nextToken(); if (_currToken != null) { if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { _startContainer = true; } return _currToken; } // null means no more children; need to return end marker _currToken = _nodeCursor.endToken(); _nodeCursor = _nodeCursor.getParent(); return _currToken; } // default works well here: //public JsonToken nextValue() throws IOException, JsonParseException @Override public JsonParser skipChildren() throws IOException, JsonParseException { if (_currToken == JsonToken.START_OBJECT) { _startContainer = false; _currToken = JsonToken.END_OBJECT; } else if (_currToken == JsonToken.START_ARRAY) { _startContainer = false; _currToken = JsonToken.END_ARRAY; } return this; } @Override public boolean isClosed() { return _closed; } /* /********************************************************** /* Public API, token accessors /********************************************************** */ @Override public String getCurrentName() { return (_nodeCursor == null) ? null : _nodeCursor.getCurrentName(); } @Override public void overrideCurrentName(String name) { if (_nodeCursor != null) { _nodeCursor.overrideCurrentName(name); } } @Override public JsonStreamContext getParsingContext() { return _nodeCursor; } @Override public JsonLocation getTokenLocation() { return JsonLocation.NA; } @Override public JsonLocation getCurrentLocation() { return JsonLocation.NA; } /* /********************************************************** /* Public API, access to textual content /********************************************************** */ @Override public String getText() { if (_closed) { return null; } // need to separate handling a bit... switch (_currToken) { case FIELD_NAME: return _nodeCursor.getCurrentName(); case VALUE_STRING: return currentNode().textValue(); case VALUE_NUMBER_INT: case VALUE_NUMBER_FLOAT: return String.valueOf(currentNode().numberValue()); case VALUE_EMBEDDED_OBJECT: JsonNode n = currentNode(); if (n != null && n.isBinary()) { // this will convert it to base64 return n.asText(); } default: return (_currToken == null) ? null : _currToken.asString(); } } @Override public char[] getTextCharacters() throws IOException, JsonParseException { return getText().toCharArray(); } @Override public int getTextLength() throws IOException, JsonParseException { return getText().length(); } @Override public int getTextOffset() throws IOException, JsonParseException { return 0; } @Override public boolean hasTextCharacters() { // generally we do not have efficient access as char[], hence: return false; } /* /********************************************************** /* Public API, typed non-text access /********************************************************** */ //public byte getByteValue() throws IOException @Override public NumberType getNumberType() throws IOException { JsonNode n = currentNumericNode(); return (n == null) ? null : n.numberType(); } @Override public BigInteger getBigIntegerValue() throws IOException { return currentNumericNode().bigIntegerValue(); } @Override public BigDecimal getDecimalValue() throws IOException { return currentNumericNode().decimalValue(); } @Override public double getDoubleValue() throws IOException { return currentNumericNode().doubleValue(); } @Override public float getFloatValue() throws IOException { return (float) currentNumericNode().doubleValue(); } @Override public int getIntValue() throws IOException { final NumericNode node = (NumericNode) currentNumericNode(); if (!node.canConvertToInt()) { reportOverflowInt(); } return node.intValue(); } @Override public long getLongValue() throws IOException { final NumericNode node = (NumericNode) currentNumericNode(); if (!node.canConvertToLong()) { reportOverflowLong(); } return node.longValue(); } @Override public Number getNumberValue() throws IOException { return currentNumericNode().numberValue(); } @Override public Object getEmbeddedObject() { if (!_closed) { JsonNode n = currentNode(); if (n != null) { if (n.isPojo()) { return ((POJONode) n).getPojo(); } if (n.isBinary()) { return ((BinaryNode) n).binaryValue(); } } } return null; } @Override public boolean isNaN() { if (!_closed) { JsonNode n = currentNode(); if (n instanceof NumericNode) { return ((NumericNode) n).isNaN(); } } return false; } /* /********************************************************** /* Public API, typed binary (base64) access /********************************************************** */ @Override public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException { // Multiple possibilities... JsonNode n = currentNode(); if (n != null) { // [databind#2096]: although `binaryValue()` works for real binary node // and embedded "POJO" node, coercion from TextNode may require variant, so: if (n instanceof TextNode) { return ((TextNode) n).getBinaryValue(b64variant); } return n.binaryValue(); } // otherwise return null to mark we have no binary content return null; } @Override public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException, JsonParseException { byte[] data = getBinaryValue(b64variant); if (data != null) { out.write(data, 0, data.length); return data.length; } return 0; } /* /********************************************************** /* Internal methods /********************************************************** */ protected JsonNode currentNode() { if (_closed || _nodeCursor == null) { return null; } return _nodeCursor.currentNode(); } protected JsonNode currentNumericNode() throws JsonParseException { JsonNode n = currentNode(); if (n == null || !n.isNumber()) { JsonToken t = (n == null) ? null : n.asToken(); throw _constructError("Current token ("+t+") not numeric, cannot use numeric value accessors"); } return n; } @Override protected void _handleEOF() throws JsonParseException { _throwInternal(); // should never get called } }