All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fasterxml.aalto.stax.StreamReaderImpl Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/* Aalto XML processor
 *
 * Copyright (c) 2006- Tatu Saloranta, [email protected]
 *
 * Licensed under the License specified in the file LICENSE which is
 * 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.fasterxml.aalto.stax;

import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collections;

import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.codehaus.stax2.*;
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.validation.DTDValidationSchema;
import org.codehaus.stax2.validation.XMLValidator;
import org.codehaus.stax2.validation.XMLValidationSchema;
import org.codehaus.stax2.validation.ValidationProblemHandler;

import org.codehaus.stax2.ri.Stax2Util;
import org.codehaus.stax2.ri.typed.CharArrayBase64Decoder;
import org.codehaus.stax2.ri.typed.ValueDecoderFactory;


import com.fasterxml.aalto.UncheckedStreamException;
import com.fasterxml.aalto.WFCException;
import com.fasterxml.aalto.impl.ErrorConsts;
import com.fasterxml.aalto.impl.IoStreamException;
import com.fasterxml.aalto.in.InputBootstrapper;
import com.fasterxml.aalto.in.PName;
import com.fasterxml.aalto.in.ReaderConfig;
import com.fasterxml.aalto.in.XmlScanner;
import com.fasterxml.aalto.util.TextAccumulator;
import com.fasterxml.aalto.util.XmlNames;

/**
 * Basic backend-independent {@link XMLStreamReader} implementation.
 * While the read implements Stax API, most of real work is delegated
 * to input (and thereby, encoding) specific backend implementations.
 */
public class StreamReaderImpl
    implements XMLStreamReader2,
               AttributeInfo, DTDInfo, LocationInfo
{
    // // // Main state constants

    final static int STATE_PROLOG = 0; // Before root element
    final static int STATE_TREE = 1; // Parsing actual XML tree
    final static int STATE_EPILOG = 2; // After root element has been closed
    final static int STATE_CLOSED = 3; // After reader has been closed

    /*
    /**********************************************************************
    /* Configuration
    /**********************************************************************
     */

    /**
     * Underlying XML scanner
     */
    protected final XmlScanner _scanner;

    // // // Config flags:

    protected final boolean _cfgCoalesceText;

    protected final boolean _cfgReportTextAsChars;

    /*
    /**********************************************************************
    /* Current state
    /**********************************************************************
     */

    protected int _currToken;

    /**
     * Main parsing/tokenization state (STATE_xxx)
     */
    protected int _parseState;

    /**
     * Prefixed name associated with the current event, if any.
     */
    protected PName _currName;

    /**
     * If the current event is START_ELEMENT, number
     * of attributes the start element has. Otherwise undefined.
     * Updated by reader, to make index checks for other attribute
     * access methods simpler.
     */
    protected int _attrCount;

    /**
     * Factory used for constructing decoders we need for typed access
     */
    protected ValueDecoderFactory _decoderFactory;

    /**
     * Lazily-constructed decoder object for decoding base64 encoded
     * element binary content.
     */
    protected CharArrayBase64Decoder _base64Decoder = null;
    
    /*
    /**********************************************************************
    /* Collected other information
    /**********************************************************************
     */

    // // // Info from XML declaration:

    //final String mXmlDeclEncoding;
    //final int mXmlDeclVersion;
    //final int mXmlDeclStandalone;

    //final String mInputEncoding;

    /**
     * Prefixed root-name DOCTYPE declaration gave us, if any (note: also
     * serves as a marker to know if we have seen DOCTYPE yet)
     */
    protected PName _dtdRootName;
    
    /*
    /**********************************************************************
    /* Life-cycle:
    /**********************************************************************
     */

    public StreamReaderImpl(XmlScanner scanner)
    {
        _scanner = scanner;
        _currToken = START_DOCUMENT;
        ReaderConfig cfg = scanner.getConfig();
        _cfgCoalesceText = cfg.willCoalesceText();
        _cfgReportTextAsChars = !cfg.willReportCData();
    }

    public static StreamReaderImpl construct(InputBootstrapper bs)
        throws XMLStreamException
    {
        return new StreamReaderImpl(bs.bootstrap());
    }

    /**
     * Should not really be public, but needed by SAX code
     */
    public XmlScanner getScanner()
    {
        return _scanner;
    }

    /*
    /**********************************************************************
    /* XMLStreamReader API
    /**********************************************************************
     */

    // // // Bit masks used for quick type comparisons

    /**
     * Bitmask for determining if it's ok to call 'getText'
     */
    final private static int MASK_GET_TEXT = 
        (1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE)
        | (1 << COMMENT) | (1 << DTD) | (1 << ENTITY_REFERENCE);

    /**
     * Bitmask for determining if it's ok to call 'getTextXXX' methods
     * (not including 'getText' itself)
     */
    final private static int MASK_GET_TEXT_XXX =
        (1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE) | (1 << COMMENT);

    /**
     * This mask is used with Stax2 getText() method (one that takes
     * Writer as an argument): accepts even wider range of event types.
     */
    final private static int MASK_GET_TEXT_WITH_WRITER = 
        (1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE)
        | (1 << COMMENT) | (1 << DTD) | (1 << ENTITY_REFERENCE)
        | (1 << PROCESSING_INSTRUCTION);

    final private static int MASK_GET_ELEMENT_TEXT = 
        (1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE)
        | (1 << ENTITY_REFERENCE);

    final private 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 private static int MASK_TYPED_ACCESS_BINARY =
        (1 << START_ELEMENT) //  note: END_ELEMENT handled separately
        | (1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE)
        ;

    /*
    /**********************************************************************
    /* XMLStreamReader, document info
    /**********************************************************************
     */

    /**
     * As per Stax (1.0) specs, needs to return whatever xml declaration
     * claimed encoding is, if any; or null if no xml declaration found.
     *

* Note: method name is rather confusing (compare to {@link #getEncoding}). */ @Override public final String getCharacterEncodingScheme() { return _scanner.getConfig().getXmlDeclEncoding(); } /** * As per Stax (1.0) specs, needs to return whatever parser determined * the encoding was, if it was able to figure it out. If not (there are * cases where this can not be found; specifically when being passed a * {@link java.io.Reader}), it should return null. */ @Override public final String getEncoding() { return _scanner.getConfig().getActualEncoding(); } @Override public String getVersion() { return _scanner.getConfig().getXmlDeclVersion(); } @Override public final boolean isStandalone() { return (_scanner.getConfig().getXmlDeclStandalone() == ReaderConfig.STANDALONE_YES); } @Override public final boolean standaloneSet() { return (_scanner.getConfig().getXmlDeclStandalone() != ReaderConfig.STANDALONE_UNKNOWN); } /* /********************************************************************** /* Public API, configuration /********************************************************************** */ @Override public Object getProperty(String name) { if (name.equals("javax.xml.stream.entities")) { // !!! TBI return Collections.EMPTY_LIST; } if (name.equals("javax.xml.stream.notations")) { // !!! TBI return Collections.EMPTY_LIST; } // false -> not mandatory, unrecognized will return null return _scanner.getConfig().getProperty(name, false); } public ReaderConfig getConfig() { return _scanner.getConfig(); } /* /********************************************************************** /* XMLStreamReader, current state /********************************************************************** */ // // // Attribute access: @Override public final int getAttributeCount() { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } return _attrCount; } @Override public final String getAttributeLocalName(int index) { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } if (index >= _attrCount || index < 0) { reportInvalidAttrIndex(index); } return _scanner.getAttrLocalName(index); } @Override public final QName getAttributeName(int index) { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } if (index >= _attrCount || index < 0) { reportInvalidAttrIndex(index); } return _scanner.getAttrQName(index); } @Override public final String getAttributeNamespace(int index) { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } if (index >= _attrCount || index < 0) { reportInvalidAttrIndex(index); } String p = _scanner.getAttrNsURI(index); return (p == null) ? "" : p; } @Override public final String getAttributePrefix(int index) { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } if (index >= _attrCount || index < 0) { reportInvalidAttrIndex(index); } String p = _scanner.getAttrPrefix(index); return (p == null) ? "" : p; } @Override public final String getAttributeType(int index) { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } if (index >= _attrCount || index < 0) { reportInvalidAttrIndex(index); } return _scanner.getAttrType(index); } @Override public final String getAttributeValue(int index) { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } if (index >= _attrCount || index < 0) { reportInvalidAttrIndex(index); } return _scanner.getAttrValue(index); } @Override public final String getAttributeValue(String nsURI, String localName) { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } return _scanner.getAttrValue(nsURI, localName); } /** * From StAX specs: *

* Reads the content of a text-only element, an exception is thrown if * this is not a text-only element. * Regardless of value of javax.xml.stream.isCoalescing this method always * returns coalesced content. *
Precondition: the current event is START_ELEMENT. *
Postcondition: the current event is the corresponding END_ELEMENT. *
*/ @Override public final String getElementText() throws XMLStreamException { if (_currToken != START_ELEMENT) { throwWfe(ErrorConsts.ERR_STATE_NOT_STELEM); } // First need to find a textual event while (true) { int type = next(); if (type == END_ELEMENT) { return ""; } if (type == COMMENT || type == PROCESSING_INSTRUCTION) { continue; } if (((1 << type) & MASK_GET_ELEMENT_TEXT) == 0) { _reportNonTextEvent(type); } break; } String text = _scanner.getText(); // Then we'll see if end is nigh... TextAccumulator acc = null; int type; while ((type = next()) != END_ELEMENT) { if (((1 << type) & MASK_GET_ELEMENT_TEXT) != 0) { if (acc == null) { acc = new TextAccumulator(); acc.addText(text); } acc.addText(getText()); continue; } if (type != COMMENT && type != PROCESSING_INSTRUCTION) { _reportNonTextEvent(type); } } return (acc == null) ? text : acc.getAndClear(); } /** * Returns type of the last event returned; or START_DOCUMENT before * any events has been explicitly returned. */ @Override public final int getEventType() { /* Only complication -- multi-part coalesced text is to be reported * as CHARACTERS always, never as CDATA (StAX specs). */ if (_currToken == CDATA) { if (_cfgCoalesceText || _cfgReportTextAsChars) { return CHARACTERS; } } return _currToken; } @Override public final String getLocalName() { // Note: for this we need not (yet) finish reading element if (_currToken == START_ELEMENT || _currToken == END_ELEMENT || _currToken == ENTITY_REFERENCE) { return _currName.getLocalName(); } throw new IllegalStateException("Current state not START_ELEMENT, END_ELEMENT or ENTITY_REFERENCE"); } // // // getLocation() defined in StreamScanner @Override public final QName getName() { if (_currToken != START_ELEMENT && _currToken != END_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM); } return _scanner.getQName(); } // // // Namespace access @Override public final NamespaceContext getNamespaceContext() { /* Unlike other getNamespaceXxx methods, this is available * for all events. * Note that the context is "live", ie. remains active (but not * static) even through calls to next(). StAX compliant apps * should not count on this behaviour, however. */ return _scanner; } @Override public final int getNamespaceCount() { if (_currToken != START_ELEMENT && _currToken != END_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM); } return _scanner.getNsCount(); } @Override public final String getNamespacePrefix(int index) { if (_currToken != START_ELEMENT && _currToken != END_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM); } String p = _scanner.getNamespacePrefix(index); return (p == null) ? "" : p; } @Override public final String getNamespaceURI() { if (_currToken != START_ELEMENT && _currToken != END_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM); } String uri = _scanner.getNamespaceURI(); return (uri == null) ? "" : uri; } @Override public final String getNamespaceURI(int index) { if (_currToken != START_ELEMENT && _currToken != END_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM); } String uri = _scanner.getNamespaceURI(index); return (uri == null) ? "" : uri; } @Override public final String getNamespaceURI(String prefix) { if (_currToken != START_ELEMENT && _currToken != END_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM); } /* Note: null means 'no such prefix bound', whereas "" means * "no namespace" (only applicable for prefix "") */ return _scanner.getNamespaceURI(prefix); } @Override public final String getPIData() { if (_currToken != PROCESSING_INSTRUCTION) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_PI); } try { return _scanner.getText(); } catch (XMLStreamException sex) { throw UncheckedStreamException.createFrom(sex); } } @Override public final String getPITarget() { if (_currToken != PROCESSING_INSTRUCTION) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_PI); } return _currName.getLocalName(); } @Override public final String getPrefix() { if (_currToken != START_ELEMENT && _currToken != END_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM); } // Note: need to return "" for "no prefix", not null, so: String p = _currName.getPrefix(); return (p == null) ? "" : p; } @Override public final String getText() { if (((1 << _currToken) & MASK_GET_TEXT) == 0) { throwNotTextual(_currToken); } try { return _scanner.getText(); } catch (XMLStreamException sex) { throw UncheckedStreamException.createFrom(sex); } } @Override public final char[] getTextCharacters() { if (((1 << _currToken) & MASK_GET_TEXT_XXX) == 0) { throwNotTextXxx(_currToken); } try { return _scanner.getTextCharacters(); } catch (XMLStreamException sex) { throw UncheckedStreamException.createFrom(sex); } } @Override public final int getTextCharacters(int srcStart, char[] target, int targetStart, int len) { if (((1 << _currToken) & MASK_GET_TEXT_XXX) == 0) { throwNotTextXxx(_currToken); } try { return _scanner.getTextCharacters(srcStart, target, targetStart, len); } catch (XMLStreamException sex) { throw UncheckedStreamException.createFrom(sex); } } @Override public final int getTextLength() { if (((1 << _currToken) & MASK_GET_TEXT_XXX) == 0) { throwNotTextXxx(_currToken); } try { return _scanner.getTextLength(); } catch (XMLStreamException sex) { throw UncheckedStreamException.createFrom(sex); } } @Override public final int getTextStart() { if (((1 << _currToken) & MASK_GET_TEXT_XXX) == 0) { throwNotTextXxx(_currToken); } /* Scanner always stores text from the beginning of its * buffers... */ return 0; } @Override public final boolean hasName() { return (_currToken == START_ELEMENT) || (_currToken == END_ELEMENT); } @Override public final boolean hasNext() { return (_currToken != END_DOCUMENT); } @Override public final boolean hasText() { return (((1 << _currToken) & MASK_GET_TEXT) != 0); } @Override public final boolean isAttributeSpecified(int index) { // No need to check for ATTRIBUTE since we never return that... if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } return _scanner.isAttrSpecified(index); } @Override public final boolean isCharacters() { return (getEventType() == CHARACTERS); } @Override public final boolean isEndElement() { return (_currToken == END_ELEMENT); } @Override public final boolean isStartElement() { return (_currToken == START_ELEMENT); } @Override public final boolean isWhiteSpace() { if (_currToken == CHARACTERS || _currToken == CDATA) { try { return _scanner.isTextWhitespace(); } catch (XMLStreamException sex) { throw UncheckedStreamException.createFrom(sex); } } return (_currToken == SPACE); } @Override public final void require(int type, String nsUri, String localName) throws XMLStreamException { int curr = _currToken; /* There are some special cases; specifically, CDATA * is sometimes reported as CHARACTERS. Let's be lenient by * allowing both 'real' and reported types, for now. */ if (curr != type) { if (curr == CDATA) { if (_cfgCoalesceText || _cfgReportTextAsChars) { curr = CHARACTERS; } } else if (curr == SPACE) { // Hmmh. Should we require it to be empty or something? //curr = CHARACTERS; // For now, let's not change the check } } if (type != curr) { throwWfe("Expected type "+ErrorConsts.tokenTypeDesc(type) +", current type " +ErrorConsts.tokenTypeDesc(curr)); } if (localName != null) { if (curr != START_ELEMENT && curr != END_ELEMENT && curr != ENTITY_REFERENCE) { throwWfe("Expected non-null local name, but current token not a START_ELEMENT, END_ELEMENT or ENTITY_REFERENCE (was "+ErrorConsts.tokenTypeDesc(_currToken)+")"); } String n = getLocalName(); if (n != localName && !n.equals(localName)) { throwWfe("Expected local name '"+localName+"'; current local name '"+n+"'."); } } if (nsUri != null) { if (curr != START_ELEMENT && curr != END_ELEMENT) { throwWfe("Expected non-null NS URI, but current token not a START_ELEMENT or END_ELEMENT (was "+ErrorConsts.tokenTypeDesc(curr)+")"); } String uri = getNamespaceURI(); // No namespace? if (nsUri.length() == 0) { if (uri != null && uri.length() > 0) { throwWfe("Expected empty namespace, instead have '"+uri+"'."); } } else { if ((nsUri != uri) && !nsUri.equals(uri)) { throwWfe("Expected namespace '"+nsUri+"'; have '" +uri+"'."); } } } // Ok, fine, all's good } /* /********************************************************************** /* XMLStreamReader, iterating /********************************************************************** */ @Override public final int next() throws XMLStreamException { if (_parseState == STATE_TREE) { int type = _scanner.nextFromTree(); if (type == XmlScanner.TOKEN_EOI) { // Not allowed here... handleTreeEoi(); } _currToken = type; /* Special cases -- sometimes (when coalescing text, or * when specifically configured to do so), CDATA and SPACE are * to be reported as CHARACTERS, although we still will * internally keep track of the real type. */ if (type == CDATA) { if (_cfgCoalesceText || _cfgReportTextAsChars) { return CHARACTERS; } } else { _currName = _scanner.getName(); if (type == END_ELEMENT) { // root closed? if (_scanner.hasEmptyStack()) { _parseState = STATE_EPILOG; } } else if (type == START_ELEMENT) { _attrCount = _scanner.getAttrCount(); } } return type; } int type; if (_parseState == STATE_PROLOG) { type = _scanner.nextFromProlog(true); // Did we get the root element? if (type == START_ELEMENT) { _parseState = STATE_TREE; _attrCount = _scanner.getAttrCount(); } else if (type == DTD) { if (_dtdRootName != null) { // dup DOCTYPEs not allowed throwWfe("Duplicate DOCTYPE declaration"); } _dtdRootName = _scanner.getName(); } } else if (_parseState == STATE_EPILOG) { type = _scanner.nextFromProlog(false); } else { // == STATE_CLOSED //return END_DOCUMENT; throw new java.util.NoSuchElementException(); } if (type < 0) { // end-of-input // Need to mark return handlePrologEoi(_parseState == STATE_PROLOG); } _currName = _scanner.getName(); return (_currToken = type); } @Override public final int nextTag() throws XMLStreamException { while (true) { int next = next(); switch (next) { case SPACE: case COMMENT: case PROCESSING_INSTRUCTION: continue; case CDATA: case CHARACTERS: if (isWhiteSpace()) { continue; } throwWfe("Received non-all-whitespace CHARACTERS or CDATA event in nextTag()."); break; // never gets here, but jikes complains without case START_ELEMENT: case END_ELEMENT: return next; } throwWfe("Received event "+ErrorConsts.tokenTypeDesc(next) +", instead of START_ELEMENT or END_ELEMENT."); } } /** *

* Note: as per StAX 1.0 specs, this method does NOT close the underlying * input reader. (that is, unless the new StAX2 property * org.codehaus.stax2.XMLInputFactory2#P_AUTO_CLOSE_INPUT is * set to true). */ @Override public final void close() throws XMLStreamException { _closeScanner(false); } @Override public final Location getLocation() { return getStartLocation(); } /* /********************************************************************** /* TypedXMLStreamReader2 implementation /********************************************************************** */ @Override public final boolean getElementAsBoolean() throws XMLStreamException { ValueDecoderFactory.BooleanDecoder dec = _decoderFactory().getBooleanDecoder(); getElementAs(dec); return dec.getValue(); } @Override public final int getElementAsInt() throws XMLStreamException { ValueDecoderFactory.IntDecoder dec = _decoderFactory().getIntDecoder(); getElementAs(dec); return dec.getValue(); } @Override public final long getElementAsLong() throws XMLStreamException { ValueDecoderFactory.LongDecoder dec = _decoderFactory().getLongDecoder(); getElementAs(dec); return dec.getValue(); } @Override public final float getElementAsFloat() throws XMLStreamException { ValueDecoderFactory.FloatDecoder dec = _decoderFactory().getFloatDecoder(); getElementAs(dec); return dec.getValue(); } @Override public final double getElementAsDouble() throws XMLStreamException { ValueDecoderFactory.DoubleDecoder dec = _decoderFactory().getDoubleDecoder(); getElementAs(dec); return dec.getValue(); } @Override public final BigInteger getElementAsInteger() throws XMLStreamException { ValueDecoderFactory.IntegerDecoder dec = _decoderFactory().getIntegerDecoder(); getElementAs(dec); return dec.getValue(); } @Override public final BigDecimal getElementAsDecimal() throws XMLStreamException { ValueDecoderFactory.DecimalDecoder dec = _decoderFactory().getDecimalDecoder(); getElementAs(dec); return dec.getValue(); } @Override public final 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 final void getElementAs(TypedValueDecoder tvd) throws XMLStreamException { // !!! TODO: optimize String value = getElementText(); value = value.trim(); if (value.length() == 0) { _handleEmptyValue(tvd); return; } try { tvd.decode(value); } catch (IllegalArgumentException iae) { throw _constructTypeException(iae, value); } } @Override public final 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); } } /* /********************************************************************** /* TypedXMLStreamReader2 implementation, array elements /********************************************************************** */ @Override public final int readElementAsIntArray(int[] value, int from, int length) throws XMLStreamException { return readElementAsArray(_decoderFactory().getIntArrayDecoder(value, from, length)); } @Override public final int readElementAsLongArray(long[] value, int from, int length) throws XMLStreamException { return readElementAsArray(_decoderFactory().getLongArrayDecoder(value, from, length)); } @Override public final int readElementAsFloatArray(float[] value, int from, int length) throws XMLStreamException { return readElementAsArray(_decoderFactory().getFloatArrayDecoder(value, from, length)); } @Override public final int readElementAsDoubleArray(double[] value, int from, int length) throws XMLStreamException { return readElementAsArray(_decoderFactory().getDoubleArrayDecoder(value, from, length)); } @Override public final int readElementAsArray(TypedArrayDecoder dec) throws XMLStreamException { int type = _currToken; // First things first: must be acceptable start state: if (((1 << type) & MASK_TYPED_ACCESS_ARRAY) == 0) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM_OR_TEXT); } // need to keep track of when we move to a new token: boolean reset; // Are we just starting (START_ELEMENT)? if (type == START_ELEMENT) { // Empty? Not common, but can short-cut handling if occurs if (_scanner.isEmptyTag()) { // might be possible to optimize, but for now this'll do: next(); 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; } if (type == CHARACTERS || type == CDATA) { break; } // otherwise just not legal (how about SPACE, unexpanded entities?) throw _constructUnexpectedInTyped(type); } reset = true; // yes, we'll be getting a new text segment } else { reset = false; // may have an existing text segment } int count = 0; while (type != END_ELEMENT) { /* Ok then: we will now have a valid textual type. Just need to * ensure current segment is completed etc. */ if (type == CHARACTERS || type == CDATA || type == SPACE) { count += _scanner.decodeElements(dec, reset); if (!dec.hasRoom()) { break; } } else if (type == COMMENT || type == PROCESSING_INSTRUCTION) { ; } else { throw _constructUnexpectedInTyped(type); } reset = true; 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 final 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 = _currToken; // 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 { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM_OR_TEXT); } } else if (type == START_ELEMENT) { // just starting (START_ELEMENT)? if (_scanner.isEmptyTag()) { // might be possible to optimize, but for now this'll do: next(); 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; } if (type == CHARACTERS || type == CDATA) { break; } // otherwise just not legal (how about SPACE, unexpanded entities?) throw _constructUnexpectedInTyped(type); } _scanner.resetForDecoding(v, dec, true); // true -> first segment } 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 || _currToken == 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; } _scanner.resetForDecoding(v, dec, false); // false -> not first segment break; } } // If nothing was found, needs to be indicated via -1, not 0 return (totalCount > 0) ? totalCount : -1; } /* /********************************************************************** /* TypedXMLStreamReader2 implementation, scalar attributes /********************************************************************** */ @Override public final int getAttributeIndex(String namespaceURI, String localName) { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } // Note: this method does not check for START_ELEMENT return findAttributeIndex(namespaceURI, localName); } @Override public final boolean getAttributeAsBoolean(int index) throws XMLStreamException { ValueDecoderFactory.BooleanDecoder dec = _decoderFactory().getBooleanDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public final int getAttributeAsInt(int index) throws XMLStreamException { ValueDecoderFactory.IntDecoder dec = _decoderFactory().getIntDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public final long getAttributeAsLong(int index) throws XMLStreamException { ValueDecoderFactory.LongDecoder dec = _decoderFactory().getLongDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public final float getAttributeAsFloat(int index) throws XMLStreamException { ValueDecoderFactory.FloatDecoder dec = _decoderFactory().getFloatDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public final double getAttributeAsDouble(int index) throws XMLStreamException { ValueDecoderFactory.DoubleDecoder dec = _decoderFactory().getDoubleDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public final BigInteger getAttributeAsInteger(int index) throws XMLStreamException { ValueDecoderFactory.IntegerDecoder dec = _decoderFactory().getIntegerDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public final BigDecimal getAttributeAsDecimal(int index) throws XMLStreamException { ValueDecoderFactory.DecimalDecoder dec = _decoderFactory().getDecimalDecoder(); getAttributeAs(index, dec); return dec.getValue(); } @Override public final QName getAttributeAsQName(int index) throws XMLStreamException { ValueDecoderFactory.QNameDecoder dec = _decoderFactory().getQNameDecoder(getNamespaceContext()); getAttributeAs(index, dec); return verifyQName(dec.getValue()); } @Override public final void getAttributeAs(int index, TypedValueDecoder tvd) throws XMLStreamException { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } try { _scanner.decodeAttrValue(index, tvd); } catch (IllegalArgumentException iae) { throw _constructTypeException(iae, getAttributeValue(index)); } } @Override public final int[] getAttributeAsIntArray(int index) throws XMLStreamException { ValueDecoderFactory.IntArrayDecoder dec = _decoderFactory().getIntArrayDecoder(); getAttributeAsArray(index, dec); return dec.getValues(); } @Override public final long[] getAttributeAsLongArray(int index) throws XMLStreamException { ValueDecoderFactory.LongArrayDecoder dec = _decoderFactory().getLongArrayDecoder(); getAttributeAsArray(index, dec); return dec.getValues(); } @Override public final float[] getAttributeAsFloatArray(int index) throws XMLStreamException { ValueDecoderFactory.FloatArrayDecoder dec = _decoderFactory().getFloatArrayDecoder(); getAttributeAsArray(index, dec); return dec.getValues(); } @Override public final 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. * * @return Number of tokens decoded, 0 if none found */ @Override public final int getAttributeAsArray(int index, TypedArrayDecoder tad) throws XMLStreamException { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } return _scanner.decodeAttrValues(index, tad); } @Override public final byte[] getAttributeAsBinary(int index) throws XMLStreamException { return getAttributeAsBinary(index, Base64Variants.getDefaultVariant()); } @Override public final byte[] getAttributeAsBinary(int index, Base64Variant v) throws XMLStreamException { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } return _scanner.decodeAttrBinaryValue(index, v, _base64Decoder()); } protected QName verifyQName(QName n) throws TypedXMLStreamException { String ln = n.getLocalPart(); int ix = XmlNames.findIllegalNameChar(ln, false); 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; } /* /********************************************************************** /* XMLStreamReader2 (Stax2) implementation /********************************************************************** */ // // // StAX2, per-reader configuration @Override public final Object getFeature(String name) { // !!! TBI return null; } @Override public final void setFeature(String name, Object value) { // !!! TBI } // NOTE: getProperty() defined in Stax 1.0 interface @Override public final boolean isPropertySupported(String name) { // !!! TBI: not all these properties are really supported return _scanner.getConfig().isPropertySupported(name); } /** * @param name Name of the property to set * @param value Value to set property to. * * @return True, if the specified property was succesfully * set to specified value; false if its value was not changed */ @Override public final boolean setProperty(String name, Object value) { /* Note: can not call local method, since it'll return false for * recognized but non-mutable properties */ return _scanner.getConfig().setProperty(name, value); } // // // StAX2, additional traversal methods @Override public final void skipElement() throws XMLStreamException { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } int nesting = 1; // need one more end elements than start elements while (true) { int type = next(); if (type == START_ELEMENT) { ++nesting; } else if (type == END_ELEMENT) { if (--nesting == 0) { break; } } } } // // // StAX2, additional attribute access @Override public final AttributeInfo getAttributeInfo() throws XMLStreamException { if (_currToken != START_ELEMENT) { throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM); } return this; } // // // StAX2, Additional DTD access /** * Since this class implements {@link DTDInfo}, method can just * return this. */ @Override public final DTDInfo getDTDInfo() throws XMLStreamException { /* Let's not allow it to be accessed during other events -- that * way callers won't count on it being available afterwards. */ if (_currToken != DTD) { return null; } return this; } // // // StAX2, Additional location information /** * Location information is always accessible, for this reader. */ @Override public final LocationInfo getLocationInfo() { return this; } // // // StAX2, Pass-through text accessors /** * Method similar to {@link #getText()}, except * that it just uses provided Writer to write all textual content. * For further optimization, it may also be allowed to do true * pass-through, thus possibly avoiding one temporary copy of the * data. *

* TODO: try to optimize to allow completely streaming pass-through: * currently will still read all data in memory buffers before * outputting * * @param w Writer to use for writing textual contents * @param preserveContents If true, reader has to preserve contents * so that further calls to getText will return * proper conntets. If false, reader is allowed to skip creation * of such copies: this can improve performance, but it also means * that further calls to getText is not guaranteed to * return meaningful data. * * @return Number of characters written to the reader */ @Override public final int getText(Writer w, boolean preserveContents) throws XMLStreamException { if (((1 << _currToken) & MASK_GET_TEXT_WITH_WRITER) == 0) { throwNotTextual(_currToken); } return _scanner.getText(w, preserveContents); } // // // StAX 2, Other accessors /** * @return Number of open elements in the stack; 0 when parser is in * prolog/epilog, 1 inside root element and so on. */ @Override public final int getDepth() { /* 20-Mar-2008, tatus: Need to modify scanner's value since * it decrements depth early for END_ELEMENT */ int d = _scanner.getDepth(); if (_currToken == END_ELEMENT) { ++d; // to compensate for too early decrement } return d; } /** * @return True, if cursor points to a start or end element that is * constructed from 'empty' element (ends with '/>'); * false otherwise. */ @Override public final boolean isEmptyElement() throws XMLStreamException { return (_currToken == START_ELEMENT) ? _scanner.isEmptyTag() : false; } @Override public final NamespaceContext getNonTransientNamespaceContext() { return _scanner.getNonTransientNamespaceContext(); } @Override public final String getPrefixedName() { switch (_currToken) { case START_ELEMENT: case END_ELEMENT: return _currName.getPrefixedName(); case ENTITY_REFERENCE: return getLocalName(); case PROCESSING_INSTRUCTION: return getPITarget(); case DTD: return getDTDRootName(); } throw new IllegalStateException("Current state not START_ELEMENT, END_ELEMENT, ENTITY_REFERENCE, PROCESSING_INSTRUCTION or DTD"); } @Override public final void closeCompletely() throws XMLStreamException { _closeScanner(true); } /* /********************************************************************** /* DTDInfo implementation (StAX 2) /********************************************************************** */ /** *

* Note: DTD-handling sub-classes need to override this method. */ @Override public final Object getProcessedDTD() { // !!! TBI return null; } @Override public final String getDTDRootName() { if (_currToken != DTD) { return null; } return (_currName == null) ? null : _currName.getPrefixedName(); } @Override public final String getDTDPublicId() { return _scanner.getDTDPublicId(); } @Override public final String getDTDSystemId() { return _scanner.getDTDSystemId(); } /** * @return Internal subset portion of the DOCTYPE declaration, if any; * empty String if none */ @Override public final String getDTDInternalSubset() { if (_currToken != DTD) { return null; } try { return _scanner.getText(); } catch (XMLStreamException sex) { throw UncheckedStreamException.createFrom(sex); } } // // StAX2, v2.0 /** * Sub-class will override this method */ @Override public final DTDValidationSchema getProcessedDTDSchema() { // !!! TBI return null; } /* /********************************************************************** /* LocationInfo implementation (StAX 2) /********************************************************************** */ // // // First, the "raw" offset accessors: @Override public final long getStartingByteOffset() { return _scanner.getStartingByteOffset(); } @Override public final long getStartingCharOffset() { return _scanner.getStartingCharOffset(); } @Override public final long getEndingByteOffset() throws XMLStreamException { return _scanner.getEndingByteOffset(); } @Override public final long getEndingCharOffset() throws XMLStreamException { return _scanner.getEndingCharOffset(); } // // // and then the object-based access methods: @Override public final XMLStreamLocation2 getStartLocation() { return _scanner.getStartLocation(); } @Override public final XMLStreamLocation2 getEndLocation() throws XMLStreamException { return _scanner.getEndLocation(); } @Override public final XMLStreamLocation2 getCurrentLocation() { return _scanner.getCurrentLocation(); } /* /********************************************************************** /* AttributeInfo implementation (StAX 2) /********************************************************************** */ //public final int getAttributeCount(); @Override public final int findAttributeIndex(String nsURI, String localName) { return _scanner.findAttrIndex(nsURI, localName); } @Override public final int getIdAttributeIndex() { // !!! TBI: Need dtd handling for it to work return -1; } @Override public final int getNotationAttributeIndex() { // !!! TBI: Need dtd handling for it to work return -1; } /* /********************************************************************** /* Stax2 validation /********************************************************************** */ @Override public final XMLValidator validateAgainst(XMLValidationSchema schema) throws XMLStreamException { // !!! TBI return null; } @Override public final XMLValidator stopValidatingAgainst(XMLValidationSchema schema) throws XMLStreamException { // !!! TBI return null; } @Override public final XMLValidator stopValidatingAgainst(XMLValidator validator) throws XMLStreamException { // !!! TBI return null; } @Override public final ValidationProblemHandler setValidationProblemHandler(ValidationProblemHandler h) { // !!! TBI return null; } /* /********************************************************************** /* Internal methods, error reporting /********************************************************************** */ /** * Helper method called when {@link #getElementText} (et al) method encounters * a token type it should not, during text coalescing */ protected void _reportNonTextEvent(int type) throws XMLStreamException { throwWfe("Expected a text token, got "+ErrorConsts.tokenTypeDesc(type)+"."); } protected Location getLastCharLocation() { // !!! TBI return _scanner.getCurrentLocation(); } protected int handlePrologEoi(boolean isProlog) throws XMLStreamException { // Either way, we can now close the reader close(); // It's ok to get EOF from epilog but not from prolog if (isProlog) { throwUnexpectedEOI(ErrorConsts.SUFFIX_IN_PROLOG); } return END_DOCUMENT; } /** * Method called when hitting an end-of-input within tree, after * a valid token */ protected void handleTreeEoi() throws XMLStreamException { _currToken = END_DOCUMENT; // !!! Should indicate open tree etc. throwUnexpectedEOI(ErrorConsts.SUFFIX_IN_TREE); } /** * Throws generic parse error with specified message and current parsing * location. */ protected void throwWfe(String msg) throws XMLStreamException { throw new WFCException(msg, getLastCharLocation()); } private void throwNotTextual(int type) { throw new IllegalStateException("Not a textual event (" +ErrorConsts.tokenTypeDesc(_currToken)+")"); } private void throwNotTextXxx(int type) { throw new IllegalStateException("getTextXxx() methods can not be called on " +ErrorConsts.tokenTypeDesc(_currToken)); } protected void throwFromIOE(IOException ioe) throws XMLStreamException { throw new IoStreamException(ioe); } protected void throwUnexpectedEOI(String msg) throws XMLStreamException { throwWfe("Unexpected End-of-input"+msg); } protected XMLStreamException _constructUnexpectedInTyped(int nextToken) { if (nextToken == START_ELEMENT) { return _constructTypeException("Element content can not contain child START_ELEMENT when using Typed Access methods", null); } return _constructTypeException("Expected a text token, got "+ErrorConsts.tokenTypeDesc(nextToken), null); } /** * 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. */ private TypedXMLStreamException _constructTypeException(IllegalArgumentException iae, String lexicalValue) { return new TypedXMLStreamException(lexicalValue, iae.getMessage(), getStartLocation(), iae); } private TypedXMLStreamException _constructTypeException(String msg, String lexicalValue) { return new TypedXMLStreamException(lexicalValue, msg, getStartLocation()); } protected void reportInvalidAttrIndex(int index) { /* 24-Jun-2006, tatus: Stax API doesn't specify what (if anything) * should be thrown. Although RI throws IndexOutOfBounds * let's throw IllegalArgumentException: that's what StaxTest * assumes. */ throw new IllegalArgumentException("Illegal attribute index, "+index+", current START_ELEMENT has "+_attrCount+" attributes"); } /* /********************************************************************** /* Internal methods, input source handling /********************************************************************** */ /** * Method called to close scanner, by asking it to release resource * it has, and potentially also close the underlying stream. */ protected void _closeScanner(boolean forceStreamClose) throws XMLStreamException { if (_parseState != STATE_CLOSED) { _parseState = STATE_CLOSED; if (_currToken != END_DOCUMENT) { _currToken = END_DOCUMENT; } } _scanner.close(forceStreamClose); } /* /********************************************************************** /* Internal methods, other /********************************************************************** */ protected final 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). */ private void _handleEmptyValue(TypedValueDecoder dec) throws XMLStreamException { try { // default action is to throw an exception dec.handleEmptyValue(); } catch (IllegalArgumentException iae) { throw _constructTypeException(iae, ""); } } /* /********************************************************************** /* Debugging help /********************************************************************** */ @Override public final String toString() { return "[Aalto stream reader, scanner: "+_scanner+"]"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy