/* Woodstox XML processor
 *
 * Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi
 *
 * Licensed under the License specified in file LICENSE, included with
 * the source code.
 * You may not use this file except in compliance with the License.
 *
 * 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.
 */

package com.ctc.wstx.sr;

import java.math.BigDecimal;
import java.math.BigInteger;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;

import org.codehaus.stax2.typed.Base64Variant;
import org.codehaus.stax2.typed.Base64Variants;
import org.codehaus.stax2.typed.TypedArrayDecoder;
import org.codehaus.stax2.typed.TypedValueDecoder;
import org.codehaus.stax2.typed.TypedXMLStreamException;
import org.codehaus.stax2.ri.Stax2Util;
import org.codehaus.stax2.ri.typed.ValueDecoderFactory;
import org.codehaus.stax2.ri.typed.CharArrayBase64Decoder;

import com.ctc.wstx.api.ReaderConfig;
import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.io.BranchingReaderSource;
import com.ctc.wstx.io.InputBootstrapper;
import com.ctc.wstx.io.WstxInputData;

Complete implementation of XMLStreamReader2, including Typed Access API (Stax2 v3.0) implementation. Only functionality missing is DTD validation, which is provided by a specialized sub-class.
/** * Complete implementation of {@link org.codehaus.stax2.XMLStreamReader2}, * including Typed Access API (Stax2 v3.0) implementation. * Only functionality missing is DTD validation, which is provided by a * specialized sub-class. */
public class TypedStreamReader extends BasicStreamReader {
Mask of event types that are legal (starting) states to call Typed Access API from.
/** * Mask of event types that are legal (starting) states * to call Typed Access API from. * */
final protected static int MASK_TYPED_ACCESS_ARRAY = (1 << START_ELEMENT) | (1 << END_ELEMENT) // for convenience | (1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE) // Not ok for PI or COMMENT? Let's assume so ; final protected static int MASK_TYPED_ACCESS_BINARY = (1 << START_ELEMENT) // note: END_ELEMENT handled separately | (1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE) ;
Minimum length of text chunks to parse before base64 decoding. Will try to limit it to fit within regular result buffers.
/** * Minimum length of text chunks to parse before base64 decoding. * Will try to limit it to fit within regular result buffers. */
final static int MIN_BINARY_CHUNK = 2000;
Factory used for constructing decoders we need for typed access
/** * Factory used for constructing decoders we need for typed access */
protected ValueDecoderFactory _decoderFactory;
Lazily-constructed decoder object for decoding base64 encoded element binary content.
/** * Lazily-constructed decoder object for decoding base64 encoded * element binary content. */
protected CharArrayBase64Decoder _base64Decoder = null; /* //////////////////////////////////////////////////// // Instance construction //////////////////////////////////////////////////// */ protected TypedStreamReader(InputBootstrapper bs, BranchingReaderSource input, ReaderCreator owner, ReaderConfig cfg, InputElementStack elemStack, boolean forER) throws XMLStreamException { super(bs, input, owner, cfg, elemStack, forER); }
Factory method for constructing readers.
Params:
  • owner – "Owner" of this reader, factory that created the reader; needed for returning updated symbol table information after parsing.
  • input – Input source used to read the XML document.
  • cfg – Object that contains reader configuration info.
/** * Factory method for constructing readers. * * @param owner "Owner" of this reader, factory that created the reader; * needed for returning updated symbol table information after parsing. * @param input Input source used to read the XML document. * @param cfg Object that contains reader configuration info. */
public static TypedStreamReader createStreamReader (BranchingReaderSource input, ReaderCreator owner, ReaderConfig cfg, InputBootstrapper bs, boolean forER) throws XMLStreamException { TypedStreamReader sr = new TypedStreamReader (bs, input, owner, cfg, createElementStack(cfg), forER); return sr; } /* //////////////////////////////////////////////////////// // TypedXMLStreamReader2 implementation, scalar elements //////////////////////////////////////////////////////// */ @Override public boolean getElementAsBoolean() throws XMLStreamException { ValueDecoderFactory.BooleanDecoder dec = _decoderFactory().getBooleanDecoder(); getElementAs(dec); return dec.getValue(); } @Override public int getElementAsInt() throws XMLStreamException { ValueDecoderFactory.IntDecoder dec = _decoderFactory().getIntDecoder(); getElementAs(dec); return dec.getValue(); } @Override public long getElementAsLong() throws XMLStreamException { ValueDecoderFactory.LongDecoder dec = _decoderFactory().getLongDecoder(); getElementAs(dec); return dec.getValue(); } @Override public float getElementAsFloat() throws XMLStreamException { ValueDecoderFactory.FloatDecoder dec = _decoderFactory().getFloatDecoder(); getElementAs(dec); return dec.getValue(); } @Override public double getElementAsDouble() throws XMLStreamException { ValueDecoderFactory.DoubleDecoder dec = _decoderFactory().getDoubleDecoder(); getElementAs(dec); return dec.getValue(); } @Override public BigInteger getElementAsInteger() throws XMLStreamException { ValueDecoderFactory.IntegerDecoder dec = _decoderFactory().getIntegerDecoder(); getElementAs(dec); return dec.getValue(); } @Override public BigDecimal getElementAsDecimal() throws XMLStreamException { ValueDecoderFactory.DecimalDecoder dec = _decoderFactory().getDecimalDecoder(); getElementAs(dec); return dec.getValue(); } @Override public QName getElementAsQName() throws XMLStreamException { ValueDecoderFactory.QNameDecoder dec = _decoderFactory().getQNameDecoder(getNamespaceContext()); getElementAs(dec); return _verifyQName(dec.getValue()); } @Override public final byte[] getElementAsBinary() throws XMLStreamException { return getElementAsBinary(Base64Variants.getDefaultVariant()); } @Override public byte[] getElementAsBinary(Base64Variant v) throws XMLStreamException { // note: code here is similar to Base64DecoderBase.aggregateAll(), see comments there Stax2Util.ByteAggregator aggr = _base64Decoder().getByteAggregator(); byte[] buffer = aggr.startAggregation(); while (true) { int offset = 0; int len = buffer.length; do { int readCount = readElementAsBinary(buffer, offset, len, v); if (readCount < 1) { // all done! return aggr.aggregateAll(buffer, offset); } offset += readCount; len -= readCount; } while (len > 0); buffer = aggr.addFullBlock(buffer); } } @Override public void getElementAs(TypedValueDecoder tvd) throws XMLStreamException { if (mCurrToken != START_ELEMENT) { throwParseError(ErrorConsts.ERR_STATE_NOT_STELEM); } /* Ok, now: with START_ELEMENT we know that it's not partially * processed; that we are in-tree (not prolog or epilog). * The only possible complication would be: */ if (mStEmptyElem) { /* And if so, we'll then get 'virtual' close tag; things * are simple as location info was set when dealing with * empty start element; and likewise, validation (if any) * has been taken care of */ mStEmptyElem = false; mCurrToken = END_ELEMENT; _handleEmptyValue(tvd); return; } // First need to find a textual event while (true) { int type = next(); if (type == END_ELEMENT) { _handleEmptyValue(tvd); return; } if (type == COMMENT || type == PROCESSING_INSTRUCTION) { continue; } if (((1 << type) & MASK_GET_ELEMENT_TEXT) == 0) { throwParseError("Expected a text token, got "+tokenTypeDesc(type)+"."); } break; } if (mTokenState < TOKEN_FULL_SINGLE) { readCoalescedText(mCurrToken, false); } /* Ok: then a quick check; if it looks like we are directly * followed by the end tag, we need not construct String * quite yet. */ if ((mInputPtr + 1) < mInputEnd && mInputBuffer[mInputPtr] == '<' && mInputBuffer[mInputPtr+1] == '/') { // Note: next() has validated text, no need for more validation mInputPtr += 2; mCurrToken = END_ELEMENT; /* Can by-pass next(), nextFromTree(), in this case. * However, must do decoding first, and only then call * readEndElem(), since this latter call may invalidate * underlying input buffer (when end tag is at buffer * boundary) */ try { // buffer now has all the data mTextBuffer.decode(tvd); } catch (IllegalArgumentException iae) { throw _constructTypeException(iae, mTextBuffer.contentsAsString()); } readEndElem(); return; } // Otherwise, we'll need to do slower processing int extra = 1 + (mTextBuffer.size() >> 1); // let's add 50% space StringBuilder sb = mTextBuffer.contentsAsStringBuilder(extra); int type; while ((type = next()) != END_ELEMENT) { if (((1 << type) & MASK_GET_ELEMENT_TEXT) != 0) { if (mTokenState < TOKEN_FULL_SINGLE) { readCoalescedText(type, false); } mTextBuffer.contentsToStringBuilder(sb); continue; } if (type != COMMENT && type != PROCESSING_INSTRUCTION) { throwParseError("Expected a text token, got "+tokenTypeDesc(type)+"."); } } // Note: calls next() have validated text, no need for more validation String str = sb.toString(); String tstr = Stax2Util.trimSpaces(str); if (tstr == null) { _handleEmptyValue(tvd); } else { try { tvd.decode(tstr); } catch (IllegalArgumentException iae) { throw _constructTypeException(iae, str); } } } /* //////////////////////////////////////////////////////// // TypedXMLStreamReader2 implementation, array elements //////////////////////////////////////////////////////// */ @Override public int readElementAsIntArray(int[] value, int from, int length) throws XMLStreamException { return readElementAsArray(_decoderFactory().getIntArrayDecoder(value, from, length)); } @Override public int readElementAsLongArray(long[] value, int from, int length) throws XMLStreamException { return readElementAsArray(_decoderFactory().getLongArrayDecoder(value, from, length)); } @Override public int readElementAsFloatArray(float[] value, int from, int length) throws XMLStreamException { return readElementAsArray(_decoderFactory().getFloatArrayDecoder(value, from, length)); } @Override public int readElementAsDoubleArray(double[] value, int from, int length) throws XMLStreamException { return readElementAsArray(_decoderFactory().getDoubleArrayDecoder(value, from, length)); }
Method called to parse array of primitives.

!!! 05-Sep-2008, tatu: Current implementation is not optimal either performance-wise, or from getting accurate Location for decoding problems. But it works otherwise, and we need to get Woodstox 4.0 out by the end of the year... so it'll do, for now.

Returns:Number of elements decoded (if any were decoded), or -1 to indicate that no more values can be decoded.
/** * Method called to parse array of primitives. *<p> * !!! 05-Sep-2008, tatu: Current implementation is not optimal * either performance-wise, or from getting accurate Location * for decoding problems. But it works otherwise, and we need * to get Woodstox 4.0 out by the end of the year... so it'll * do, for now. * * @return Number of elements decoded (if any were decoded), or * -1 to indicate that no more values can be decoded. */
@Override public final int readElementAsArray(TypedArrayDecoder dec) throws XMLStreamException { int type = mCurrToken; // First things first: must be acceptable start state: if (((1 << type) & MASK_TYPED_ACCESS_ARRAY) == 0) { throwNotTextualOrElem(type); } // Are we just starting (START_ELEMENT)? if (type == START_ELEMENT) { // Empty? Not common, but can short-cut handling if occurs if (mStEmptyElem) { mStEmptyElem = false; mCurrToken = END_ELEMENT; return -1; } // Otherwise let's just find the first text segment while (true) { type = next(); if (type == END_ELEMENT) { // Simple... no textul content return -1; } if (type == COMMENT || type == PROCESSING_INSTRUCTION) { continue; } if (type == CHARACTERS || type == CDATA) { break; } // otherwise just not legal (how about SPACE, unexpanded entities?) throw _constructUnexpectedInTyped(type); } } int count = 0; while (type != END_ELEMENT) { /* Ok then: we will have a valid textual type. Just need to * ensure current segment is completed. Plus, for current impl, * also need to coalesce to prevent artificial CDATA/text * boundary from splitting tokens */ if (type == CHARACTERS || type == CDATA || type == SPACE) { if (mTokenState < TOKEN_FULL_SINGLE) { readCoalescedText(type, false); } } else if (type == COMMENT || type == PROCESSING_INSTRUCTION) { type = next(); continue; } else { throw _constructUnexpectedInTyped(type); } count += mTextBuffer.decodeElements(dec, this); if (!dec.hasRoom()) { break; } type = next(); } // If nothing was found, needs to be indicated via -1, not 0 return (count > 0) ? count : -1; } /* //////////////////////////////////////////////////////// // TypedXMLStreamReader2 implementation, binary data //////////////////////////////////////////////////////// */ @Override public final int readElementAsBinary(byte[] resultBuffer, int offset, int maxLength) throws XMLStreamException { return readElementAsBinary(resultBuffer, offset, maxLength, Base64Variants.getDefaultVariant()); } @Override public int readElementAsBinary(byte[] resultBuffer, int offset, int maxLength, Base64Variant v) throws XMLStreamException { if (resultBuffer == null) { throw new IllegalArgumentException("resultBuffer is null"); } if (offset < 0) { throw new IllegalArgumentException("Illegal offset ("+offset+"), must be [0, "+resultBuffer.length+"["); } if (maxLength < 1 || (offset + maxLength) > resultBuffer.length) { if (maxLength == 0) { // special case, allowed, but won't do anything return 0; } throw new IllegalArgumentException("Illegal maxLength ("+maxLength+"), has to be positive number, and offset+maxLength can not exceed"+resultBuffer.length); } final CharArrayBase64Decoder dec = _base64Decoder(); int type = mCurrToken; // First things first: must be acceptable start state: if (((1 << type) & MASK_TYPED_ACCESS_BINARY) == 0) { if (type == END_ELEMENT) { // Minor complication: may have unflushed stuff (non-padded versions) if (!dec.hasData()) { return -1; } } else { throwNotTextualOrElem(type); } } else if (type == START_ELEMENT) { // just starting (START_ELEMENT)? if (mStEmptyElem) { // empty element? simple... mStEmptyElem = false; mCurrToken = END_ELEMENT; return -1; } // Otherwise let's just find the first text segment while (true) { type = next(); if (type == END_ELEMENT) { // Simple... no textual content return -1; } if (type == COMMENT || type == PROCESSING_INSTRUCTION) { continue; } /* 12-Dec-2009, tatu: Important: in coalescing mode we may * have incomplete segment that needs to be completed */ if (mTokenState < mStTextThreshold) { finishToken(false); } _initBinaryChunks(v, dec, type, true); break; } } int totalCount = 0; main_loop: while (true) { // Ok, decode: int count; try { count = dec.decode(resultBuffer, offset, maxLength); } catch (IllegalArgumentException iae) { // !!! 26-Sep-2008, tatus: should try to figure out which char (etc) triggered problem to pass with typed exception throw _constructTypeException(iae.getMessage(), ""); } offset += count; totalCount += count; maxLength -= count; /* And if we filled the buffer we are done. Or, an edge * case: reached END_ELEMENT (for non-padded variant) */ if (maxLength < 1 || mCurrToken == END_ELEMENT) { break; } // Otherwise need to advance to the next event while (true) { type = next(); if (type == COMMENT || type == PROCESSING_INSTRUCTION || type == SPACE) { // space is ignorable too continue; } if (type == END_ELEMENT) { /* Just need to verify we don't have partial stuff * (missing one to three characters of a full quartet * that encodes 1 - 3 bytes). Also: non-padding * variants can be in incomplete state, from which * data may need to be flushed... */ int left = dec.endOfContent(); if (left < 0) { // incomplete, error throw _constructTypeException("Incomplete base64 triplet at the end of decoded content", ""); } else if (left > 0) { // 1 or 2 more bytes of data, loop some more continue main_loop; } // Otherwise, no more data, we are done break main_loop; } /* 12-Dec-2009, tatu: Important: in coalescing mode we may * have incomplete segment that needs to be completed */ if (mTokenState < mStTextThreshold) { finishToken(false); } _initBinaryChunks(v, dec, type, false); break; } } // If nothing was found, needs to be indicated via -1, not 0 return (totalCount > 0) ? totalCount : -1; } private final void _initBinaryChunks(Base64Variant v, CharArrayBase64Decoder dec, int type, boolean isFirst) throws XMLStreamException { if (type == CHARACTERS) { if (mTokenState < mStTextThreshold) { mTokenState = readTextSecondary(MIN_BINARY_CHUNK, false) ? TOKEN_FULL_SINGLE : TOKEN_PARTIAL_SINGLE; } } else if (type == CDATA) { if (mTokenState < mStTextThreshold) { mTokenState = readCDataSecondary(MIN_BINARY_CHUNK) ? TOKEN_FULL_SINGLE : TOKEN_PARTIAL_SINGLE; } } else { throw _constructUnexpectedInTyped(type); } mTextBuffer.initBinaryChunks(v, dec, isFirst); } /* /////////////////////////////////////////////////////////// // TypedXMLStreamReader2 implementation, scalar attributes /////////////////////////////////////////////////////////// */ @Override public int getAttributeIndex(String namespaceURI, String localName) { // Note: cut'n pasted from "getAttributeInfo()" if (mCurrToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } return mElementStack.findAttributeIndex(namespaceURI, localName); } @Override public boolean getAttributeAsBoolean(int index) throws XMLStreamException { ValueDecoderFactory.BooleanDecoder dec = _decoderFactory().getBooleanDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public int getAttributeAsInt(int index) throws XMLStreamException { ValueDecoderFactory.IntDecoder dec = _decoderFactory().getIntDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public long getAttributeAsLong(int index) throws XMLStreamException { ValueDecoderFactory.LongDecoder dec = _decoderFactory().getLongDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public float getAttributeAsFloat(int index) throws XMLStreamException { ValueDecoderFactory.FloatDecoder dec = _decoderFactory().getFloatDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public double getAttributeAsDouble(int index) throws XMLStreamException { ValueDecoderFactory.DoubleDecoder dec = _decoderFactory().getDoubleDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public BigInteger getAttributeAsInteger(int index) throws XMLStreamException { ValueDecoderFactory.IntegerDecoder dec = _decoderFactory().getIntegerDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public BigDecimal getAttributeAsDecimal(int index) throws XMLStreamException { ValueDecoderFactory.DecimalDecoder dec = _decoderFactory().getDecimalDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public QName getAttributeAsQName(int index) throws XMLStreamException { ValueDecoderFactory.QNameDecoder dec = _decoderFactory().getQNameDecoder(getNamespaceContext()); getAttributeAs(index, dec); return _verifyQName(dec.getValue()); } @Override public void getAttributeAs(int index, TypedValueDecoder tvd) throws XMLStreamException { if (mCurrToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } try { mAttrCollector.decodeValue(index, tvd); } catch (IllegalArgumentException iae) { throw _constructTypeException(iae, mAttrCollector.getValue(index)); } } @Override public int[] getAttributeAsIntArray(int index) throws XMLStreamException { ValueDecoderFactory.IntArrayDecoder dec = _decoderFactory().getIntArrayDecoder(); getAttributeAsArray(index, dec); return dec.getValues(); } @Override public long[] getAttributeAsLongArray(int index) throws XMLStreamException { ValueDecoderFactory.LongArrayDecoder dec = _decoderFactory().getLongArrayDecoder(); getAttributeAsArray(index, dec); return dec.getValues(); } @Override public float[] getAttributeAsFloatArray(int index) throws XMLStreamException { ValueDecoderFactory.FloatArrayDecoder dec = _decoderFactory().getFloatArrayDecoder(); getAttributeAsArray(index, dec); return dec.getValues(); } @Override public double[] getAttributeAsDoubleArray(int index) throws XMLStreamException { ValueDecoderFactory.DoubleArrayDecoder dec = _decoderFactory().getDoubleArrayDecoder(); getAttributeAsArray(index, dec); return dec.getValues(); }
Method that allows reading contents of an attribute as an array of whitespace-separate tokens, decoded using specified decoder.
Returns:Number of tokens decoded, 0 if none found
/** * Method that allows reading contents of an attribute as an array * of whitespace-separate tokens, decoded using specified decoder. * * @return Number of tokens decoded, 0 if none found */
@Override public int getAttributeAsArray(int index, TypedArrayDecoder tad) throws XMLStreamException { if (mCurrToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } return mAttrCollector.decodeValues(index, tad, this); } @Override public byte[] getAttributeAsBinary(int index) throws XMLStreamException { return getAttributeAsBinary(index, Base64Variants.getDefaultVariant()); } @Override public byte[] getAttributeAsBinary(int index, Base64Variant v) throws XMLStreamException { return mAttrCollector.decodeBinary(index, v, _base64Decoder(), this); } /* ///////////////////////////////////////////////////// // Internal helper methods ///////////////////////////////////////////////////// */
Method called to verify validity of the parsed QName element or attribute value. At this point binding of a prefixed name (if qname has a prefix) has been verified, and thereby prefix also must be valid (since there must have been a preceding declaration). But local name might still not be a legal well-formed xml name, so let's verify that.
/** * Method called to verify validity of the parsed QName element * or attribute value. At this point binding of a prefixed name * (if qname has a prefix) has been verified, and thereby prefix * also must be valid (since there must have been a preceding * declaration). But local name might still not be a legal * well-formed xml name, so let's verify that. */
protected QName _verifyQName(QName n) throws TypedXMLStreamException { String ln = n.getLocalPart(); int ix = WstxInputData.findIllegalNameChar(ln, mCfgNsEnabled, mXml11); if (ix >= 0) { String prefix = n.getPrefix(); String pname = (prefix != null && prefix.length() > 0) ? (prefix + ":" +ln) : ln; throw _constructTypeException("Invalid local name \""+ln+"\" (character at #"+ix+" is invalid)", pname); } return n; } protected ValueDecoderFactory _decoderFactory() { if (_decoderFactory == null) { _decoderFactory = new ValueDecoderFactory(); } return _decoderFactory; } protected CharArrayBase64Decoder _base64Decoder() { if (_base64Decoder == null) { _base64Decoder = new CharArrayBase64Decoder(); } return _base64Decoder; }
Method called to handle value that has empty String as representation. This will usually either lead to an exception, or parsing to the default value for the type in question (null for nullable types and so on).
/** * Method called to handle value that has empty String * as representation. This will usually either lead to an * exception, or parsing to the default value for the * type in question (null for nullable types and so on). */
private void _handleEmptyValue(TypedValueDecoder dec) throws XMLStreamException { try { // default action is to throw an exception dec.handleEmptyValue(); } catch (IllegalArgumentException iae) { throw _constructTypeException(iae, ""); } }
Method called to wrap or convert given conversion-fail exception into a full TypedXMLStreamException,
Params:
  • iae – Problem as reported by converter
  • lexicalValue – Lexical value (element content, attribute value) that could not be converted succesfully.
/** * Method called to wrap or convert given conversion-fail exception * into a full {@link TypedXMLStreamException}, * * @param iae Problem as reported by converter * @param lexicalValue Lexical value (element content, attribute value) * that could not be converted succesfully. */
protected TypedXMLStreamException _constructTypeException(IllegalArgumentException iae, String lexicalValue) { return new TypedXMLStreamException(lexicalValue, iae.getMessage(), getStartLocation(), iae); } }