/* Aalto XML processor
 *
 * Copyright (c) 2006- Tatu Saloranta, tatu.saloranta@iki.fi
 *
 * 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 <code>START_ELEMENT</code>, 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.
     *<p>
     * 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:
     *<blockquote>
     * 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.
     *<br>Precondition: the current event is START_ELEMENT.
     *<br>Postcondition: the current event is the corresponding END_ELEMENT. 
     *</blockquote>
     */
    @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.");
        }
    }

    /**
     *<p>
     * Note: as per StAX 1.0 specs, this method does NOT close the underlying
     * input reader. (that is, unless the new StAX2 property
     * <code>org.codehaus.stax2.XMLInputFactory2#P_AUTO_CLOSE_INPUT</code> 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

    @Deprecated // in base class
    @Override
    public final Object getFeature(String name) {
        // !!! TBI
        return null;
    }

    @Deprecated // in base class
    @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 <b>succesfully</b>
     *    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 <code>this</code>.
     */
    @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.
     *<p>
     * 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 <code>getText</code> 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 <code>getText</code> 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 '/&gt;');
     *    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)
    /**********************************************************************
     */

    /**
     *<p>
     * 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+"]";
    }
}
