package com.fasterxml.jackson.dataformat.xml.deser;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Set;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserMinimalBase;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.fasterxml.jackson.dataformat.xml.PackageVersion;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
JsonParser
implementation that exposes XML structure as set of JSON events that can be used for data binding. /**
* {@link JsonParser} implementation that exposes XML structure as
* set of JSON events that can be used for data binding.
*/
public class FromXmlParser
extends ParserMinimalBase
{
The default name placeholder for XML text segments is empty
String ("").
/**
* The default name placeholder for XML text segments is empty
* String ("").
*/
public final static String DEFAULT_UNNAMED_TEXT_PROPERTY = "";
Enumeration that defines all togglable features for XML parsers.
/**
* Enumeration that defines all togglable features for XML parsers.
*/
public enum Feature implements FormatFeature
{
Feature that indicates whether XML Empty elements (ones where there are no separate start and end tages, but just one tag that ends with "/>") are exposed as JsonToken.VALUE_NULL
) or not. If they are not returned as `null` tokens, they will be returned as JsonToken.VALUE_STRING
tokens with textual value of "" (empty String).
Default setting is `true` for backwards compatibility.
Since: 2.9
/**
* Feature that indicates whether XML Empty elements (ones where there are
* no separate start and end tages, but just one tag that ends with "/>")
* are exposed as {@link JsonToken#VALUE_NULL}) or not. If they are not
* returned as `null` tokens, they will be returned as {@link JsonToken#VALUE_STRING}
* tokens with textual value of "" (empty String).
*<p>
* Default setting is `true` for backwards compatibility.
*
* @since 2.9
*/
EMPTY_ELEMENT_AS_NULL(true)
;
final boolean _defaultState;
final int _mask;
Method that calculates bit set (flags) of all features that
are enabled by default.
/**
* Method that calculates bit set (flags) of all features that
* are enabled by default.
*/
public static int collectDefaults()
{
int flags = 0;
for (Feature f : values()) {
if (f.enabledByDefault()) {
flags |= f.getMask();
}
}
return flags;
}
private Feature(boolean defaultState) {
_defaultState = defaultState;
_mask = (1 << ordinal());
}
@Override public boolean enabledByDefault() { return _defaultState; }
@Override public int getMask() { return _mask; }
@Override public boolean enabledIn(int flags) { return (flags & getMask()) != 0; }
}
In cases where a start element has both attributes and non-empty textual
value, we have to create a bogus property; we will use this as
the property name.
Name used for pseudo-property used for returning XML Text value (which does
not have actual element name to use). Defaults to empty String, but
may be changed for interoperability reasons: JAXB, for example, uses
"value" as name.
Since: 2.1
/**
* In cases where a start element has both attributes and non-empty textual
* value, we have to create a bogus property; we will use this as
* the property name.
*<p>
* Name used for pseudo-property used for returning XML Text value (which does
* not have actual element name to use). Defaults to empty String, but
* may be changed for interoperability reasons: JAXB, for example, uses
* "value" as name.
*
* @since 2.1
*/
protected String _cfgNameForTextElement = DEFAULT_UNNAMED_TEXT_PROPERTY;
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
Bit flag composed of bits that indicate which Feature
s are enabled. /**
* Bit flag composed of bits that indicate which
* {@link FromXmlParser.Feature}s
* are enabled.
*/
protected int _formatFeatures;
protected ObjectCodec _objectCodec;
/*
/**********************************************************
/* I/O state
/**********************************************************
*/
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;
final protected IOContext _ioContext;
/*
/**********************************************************
/* Parsing state
/**********************************************************
*/
Information about parser context, context in which
the next token is to be parsed (root, array, object).
/**
* Information about parser context, context in which
* the next token is to be parsed (root, array, object).
*/
protected XmlReadContext _parsingContext;
protected final XmlTokenStream _xmlTokens;
We need special handling to keep track of whether a value
may be exposed as simple leaf value.
/**
*
* We need special handling to keep track of whether a value
* may be exposed as simple leaf value.
*/
protected boolean _mayBeLeaf;
protected JsonToken _nextToken;
protected String _currText;
protected Set<String> _namesToWrap;
/*
/**********************************************************
/* Parsing state, parsed values
/**********************************************************
*/
ByteArrayBuilder is needed if 'getBinaryValue' is called. If so,
we better reuse it for remainder of content.
/**
* ByteArrayBuilder is needed if 'getBinaryValue' is called. If so,
* we better reuse it for remainder of content.
*/
protected ByteArrayBuilder _byteArrayBuilder = null;
We will hold on to decoded binary data, for duration of current event, so that multiple calls to JsonParser.getBinaryValue
will not need to decode data more than once. /**
* We will hold on to decoded binary data, for duration of
* current event, so that multiple calls to
* {@link #getBinaryValue} will not need to decode data more
* than once.
*/
protected byte[] _binaryValue;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
ObjectCodec codec, XMLStreamReader xmlReader)
throws IOException
{
super(genericParserFeatures);
_formatFeatures = xmlFeatures;
_ioContext = ctxt;
_objectCodec = codec;
_parsingContext = XmlReadContext.createRootContext(-1, -1);
_xmlTokens = new XmlTokenStream(xmlReader, ctxt.getSourceReference(),
_formatFeatures);
// 04-Jan-2019, tatu: Root-level nulls need slightly specific handling;
// changed in 2.10.2
if (_xmlTokens.hasXsiNil()) {
_nextToken = JsonToken.VALUE_NULL;
} else if (_xmlTokens.getCurrentToken() == XmlTokenStream.XML_START_ELEMENT) {
_nextToken = JsonToken.START_OBJECT;
} else {
_reportError("Internal problem: invalid starting state (%d)", _xmlTokens.getCurrentToken());
}
}
@Override
public Version version() {
return PackageVersion.VERSION;
}
@Override
public ObjectCodec getCodec() {
return _objectCodec;
}
@Override
public void setCodec(ObjectCodec c) {
_objectCodec = c;
}
Since: 2.1
/**
* @since 2.1
*/
public void setXMLTextElementName(String name) {
_cfgNameForTextElement = name;
}
XML format does require support from custom ObjectCodec
(that is, XmlMapper
), so need to return true here. Returns: True since XML format does require support from codec
/**
* XML format does require support from custom {@link ObjectCodec}
* (that is, {@link XmlMapper}), so need to return true here.
*
* @return True since XML format does require support from codec
*/
@Override
public boolean requiresCustomCodec() {
return true;
}
/*
/**********************************************************
/* Extended API, configuration
/**********************************************************
*/
public FromXmlParser enable(Feature f) {
_formatFeatures |= f.getMask();
_xmlTokens.setFormatFeatures(_formatFeatures);
return this;
}
public FromXmlParser disable(Feature f) {
_formatFeatures &= ~f.getMask();
_xmlTokens.setFormatFeatures(_formatFeatures);
return this;
}
public final boolean isEnabled(Feature f) {
return (_formatFeatures & f.getMask()) != 0;
}
public FromXmlParser configure(Feature f, boolean state) {
if (state) {
enable(f);
} else {
disable(f);
}
return this;
}
/*
/**********************************************************
/* FormatFeature support
/**********************************************************
*/
@Override
public int getFormatFeatures() {
return _formatFeatures;
}
@Override
public JsonParser overrideFormatFeatures(int values, int mask) {
_formatFeatures = (_formatFeatures & ~mask) | (values & mask);
return this;
}
/*
/**********************************************************
/* Extended API, access to some internal components
/**********************************************************
*/
Method that allows application direct access to underlying Stax XMLStreamWriter
. Note that use of writer is discouraged, and may interfere with processing of this writer; however, occasionally it may be necessary. Note: writer instance will always be of type XMLStreamWriter2
(including Typed Access API) so upcasts are safe.
/**
* Method that allows application direct access to underlying
* Stax {@link XMLStreamWriter}. Note that use of writer is
* discouraged, and may interfere with processing of this writer;
* however, occasionally it may be necessary.
*<p>
* Note: writer instance will always be of type
* {@link org.codehaus.stax2.XMLStreamWriter2} (including
* Typed Access API) so upcasts are safe.
*/
public XMLStreamReader getStaxReader() {
return _xmlTokens.getXmlReader();
}
/*
/**********************************************************
/* Internal API
/**********************************************************
*/
Method that may be called to indicate that specified names
(only local parts retained currently: this may be changed in
future) should be considered "auto-wrapping", meaning that
they will be doubled to contain two opening elements, two
matching closing elements. This is needed for supporting
handling of so-called "unwrapped" array types, something
XML mappings like JAXB often use.
NOTE: this method is considered part of internal implementation
interface, and it is NOT guaranteed to remain unchanged
between minor versions (it is however expected not to change in
patch versions). So if you have to use it, be prepared for
possible additional work.
Since: 2.1
/**
* Method that may be called to indicate that specified names
* (only local parts retained currently: this may be changed in
* future) should be considered "auto-wrapping", meaning that
* they will be doubled to contain two opening elements, two
* matching closing elements. This is needed for supporting
* handling of so-called "unwrapped" array types, something
* XML mappings like JAXB often use.
*<p>
* NOTE: this method is considered part of internal implementation
* interface, and it is <b>NOT</b> guaranteed to remain unchanged
* between minor versions (it is however expected not to change in
* patch versions). So if you have to use it, be prepared for
* possible additional work.
*
* @since 2.1
*/
public void addVirtualWrapping(Set<String> namesToWrap)
{
/* 17-Sep-2012, tatu: Not 100% sure why, but this is necessary to avoid
* problems with Lists-in-Lists properties
*/
String name = _xmlTokens.getLocalName();
if (name != null && namesToWrap.contains(name)) {
_xmlTokens.repeatStartElement();
}
_namesToWrap = namesToWrap;
_parsingContext.setNamesToWrap(namesToWrap);
}
/*
/**********************************************************
/* JsonParser impl
/**********************************************************
*/
Method that can be called to get the name associated with
the current event.
/**
* Method that can be called to get the name associated with
* the current event.
*/
@Override
public String getCurrentName() throws IOException
{
// start markers require information from parent
String name;
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
XmlReadContext parent = _parsingContext.getParent();
name = parent.getCurrentName();
} else {
name = _parsingContext.getCurrentName();
}
// sanity check
if (name == null) {
throw new IllegalStateException("Missing name, in state: "+_currToken);
}
return name;
}
@Override
public void overrideCurrentName(String name)
{
// Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing:
XmlReadContext ctxt = _parsingContext;
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
ctxt = ctxt.getParent();
}
ctxt.setCurrentName(name);
}
@Override
public void close() throws IOException
{
if (!_closed) {
_closed = true;
try {
if (_ioContext.isResourceManaged() || isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)) {
_xmlTokens.closeCompletely();
} else {
_xmlTokens.close();
}
} catch (XMLStreamException e) {
StaxUtil.throwAsParseException(e, this);
} finally {
// Also, internal buffer(s) can now be released as well
_releaseBuffers();
}
}
}
@Override
public boolean isClosed() { return _closed; }
@Override
public XmlReadContext getParsingContext() {
return _parsingContext;
}
Method that return the starting location of the current
token; that is, position of the first character from input
that starts the current token.
/**
* Method that return the <b>starting</b> location of the current
* token; that is, position of the first character from input
* that starts the current token.
*/
@Override
public JsonLocation getTokenLocation() {
return _xmlTokens.getTokenLocation();
}
Method that returns location of the last processed character;
usually for error reporting purposes
/**
* Method that returns location of the last processed character;
* usually for error reporting purposes
*/
@Override
public JsonLocation getCurrentLocation() {
return _xmlTokens.getCurrentLocation();
}
Since xml representation can not really distinguish between array
and object starts (both are represented with elements), this method
is overridden and taken to mean that expecation is that the current
start element is to mean 'start array', instead of default of
'start object'.
/**
* Since xml representation can not really distinguish between array
* and object starts (both are represented with elements), this method
* is overridden and taken to mean that expecation is that the current
* start element is to mean 'start array', instead of default of
* 'start object'.
*/
@Override
public boolean isExpectedStartArrayToken()
{
JsonToken t = _currToken;
if (t == JsonToken.START_OBJECT) {
_currToken = JsonToken.START_ARRAY;
// Ok: must replace current context with array as well
_parsingContext.convertToArray();
//System.out.println(" isExpectedArrayStart: OBJ->Array, wraps now: "+_parsingContext.getNamesToWrap());
// And just in case a field name was to be returned, wipe it
// 06-Jan-2015, tatu: Actually, could also be empty Object buffered; if so, convert...
if (_nextToken == JsonToken.END_OBJECT) {
_nextToken = JsonToken.END_ARRAY;
} else {
_nextToken = null;
}
// and last thing, [dataformat-xml#33], better ignore attributes
_xmlTokens.skipAttributes();
return true;
}
//System.out.println(" isExpectedArrayStart?: t="+t);
return (t == JsonToken.START_ARRAY);
}
// DEBUGGING
/*
@Override
public JsonToken nextToken() throws IOException
{
JsonToken t = nextToken0();
if (t != null) {
switch (t) {
case FIELD_NAME:
System.out.println("JsonToken: FIELD_NAME '"+_parsingContext.getCurrentName()+"'");
break;
case VALUE_STRING:
System.out.println("JsonToken: VALUE_STRING '"+getText()+"'");
break;
default:
System.out.println("JsonToken: "+t);
}
}
return t;
}
*/
// public JsonToken nextToken0() throws IOException
@Override
public JsonToken nextToken() throws IOException
{
_binaryValue = null;
if (_nextToken != null) {
JsonToken t = _nextToken;
_currToken = t;
_nextToken = null;
switch (t) {
case START_OBJECT:
_parsingContext = _parsingContext.createChildObjectContext(-1, -1);
break;
case START_ARRAY:
_parsingContext = _parsingContext.createChildArrayContext(-1, -1);
break;
case END_OBJECT:
case END_ARRAY:
_parsingContext = _parsingContext.getParent();
_namesToWrap = _parsingContext.getNamesToWrap();
break;
case FIELD_NAME:
_parsingContext.setCurrentName(_xmlTokens.getLocalName());
break;
default: // VALUE_STRING, VALUE_NULL
// should be fine as is?
}
return t;
}
int token;
try {
token = _xmlTokens.next();
} catch (XMLStreamException e) {
token = StaxUtil.throwAsParseException(e, this);
}
// Need to have a loop just because we may have to eat/convert
// a start-element that indicates an array element.
while (token == XmlTokenStream.XML_START_ELEMENT) {
// If we thought we might get leaf, no such luck
if (_mayBeLeaf) {
// leave _mayBeLeaf set, as we start a new context
_nextToken = JsonToken.FIELD_NAME;
_parsingContext = _parsingContext.createChildObjectContext(-1, -1);
return (_currToken = JsonToken.START_OBJECT);
}
if (_parsingContext.inArray()) {
// Yup: in array, so this element could be verified; but it won't be
// reported anyway, and we need to process following event.
try {
token = _xmlTokens.next();
} catch (XMLStreamException e) {
StaxUtil.throwAsParseException(e, this);
}
_mayBeLeaf = true;
continue;
}
String name = _xmlTokens.getLocalName();
_parsingContext.setCurrentName(name);
// Ok: virtual wrapping can be done by simply repeating current START_ELEMENT.
// Couple of ways to do it; but start by making _xmlTokens replay the thing...
if (_namesToWrap != null && _namesToWrap.contains(name)) {
_xmlTokens.repeatStartElement();
}
_mayBeLeaf = true;
// Ok: in array context we need to skip reporting field names.
// But what's the best way to find next token?
return (_currToken = JsonToken.FIELD_NAME);
}
// Ok; beyond start element, what do we get?
while (true) {
switch (token) {
case XmlTokenStream.XML_END_ELEMENT:
// Simple, except that if this is a leaf, need to suppress end:
if (_mayBeLeaf) {
_mayBeLeaf = false;
if (_parsingContext.inArray()) {
// 06-Jan-2015, tatu: as per [dataformat-xml#180], need to
// expose as empty Object, not null
_nextToken = JsonToken.END_OBJECT;
_parsingContext = _parsingContext.createChildObjectContext(-1, -1);
return (_currToken = JsonToken.START_OBJECT);
}
// 07-Sep-2019, tatu: for [dataformat-xml#353], must NOT return second null
if (_currToken != JsonToken.VALUE_NULL) {
return (_currToken = JsonToken.VALUE_NULL);
}
}
_currToken = _parsingContext.inArray() ? JsonToken.END_ARRAY : JsonToken.END_OBJECT;
_parsingContext = _parsingContext.getParent();
_namesToWrap = _parsingContext.getNamesToWrap();
return _currToken;
case XmlTokenStream.XML_ATTRIBUTE_NAME:
// If there was a chance of leaf node, no more...
if (_mayBeLeaf) {
_mayBeLeaf = false;
_nextToken = JsonToken.FIELD_NAME;
_currText = _xmlTokens.getText();
_parsingContext = _parsingContext.createChildObjectContext(-1, -1);
return (_currToken = JsonToken.START_OBJECT);
}
_parsingContext.setCurrentName(_xmlTokens.getLocalName());
return (_currToken = JsonToken.FIELD_NAME);
case XmlTokenStream.XML_ATTRIBUTE_VALUE:
_currText = _xmlTokens.getText();
return (_currToken = JsonToken.VALUE_STRING);
case XmlTokenStream.XML_TEXT:
_currText = _xmlTokens.getText();
if (_mayBeLeaf) {
_mayBeLeaf = false;
// One more refinement (pronunced like "hack") is that if
// we had an empty String (or all white space), and we are
// deserializing an array, we better hide the empty text.
// Also: must skip following END_ELEMENT
try {
_xmlTokens.skipEndElement();
} catch (XMLStreamException e) {
StaxUtil.throwAsParseException(e, this);
}
if (_parsingContext.inArray()) {
if (_isEmpty(_currText)) {
// 06-Jan-2015, tatu: as per [dataformat-xml#180], need to
// expose as empty Object, not null (or, worse, as used to
// be done, by swallowing the token)
_nextToken = JsonToken.END_OBJECT;
_parsingContext = _parsingContext.createChildObjectContext(-1, -1);
return (_currToken = JsonToken.START_OBJECT);
}
}
return (_currToken = JsonToken.VALUE_STRING);
}
// [dataformat-xml#177]: empty text may also need to be skipped
// but... [dataformat-xml#191]: looks like we can't short-cut, must
// loop over again
if (_parsingContext.inObject()) {
if ((_currToken != JsonToken.FIELD_NAME) && _isEmpty(_currText)) {
try {
token = _xmlTokens.next();
} catch (XMLStreamException e) {
StaxUtil.throwAsParseException(e, this);
}
continue;
}
}
// If not a leaf (or otherwise ignorable), need to transform into property...
_parsingContext.setCurrentName(_cfgNameForTextElement);
_nextToken = JsonToken.VALUE_STRING;
return (_currToken = JsonToken.FIELD_NAME);
case XmlTokenStream.XML_END:
return (_currToken = null);
default:
return _internalErrorUnknownToken(token);
}
}
}
/*
/**********************************************************
/* Overrides of specialized nextXxx() methods
/**********************************************************
*/
/*
@Override
public String nextFieldName() throws IOException {
if (nextToken() == JsonToken.FIELD_NAME) {
return getCurrentName();
}
return null;
}
*/
Method overridden to support more reliable deserialization of
String collections.
/**
* Method overridden to support more reliable deserialization of
* String collections.
*/
@Override
public String nextTextValue() throws IOException
{
_binaryValue = null;
if (_nextToken != null) {
JsonToken t = _nextToken;
_currToken = t;
_nextToken = null;
// expected case; yes, got a String
if (t == JsonToken.VALUE_STRING) {
return _currText;
}
_updateState(t);
return null;
}
int token;
try {
token = _xmlTokens.next();
} catch (XMLStreamException e) {
token = StaxUtil.throwAsParseException(e, this);
}
// mostly copied from 'nextToken()'
while (token == XmlTokenStream.XML_START_ELEMENT) {
if (_mayBeLeaf) {
_nextToken = JsonToken.FIELD_NAME;
_parsingContext = _parsingContext.createChildObjectContext(-1, -1);
_currToken = JsonToken.START_OBJECT;
return null;
}
if (_parsingContext.inArray()) {
try {
token = _xmlTokens.next();
} catch (XMLStreamException e) {
StaxUtil.throwAsParseException(e, this);
}
_mayBeLeaf = true;
continue;
}
String name = _xmlTokens.getLocalName();
_parsingContext.setCurrentName(name);
if (_namesToWrap != null && _namesToWrap.contains(name)) {
_xmlTokens.repeatStartElement();
}
_mayBeLeaf = true;
_currToken = JsonToken.FIELD_NAME;
return null;
}
// Ok; beyond start element, what do we get?
switch (token) {
case XmlTokenStream.XML_END_ELEMENT:
if (_mayBeLeaf) {
// NOTE: this is different from nextToken() -- produce "", NOT null
_mayBeLeaf = false;
_currToken = JsonToken.VALUE_STRING;
return (_currText = "");
}
_currToken = _parsingContext.inArray() ? JsonToken.END_ARRAY : JsonToken.END_OBJECT;
_parsingContext = _parsingContext.getParent();
_namesToWrap = _parsingContext.getNamesToWrap();
break;
case XmlTokenStream.XML_ATTRIBUTE_NAME:
// If there was a chance of leaf node, no more...
if (_mayBeLeaf) {
_mayBeLeaf = false;
_nextToken = JsonToken.FIELD_NAME;
_currText = _xmlTokens.getText();
_parsingContext = _parsingContext.createChildObjectContext(-1, -1);
_currToken = JsonToken.START_OBJECT;
} else {
_parsingContext.setCurrentName(_xmlTokens.getLocalName());
_currToken = JsonToken.FIELD_NAME;
}
break;
case XmlTokenStream.XML_ATTRIBUTE_VALUE:
_currToken = JsonToken.VALUE_STRING;
return (_currText = _xmlTokens.getText());
case XmlTokenStream.XML_TEXT:
_currText = _xmlTokens.getText();
if (_mayBeLeaf) {
_mayBeLeaf = false;
// Also: must skip following END_ELEMENT
try {
_xmlTokens.skipEndElement();
} catch (XMLStreamException e) {
StaxUtil.throwAsParseException(e, this);
}
// NOTE: this is different from nextToken() -- NO work-around
// for otherwise empty List/array
_currToken = JsonToken.VALUE_STRING;
return _currText;
}
// If not a leaf, need to transform into property...
_parsingContext.setCurrentName(_cfgNameForTextElement);
_nextToken = JsonToken.VALUE_STRING;
_currToken = JsonToken.FIELD_NAME;
break;
case XmlTokenStream.XML_END:
_currToken = null;
default:
return _internalErrorUnknownToken(token);
}
return null;
}
private void _updateState(JsonToken t)
{
switch (t) {
case START_OBJECT:
_parsingContext = _parsingContext.createChildObjectContext(-1, -1);
break;
case START_ARRAY:
_parsingContext = _parsingContext.createChildArrayContext(-1, -1);
break;
case END_OBJECT:
case END_ARRAY:
_parsingContext = _parsingContext.getParent();
_namesToWrap = _parsingContext.getNamesToWrap();
break;
case FIELD_NAME:
_parsingContext.setCurrentName(_xmlTokens.getLocalName());
break;
default:
_internalErrorUnknownToken(t);
}
}
/*
/**********************************************************
/* Public API, access to token information, text
/**********************************************************
*/
@Override
public String getText() throws IOException
{
if (_currToken == null) {
return null;
}
switch (_currToken) {
case FIELD_NAME:
return getCurrentName();
case VALUE_STRING:
return _currText;
default:
return _currToken.asString();
}
}
// @since 2.1
@Override
public final String getValueAsString() throws IOException {
return getValueAsString(null);
}
@Override
public String getValueAsString(String defValue) throws IOException
{
JsonToken t = _currToken;
if (t == null) {
return null;
}
switch (t) {
case FIELD_NAME:
return getCurrentName();
case VALUE_STRING:
return _currText;
case START_OBJECT:
// the interesting case; may be able to convert certain kinds of
// elements (specifically, ones with attributes, CDATA only content)
// into VALUE_STRING
try {
String str = _xmlTokens.convertToString();
if (str != null) {
// need to convert token, as well as "undo" START_OBJECT
// note: Should NOT update context, because we will still be getting
// matching END_OBJECT, which will undo contexts properly
_parsingContext = _parsingContext.getParent();
_namesToWrap = _parsingContext.getNamesToWrap();
_currToken = JsonToken.VALUE_STRING;
_nextToken = null;
// One more thing: must explicitly skip the END_OBJECT that would follow
try {
_xmlTokens.skipEndElement();
} catch (XMLStreamException e) {
StaxUtil.throwAsParseException(e, this);
}
return (_currText = str);
}
} catch (XMLStreamException e) {
StaxUtil.throwAsParseException(e, this);
}
return null;
default:
if (_currToken.isScalarValue()) {
return _currToken.asString();
}
}
return defValue;
}
@Override
public char[] getTextCharacters() throws IOException {
String text = getText();
return (text == null) ? null : text.toCharArray();
}
@Override
public int getTextLength() throws IOException {
String text = getText();
return (text == null) ? 0 : text.length();
}
@Override
public int getTextOffset() throws IOException {
return 0;
}
XML input actually would offer access to character arrays; but since
we must coalesce things it cannot really be exposed.
/**
* XML input actually would offer access to character arrays; but since
* we must coalesce things it cannot really be exposed.
*/
@Override
public boolean hasTextCharacters()
{
return false;
}
@Override // since 2.8
public int getText(Writer writer) throws IOException
{
String str = getText();
if (str == null) {
return 0;
}
writer.write(str);
return str.length();
}
/*
/**********************************************************
/* Public API, access to token information, binary
/**********************************************************
*/
@Override
public Object getEmbeddedObject() throws IOException {
// no way to embed POJOs for now...
return null;
}
@Override
public byte[] getBinaryValue(Base64Variant b64variant) throws IOException
{
if (_currToken != JsonToken.VALUE_STRING &&
(_currToken != JsonToken.VALUE_EMBEDDED_OBJECT || _binaryValue == null)) {
_reportError("Current token ("+_currToken+") not VALUE_STRING or VALUE_EMBEDDED_OBJECT, can not access as binary");
}
/* To ensure that we won't see inconsistent data, better clear up
* state...
*/
if (_binaryValue == null) {
try {
_binaryValue = _decodeBase64(b64variant);
} catch (IllegalArgumentException iae) {
throw _constructError("Failed to decode VALUE_STRING as base64 ("+b64variant+"): "+iae.getMessage());
}
}
return _binaryValue;
}
@SuppressWarnings("resource")
protected byte[] _decodeBase64(Base64Variant b64variant) throws IOException
{
ByteArrayBuilder builder = _getByteArrayBuilder();
final String str = getText();
_decodeBase64(str, builder, b64variant);
return builder.toByteArray();
}
/*
/**********************************************************
/* Numeric accessors
/**********************************************************
*/
@Override
public BigInteger getBigIntegerValue() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public BigDecimal getDecimalValue() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public double getDoubleValue() throws IOException {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getFloatValue() throws IOException {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getIntValue() throws IOException {
// TODO Auto-generated method stub
return 0;
}
@Override
public long getLongValue() throws IOException {
// TODO Auto-generated method stub
return 0;
}
@Override
public NumberType getNumberType() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public Number getNumberValue() throws IOException {
// TODO Auto-generated method stub
return null;
}
/*
/**********************************************************
/* Abstract method impls for stuff from JsonParser
/**********************************************************
*/
Method called when an EOF is encountered between tokens.
If so, it may be a legitimate EOF, but only iff there
is no open non-root context.
/**
* Method called when an EOF is encountered between tokens.
* If so, it may be a legitimate EOF, but only iff there
* is no open non-root context.
*/
@Override
protected void _handleEOF() throws JsonParseException
{
if (!_parsingContext.inRoot()) {
String marker = _parsingContext.inArray() ? "Array" : "Object";
_reportInvalidEOF(String.format(
": expected close marker for %s (start marker at %s)",
marker,
_parsingContext.getStartLocation(_ioContext.getSourceReference())),
null);
}
}
/*
/**********************************************************
/* Internal methods
/**********************************************************
*/
Method called to release internal buffers owned by the base
parser.
/**
* Method called to release internal buffers owned by the base
* parser.
*/
protected void _releaseBuffers() throws IOException {
// anything we can/must release? Underlying parser should do all of it, for now?
}
protected ByteArrayBuilder _getByteArrayBuilder()
{
if (_byteArrayBuilder == null) {
_byteArrayBuilder = new ByteArrayBuilder();
} else {
_byteArrayBuilder.reset();
}
return _byteArrayBuilder;
}
protected boolean _isEmpty(String str)
{
int len = (str == null) ? 0 : str.length();
if (len > 0) {
for (int i = 0; i < len; ++i) {
if (str.charAt(i) > ' ') {
return false;
}
}
}
return true;
}
private <T> T _internalErrorUnknownToken(Object token) {
throw new IllegalStateException("Internal error: unrecognized XmlTokenStream token: "+token);
}
}