/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2004-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.sun.xml.fastinfoset.sax;

import com.sun.xml.fastinfoset.Decoder;
import com.sun.xml.fastinfoset.DecoderStateTables;
import com.sun.xml.fastinfoset.EncodingConstants;
import com.sun.xml.fastinfoset.QualifiedName;
import com.sun.xml.fastinfoset.algorithm.BuiltInEncodingAlgorithmFactory;
import com.sun.xml.fastinfoset.algorithm.BuiltInEncodingAlgorithmState;
import org.jvnet.fastinfoset.sax.EncodingAlgorithmContentHandler;
import org.jvnet.fastinfoset.sax.FastInfosetReader;
import org.jvnet.fastinfoset.sax.PrimitiveTypeContentHandler;
import com.sun.xml.fastinfoset.util.CharArray;
import com.sun.xml.fastinfoset.util.CharArrayString;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Map;
import org.jvnet.fastinfoset.EncodingAlgorithm;
import org.jvnet.fastinfoset.EncodingAlgorithmException;
import org.jvnet.fastinfoset.EncodingAlgorithmIndexes;
import org.jvnet.fastinfoset.FastInfosetException;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;
import com.sun.xml.fastinfoset.CommonResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.ext.DeclHandler;

/**
 * The Fast Infoset SAX parser.
 * <p>
 * Instantiate this parser to parse a fast infoset document in accordance
 * with the SAX API.
 *
 * <p>
 * More than one fast infoset document may be decoded from the
 * {@link java.io.InputStream}.
 */
public class SAXDocumentParser extends Decoder implements FastInfosetReader {
    private static final Logger logger = Logger.getLogger(SAXDocumentParser.class.getName());
    
    /*
     * Empty lexical handler used by default to report
     * lexical-based events
     */
    private static final class LexicalHandlerImpl implements LexicalHandler {
        public void comment(char[] ch, int start, int end) { }
        
        public void startDTD(String name, String publicId, String systemId) { }
        public void endDTD() { }
        
        public void startEntity(String name) { }
        public void endEntity(String name) { }
        
        public void startCDATA() { }
        public void endCDATA() { }
    };
    
    /*
     * Empty DTD declaration handler used by default to report
     * DTD declaration-based events
     */
    private static final class DeclHandlerImpl implements DeclHandler {
        public void elementDecl(String name, String model) throws SAXException {
        }
        
        public void attributeDecl(String eName, String aName,
                String type, String mode, String value) throws SAXException {
        }
        
        public void internalEntityDecl(String name,
                String value) throws SAXException {
        }
        
        public void externalEntityDecl(String name,
                String publicId, String systemId) throws SAXException {
        }
    }
    
    /**
     * SAX Namespace attributes features
     */
    protected boolean _namespacePrefixesFeature = false;
    
    /**
     * Reference to entity resolver.
     */
    protected EntityResolver _entityResolver;
    
    /**
     * Reference to dtd handler.
     */
    protected DTDHandler _dtdHandler;
    
    /**
     * Reference to content handler.
     */
    protected ContentHandler _contentHandler;
    
    /**
     * Reference to error handler.
     */
    protected ErrorHandler _errorHandler;
    
    /**
     * Reference to lexical handler.
     */
    protected LexicalHandler _lexicalHandler;
    
    /**
     * Reference to DTD declaration handler.
     */
    protected DeclHandler _declHandler;
    
    protected EncodingAlgorithmContentHandler _algorithmHandler;
    
    protected PrimitiveTypeContentHandler _primitiveHandler;
    
    protected BuiltInEncodingAlgorithmState builtInAlgorithmState =
            new BuiltInEncodingAlgorithmState();
    
    protected AttributesHolder _attributes;
    
    protected int[] _namespacePrefixes = new int[16];
    
    protected int _namespacePrefixesIndex;
    
    protected boolean _clearAttributes = false;
    
    /** Creates a new instance of DocumetParser2 */
    public SAXDocumentParser() {
        DefaultHandler handler = new DefaultHandler();
        _attributes = new AttributesHolder(_registeredEncodingAlgorithms);
        
        _entityResolver = handler;
        _dtdHandler = handler;
        _contentHandler = handler;
        _errorHandler = handler;
        _lexicalHandler = new LexicalHandlerImpl();
        _declHandler = new DeclHandlerImpl();
    }
    
    protected void resetOnError() {
        _clearAttributes = false;
        _attributes.clear();
        _namespacePrefixesIndex = 0;
        
        if (_v != null) {
            _v.prefix.clearCompletely();
        }
        _duplicateAttributeVerifier.clear();
    }
    
    // XMLReader interface
    
    public boolean getFeature(String name)
    throws SAXNotRecognizedException, SAXNotSupportedException {
        if (name.equals(Features.NAMESPACES_FEATURE)) {
            return true;
        } else if (name.equals(Features.NAMESPACE_PREFIXES_FEATURE)) {
            return _namespacePrefixesFeature;
        } else if (name.equals(Features.STRING_INTERNING_FEATURE) ||
                name.equals(FastInfosetReader.STRING_INTERNING_PROPERTY)) {
            return getStringInterning();
        } else {
            throw new SAXNotRecognizedException(
                    CommonResourceBundle.getInstance().getString("message.featureNotSupported") + name);
        }
    }
    
    public void setFeature(String name, boolean value)
    throws SAXNotRecognizedException, SAXNotSupportedException {
        if (name.equals(Features.NAMESPACES_FEATURE)) {
            if (value == false) {
                throw new SAXNotSupportedException(name + ":" + value);
            }
        } else if (name.equals(Features.NAMESPACE_PREFIXES_FEATURE)) {
            _namespacePrefixesFeature = value;
        } else if (name.equals(Features.STRING_INTERNING_FEATURE) ||
                name.equals(FastInfosetReader.STRING_INTERNING_PROPERTY)) {
            setStringInterning(value);
        } else {
            throw new SAXNotRecognizedException(
                    CommonResourceBundle.getInstance().getString("message.featureNotSupported") + name);
        }
    }
    
    public Object getProperty(String name)
    throws SAXNotRecognizedException, SAXNotSupportedException {
        if (name.equals(Properties.LEXICAL_HANDLER_PROPERTY)) {
            return getLexicalHandler();
        } else if (name.equals(Properties.DTD_DECLARATION_HANDLER_PROPERTY)) {
            return getDeclHandler();
        } else if (name.equals(FastInfosetReader.EXTERNAL_VOCABULARIES_PROPERTY)) {
            return getExternalVocabularies();
        } else if (name.equals(FastInfosetReader.REGISTERED_ENCODING_ALGORITHMS_PROPERTY)) {
            return getRegisteredEncodingAlgorithms();
        } else if (name.equals(FastInfosetReader.ENCODING_ALGORITHM_CONTENT_HANDLER_PROPERTY)) {
            return getEncodingAlgorithmContentHandler();
        } else if (name.equals(FastInfosetReader.PRIMITIVE_TYPE_CONTENT_HANDLER_PROPERTY)) {
            return getPrimitiveTypeContentHandler();
        } else {
            throw new SAXNotRecognizedException(CommonResourceBundle.getInstance().
                    getString("message.propertyNotRecognized", new Object[]{name}));
        }
    }
    
    public void setProperty(String name, Object value)
    throws SAXNotRecognizedException, SAXNotSupportedException {
        if (name.equals(Properties.LEXICAL_HANDLER_PROPERTY)) {
            if (value instanceof LexicalHandler) {
                setLexicalHandler((LexicalHandler)value);
            } else {
                throw new SAXNotSupportedException(Properties.LEXICAL_HANDLER_PROPERTY);
            }
        } else if (name.equals(Properties.DTD_DECLARATION_HANDLER_PROPERTY)) {
            if (value instanceof DeclHandler) {
                setDeclHandler((DeclHandler)value);
            } else {
                throw new SAXNotSupportedException(Properties.LEXICAL_HANDLER_PROPERTY);
            }
        } else if (name.equals(FastInfosetReader.EXTERNAL_VOCABULARIES_PROPERTY)) {
            if (value instanceof Map) {
                setExternalVocabularies((Map)value);
            } else {
                throw new SAXNotSupportedException(FastInfosetReader.EXTERNAL_VOCABULARIES_PROPERTY);
            }
        } else if (name.equals(FastInfosetReader.REGISTERED_ENCODING_ALGORITHMS_PROPERTY)) {
            if (value instanceof Map) {
                setRegisteredEncodingAlgorithms((Map)value);
            } else {
                throw new SAXNotSupportedException(FastInfosetReader.REGISTERED_ENCODING_ALGORITHMS_PROPERTY);
            }
        } else if (name.equals(FastInfosetReader.ENCODING_ALGORITHM_CONTENT_HANDLER_PROPERTY)) {
            if (value instanceof EncodingAlgorithmContentHandler) {
                setEncodingAlgorithmContentHandler((EncodingAlgorithmContentHandler)value);
            } else {
                throw new SAXNotSupportedException(FastInfosetReader.ENCODING_ALGORITHM_CONTENT_HANDLER_PROPERTY);
            }
        } else if (name.equals(FastInfosetReader.PRIMITIVE_TYPE_CONTENT_HANDLER_PROPERTY)) {
            if (value instanceof PrimitiveTypeContentHandler) {
                setPrimitiveTypeContentHandler((PrimitiveTypeContentHandler)value);
            } else {
                throw new SAXNotSupportedException(FastInfosetReader.PRIMITIVE_TYPE_CONTENT_HANDLER_PROPERTY);
            }
        } else if (name.equals(FastInfosetReader.BUFFER_SIZE_PROPERTY)) {
            if (value instanceof Integer) {
                setBufferSize(((Integer)value).intValue());
            } else {
                throw new SAXNotSupportedException(FastInfosetReader.BUFFER_SIZE_PROPERTY);
            }
        } else {
            throw new SAXNotRecognizedException(CommonResourceBundle.getInstance().
                    getString("message.propertyNotRecognized", new Object[]{name}));
        }
    }
    
    public void setEntityResolver(EntityResolver resolver) {
        _entityResolver = resolver;
    }
    
    public EntityResolver getEntityResolver() {
        return _entityResolver;
    }
    
    public void setDTDHandler(DTDHandler handler) {
        _dtdHandler = handler;
    }
    
    public DTDHandler getDTDHandler() {
        return _dtdHandler;
    }
    public void setContentHandler(ContentHandler handler) {
        _contentHandler = handler;
    }
    
    public ContentHandler getContentHandler() {
        return _contentHandler;
    }
    
    public void setErrorHandler(ErrorHandler handler) {
        _errorHandler = handler;
    }
    
    public ErrorHandler getErrorHandler() {
        return _errorHandler;
    }
    
    public void parse(InputSource input) throws IOException, SAXException {
        try {
            InputStream s = input.getByteStream();
            if (s == null) {
                String systemId = input.getSystemId();
                if (systemId == null) {
                    throw new SAXException(CommonResourceBundle.getInstance().getString("message.inputSource"));
                }
                parse(systemId);
            } else {
                parse(s);
            }
        } catch (FastInfosetException e) {
            logger.log(Level.FINE, "parsing error", e);
            throw new SAXException(e);
        }
    }
    
    public void parse(String systemId) throws IOException, SAXException {
        try {
            systemId = SystemIdResolver.getAbsoluteURI(systemId);
            parse(new URL(systemId).openStream());
        } catch (FastInfosetException e) {
            logger.log(Level.FINE, "parsing error", e);
            throw new SAXException(e);
        }
    }
    
    
    
    
    // FastInfosetReader
    
    public final void parse(InputStream s) throws IOException, FastInfosetException, SAXException {
        setInputStream(s);
        parse();
    }
    
    public void setLexicalHandler(LexicalHandler handler) {
        _lexicalHandler = handler;
    }
    
    public LexicalHandler getLexicalHandler() {
        return _lexicalHandler;
    }
    
    public void setDeclHandler(DeclHandler handler) {
        _declHandler = handler;
    }
    
    public DeclHandler getDeclHandler() {
        return _declHandler;
    }
    
    public void setEncodingAlgorithmContentHandler(EncodingAlgorithmContentHandler handler) {
        _algorithmHandler = handler;
    }
    
    public EncodingAlgorithmContentHandler getEncodingAlgorithmContentHandler() {
        return _algorithmHandler;
    }
    
    public void setPrimitiveTypeContentHandler(PrimitiveTypeContentHandler handler) {
        _primitiveHandler = handler;
    }
    
    public PrimitiveTypeContentHandler getPrimitiveTypeContentHandler() {
        return _primitiveHandler;
    }
    
    
    
    
    public final void parse() throws FastInfosetException, IOException {
        if (_octetBuffer.length < _bufferSize) {
            _octetBuffer = new byte[_bufferSize];
        }
        
        try {
            reset();
            decodeHeader();
            if (_parseFragments)
                processDIIFragment();
            else
                processDII();
        } catch (RuntimeException e) {
            try {
                _errorHandler.fatalError(new SAXParseException(e.getClass().getName(), null, e));
            } catch (Exception ee) {
            }
            resetOnError();
            // Wrap runtime exception
            throw new FastInfosetException(e);
        } catch (FastInfosetException e) {
            try {
                _errorHandler.fatalError(new SAXParseException(e.getClass().getName(), null, e));
            } catch (Exception ee) {
            }
            resetOnError();
            throw e;
        } catch (IOException e) {
            try {
                _errorHandler.fatalError(new SAXParseException(e.getClass().getName(), null, e));
            } catch (Exception ee) {
            }
            resetOnError();
            throw e;
        }
    }
    
    protected final void processDII() throws FastInfosetException, IOException {
        try {
            _contentHandler.startDocument();
        } catch (SAXException e) {
            throw new FastInfosetException("processDII", e);
        }
        
        _b = read();
        if (_b > 0) {
            processDIIOptionalProperties();
        }
        
        // Decode one Document Type II, Comment IIs, PI IIs and one EII
        boolean firstElementHasOccured = false;
        boolean documentTypeDeclarationOccured = false;
        while(!_terminate || !firstElementHasOccured) {
            _b = read();
            switch(DecoderStateTables.DII(_b)) {
                case DecoderStateTables.EII_NO_AIIS_INDEX_SMALL:
                    processEII(_elementNameTable._array[_b], false);
                    firstElementHasOccured = true;
                    break;
                case DecoderStateTables.EII_AIIS_INDEX_SMALL:
                    processEII(_elementNameTable._array[_b & EncodingConstants.INTEGER_3RD_BIT_SMALL_MASK], true);
                    firstElementHasOccured = true;
                    break;
                case DecoderStateTables.EII_INDEX_MEDIUM:
                    processEII(decodeEIIIndexMedium(), (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    firstElementHasOccured = true;
                    break;
                case DecoderStateTables.EII_INDEX_LARGE:
                    processEII(decodeEIIIndexLarge(), (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    firstElementHasOccured = true;
                    break;
                case DecoderStateTables.EII_LITERAL:
                {
                    final QualifiedName qn = decodeLiteralQualifiedName(
                            _b & EncodingConstants.LITERAL_QNAME_PREFIX_NAMESPACE_NAME_MASK,
                            _elementNameTable.getNext());
                    _elementNameTable.add(qn);
                    processEII(qn, (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    firstElementHasOccured = true;
                    break;
                }
                case DecoderStateTables.EII_NAMESPACES:
                    processEIIWithNamespaces();
                    firstElementHasOccured = true;
                    break;
                case DecoderStateTables.DOCUMENT_TYPE_DECLARATION_II:
                {
                    if (documentTypeDeclarationOccured) {
                        throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.secondOccurenceOfDTDII"));
                    }
                    documentTypeDeclarationOccured = true;
                    
                    String system_identifier = ((_b & EncodingConstants.DOCUMENT_TYPE_SYSTEM_IDENTIFIER_FLAG) > 0)
                    ? decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherURI) : "";
                    String public_identifier = ((_b & EncodingConstants.DOCUMENT_TYPE_PUBLIC_IDENTIFIER_FLAG) > 0)
                    ? decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherURI) : "";
                    
                    _b = read();
                    while (_b == EncodingConstants.PROCESSING_INSTRUCTION) {
                        switch(decodeNonIdentifyingStringOnFirstBit()) {
                            case NISTRING_STRING:
                                if (_addToTable) {
                                    _v.otherString.add(new CharArray(_charBuffer, 0, _charBufferLength, true));
                                }
                                break;
                            case NISTRING_ENCODING_ALGORITHM:
                                throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.processingIIWithEncodingAlgorithm"));
                            case NISTRING_INDEX:
                                break;
                            case NISTRING_EMPTY_STRING:
                                break;
                        }
                        _b = read();
                    }
                    if ((_b & EncodingConstants.TERMINATOR) != EncodingConstants.TERMINATOR) {
                        throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.processingInstructionIIsNotTerminatedCorrectly"));
                    }
                    if (_b == EncodingConstants.DOUBLE_TERMINATOR) {
                        _terminate = true;
                    }
                    
                    if (_notations != null) _notations.clear();
                    if (_unparsedEntities != null) _unparsedEntities.clear();
                    /*
                     * TODO
                     * Report All events associated with DTD, PIs, notations etc
                     */
                    break;
                }
                case DecoderStateTables.COMMENT_II:
                    processCommentII();
                    break;
                case DecoderStateTables.PROCESSING_INSTRUCTION_II:
                    processProcessingII();
                    break;
                case DecoderStateTables.TERMINATOR_DOUBLE:
                    _doubleTerminate = true;
                case DecoderStateTables.TERMINATOR_SINGLE:
                    _terminate = true;
                    break;
                default:
                    throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.IllegalStateDecodingDII"));
            }
        }
        
        // Decode any remaining Comment IIs, PI IIs
        while(!_terminate) {
            _b = read();
            switch(DecoderStateTables.DII(_b)) {
                case DecoderStateTables.COMMENT_II:
                    processCommentII();
                    break;
                case DecoderStateTables.PROCESSING_INSTRUCTION_II:
                    processProcessingII();
                    break;
                case DecoderStateTables.TERMINATOR_DOUBLE:
                    _doubleTerminate = true;
                case DecoderStateTables.TERMINATOR_SINGLE:
                    _terminate = true;
                    break;
                default:
                    throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.IllegalStateDecodingDII"));
            }
        }
        
        try {
            _contentHandler.endDocument();
        } catch (SAXException e) {
            throw new FastInfosetException("processDII", e);
        }
    }
    
    protected final void processDIIFragment() throws FastInfosetException, IOException {
        try {
            _contentHandler.startDocument();
        } catch (SAXException e) {
            throw new FastInfosetException("processDII", e);
        }
        
        _b = read();
        if (_b > 0) {
            processDIIOptionalProperties();
        }
        
        while(!_terminate) {
            _b = read();
            switch(DecoderStateTables.EII(_b)) {
                case DecoderStateTables.EII_NO_AIIS_INDEX_SMALL:
                    processEII(_elementNameTable._array[_b], false);
                    break;
                case DecoderStateTables.EII_AIIS_INDEX_SMALL:
                    processEII(_elementNameTable._array[_b & EncodingConstants.INTEGER_3RD_BIT_SMALL_MASK], true);
                    break;
                case DecoderStateTables.EII_INDEX_MEDIUM:
                    processEII(decodeEIIIndexMedium(), (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    break;
                case DecoderStateTables.EII_INDEX_LARGE:
                    processEII(decodeEIIIndexLarge(), (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    break;
                case DecoderStateTables.EII_LITERAL:
                {
                    final QualifiedName qn = decodeLiteralQualifiedName(
                            _b & EncodingConstants.LITERAL_QNAME_PREFIX_NAMESPACE_NAME_MASK,
                            _elementNameTable.getNext());
                    _elementNameTable.add(qn);
                    processEII(qn, (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    break;
                }
                case DecoderStateTables.EII_NAMESPACES:
                    processEIIWithNamespaces();
                    break;
                case DecoderStateTables.CII_UTF8_SMALL_LENGTH:
                    _octetBufferLength = (_b & EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_SMALL_MASK)
                    + 1;
                    processUtf8CharacterString();
                    break;
                case DecoderStateTables.CII_UTF8_MEDIUM_LENGTH:
                    _octetBufferLength = read() + EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_SMALL_LIMIT;
                    processUtf8CharacterString();
                    break;
                case DecoderStateTables.CII_UTF8_LARGE_LENGTH:
                    _octetBufferLength = ((read() << 24) |
                            (read() << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_MEDIUM_LIMIT;
                    processUtf8CharacterString();
                    break;
                case DecoderStateTables.CII_UTF16_SMALL_LENGTH:
                    _octetBufferLength = (_b & EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_SMALL_MASK)
                    + 1;
                    decodeUtf16StringAsCharBuffer();
                    if ((_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0) {
                        _characterContentChunkTable.add(_charBuffer, _charBufferLength);
                    }
                    
                    try {
                        _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                case DecoderStateTables.CII_UTF16_MEDIUM_LENGTH:
                    _octetBufferLength = read() + EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_SMALL_LIMIT;
                    decodeUtf16StringAsCharBuffer();
                    if ((_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0) {
                        _characterContentChunkTable.add(_charBuffer, _charBufferLength);
                    }
                    
                    try {
                        _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                case DecoderStateTables.CII_UTF16_LARGE_LENGTH:
                    _octetBufferLength = ((read() << 24) |
                            (read() << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_MEDIUM_LIMIT;
                    decodeUtf16StringAsCharBuffer();
                    if ((_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0) {
                        _characterContentChunkTable.add(_charBuffer, _charBufferLength);
                    }
                    
                    try {
                        _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                case DecoderStateTables.CII_RA:
                {
                    final boolean addToTable = (_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0;
                    
                    // Decode resitricted alphabet integer
                    _identifier = (_b & 0x02) << 6;
                    _b = read();
                    _identifier |= (_b & 0xFC) >> 2;
                    
                    decodeOctetsOnSeventhBitOfNonIdentifyingStringOnThirdBit(_b);
                    
                    decodeRestrictedAlphabetAsCharBuffer();
                    
                    if (addToTable) {
                        _characterContentChunkTable.add(_charBuffer, _charBufferLength);
                    }
                    
                    try {
                        _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.CII_EA:
                {
                    final boolean addToTable = (_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0;
                    
                    // Decode encoding algorithm integer
                    _identifier = (_b & 0x02) << 6;
                    _b = read();
                    _identifier |= (_b & 0xFC) >> 2;
                    
                    decodeOctetsOnSeventhBitOfNonIdentifyingStringOnThirdBit(_b);
                    
                    processCIIEncodingAlgorithm(addToTable);
                    break;
                }
                case DecoderStateTables.CII_INDEX_SMALL:
                {
                    final int index = _b & EncodingConstants.INTEGER_4TH_BIT_SMALL_MASK;
                    try {
                        _contentHandler.characters(_characterContentChunkTable._array,
                                _characterContentChunkTable._offset[index],
                                _characterContentChunkTable._length[index]);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.CII_INDEX_MEDIUM:
                {
                    final int index = (((_b & EncodingConstants.INTEGER_4TH_BIT_MEDIUM_MASK) << 8) | read())
                    + EncodingConstants.INTEGER_4TH_BIT_SMALL_LIMIT;
                    try {
                        _contentHandler.characters(_characterContentChunkTable._array,
                                _characterContentChunkTable._offset[index],
                                _characterContentChunkTable._length[index]);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.CII_INDEX_LARGE:
                {
                    final int index = (((_b & EncodingConstants.INTEGER_4TH_BIT_LARGE_MASK) << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.INTEGER_4TH_BIT_MEDIUM_LIMIT;
                    
                    try {
                        _contentHandler.characters(_characterContentChunkTable._array,
                                _characterContentChunkTable._offset[index],
                                _characterContentChunkTable._length[index]);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.CII_INDEX_LARGE_LARGE:
                {
                    final int index = ((read() << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.INTEGER_4TH_BIT_LARGE_LIMIT;
                    
                    try {
                        _contentHandler.characters(_characterContentChunkTable._array,
                                _characterContentChunkTable._offset[index],
                                _characterContentChunkTable._length[index]);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.COMMENT_II:
                    processCommentII();
                    break;
                case DecoderStateTables.PROCESSING_INSTRUCTION_II:
                    processProcessingII();
                    break;
                case DecoderStateTables.UNEXPANDED_ENTITY_REFERENCE_II:
                {
                    String entity_reference_name = decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherNCName);
                    
                    String system_identifier = ((_b & EncodingConstants.UNEXPANDED_ENTITY_SYSTEM_IDENTIFIER_FLAG) > 0)
                    ? decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherURI) : "";
                    String public_identifier = ((_b & EncodingConstants.UNEXPANDED_ENTITY_PUBLIC_IDENTIFIER_FLAG) > 0)
                    ? decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherURI) : "";
                    
                    try {
                        /*
                         * TODO
                         * Need to verify if the skippedEntity method:
                         * http://java.sun.com/j2se/1.4.2/docs/api/org/xml/sax/ContentHandler.html#skippedEntity(java.lang.String)
                         * is the correct method to call. It appears so but a more extensive
                         * check is necessary.
                         */
                        _contentHandler.skippedEntity(entity_reference_name);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processUnexpandedEntityReferenceII", e);
                    }
                    break;
                }
                case DecoderStateTables.TERMINATOR_DOUBLE:
                    _doubleTerminate = true;
                case DecoderStateTables.TERMINATOR_SINGLE:
                    _terminate = true;
                    break;
                default:
                    throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.IllegalStateDecodingEII"));
            }
        }
        
        try {
            _contentHandler.endDocument();
        } catch (SAXException e) {
            throw new FastInfosetException("processDII", e);
        }
    }
    
    protected final void processDIIOptionalProperties() throws FastInfosetException, IOException {
        // Optimize for the most common case
        if (_b == EncodingConstants.DOCUMENT_INITIAL_VOCABULARY_FLAG) {
            decodeInitialVocabulary();
            return;
        }
        
        if ((_b & EncodingConstants.DOCUMENT_ADDITIONAL_DATA_FLAG) > 0) {
            decodeAdditionalData();
            /*
             * TODO
             * how to report the additional data?
             */
        }
        
        if ((_b & EncodingConstants.DOCUMENT_INITIAL_VOCABULARY_FLAG) > 0) {
            decodeInitialVocabulary();
        }
        
        if ((_b & EncodingConstants.DOCUMENT_NOTATIONS_FLAG) > 0) {
            decodeNotations();
            /*
                try {
                    _dtdHandler.notationDecl(name, public_identifier, system_identifier);
                } catch (SAXException e) {
                    throw new IOException("NotationsDeclarationII");
                }
             */
        }
        
        if ((_b & EncodingConstants.DOCUMENT_UNPARSED_ENTITIES_FLAG) > 0) {
            decodeUnparsedEntities();
            /*
                try {
                    _dtdHandler.unparsedEntityDecl(name, public_identifier, system_identifier, notation_name);
                } catch (SAXException e) {
                    throw new IOException("UnparsedEntitiesII");
                }
             */
        }
        
        if ((_b & EncodingConstants.DOCUMENT_CHARACTER_ENCODING_SCHEME) > 0) {
            String characterEncodingScheme = decodeCharacterEncodingScheme();
            /*
             * TODO
             * how to report the character encoding scheme?
             */
        }
        
        if ((_b & EncodingConstants.DOCUMENT_STANDALONE_FLAG) > 0) {
            boolean standalone = (read() > 0) ? true : false ;
            /*
             * TODO
             * how to report the standalone flag?
             */
        }
        
        if ((_b & EncodingConstants.DOCUMENT_VERSION_FLAG) > 0) {
            decodeVersion();
            /*
             * TODO
             * how to report the standalone flag?
             */
        }
    }
    
    protected final void processEII(QualifiedName name, boolean hasAttributes) throws FastInfosetException, IOException {
        if (_prefixTable._currentInScope[name.prefixIndex] != name.namespaceNameIndex) {
            throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.qNameOfEIINotInScope"));
        }
        
        if (hasAttributes) {
            processAIIs();
        }
        
        try {
            _contentHandler.startElement(name.namespaceName, name.localName, name.qName, _attributes);
        } catch (SAXException e) {
            logger.log(Level.FINE, "processEII error", e);
            throw new FastInfosetException("processEII", e);
        }
        
        if (_clearAttributes) {
            _attributes.clear();
            _clearAttributes = false;
        }
        
        while(!_terminate) {
            _b = read();
            switch(DecoderStateTables.EII(_b)) {
                case DecoderStateTables.EII_NO_AIIS_INDEX_SMALL:
                    processEII(_elementNameTable._array[_b], false);
                    break;
                case DecoderStateTables.EII_AIIS_INDEX_SMALL:
                    processEII(_elementNameTable._array[_b & EncodingConstants.INTEGER_3RD_BIT_SMALL_MASK], true);
                    break;
                case DecoderStateTables.EII_INDEX_MEDIUM:
                    processEII(decodeEIIIndexMedium(), (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    break;
                case DecoderStateTables.EII_INDEX_LARGE:
                    processEII(decodeEIIIndexLarge(), (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    break;
                case DecoderStateTables.EII_LITERAL:
                {
                    final QualifiedName qn = decodeLiteralQualifiedName(
                            _b & EncodingConstants.LITERAL_QNAME_PREFIX_NAMESPACE_NAME_MASK,
                            _elementNameTable.getNext());
                    _elementNameTable.add(qn);
                    processEII(qn, (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0);
                    break;
                }
                case DecoderStateTables.EII_NAMESPACES:
                    processEIIWithNamespaces();
                    break;
                case DecoderStateTables.CII_UTF8_SMALL_LENGTH:
                    _octetBufferLength = (_b & EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_SMALL_MASK)
                    + 1;
                    processUtf8CharacterString();
                    break;
                case DecoderStateTables.CII_UTF8_MEDIUM_LENGTH:
                    _octetBufferLength = read() + EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_SMALL_LIMIT;
                    processUtf8CharacterString();
                    break;
                case DecoderStateTables.CII_UTF8_LARGE_LENGTH:
                    _octetBufferLength = ((read() << 24) |
                            (read() << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_MEDIUM_LIMIT;
                    processUtf8CharacterString();
                    break;
                case DecoderStateTables.CII_UTF16_SMALL_LENGTH:
                    _octetBufferLength = (_b & EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_SMALL_MASK)
                    + 1;
                    decodeUtf16StringAsCharBuffer();
                    if ((_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0) {
                        _characterContentChunkTable.add(_charBuffer, _charBufferLength);
                    }
                    
                    try {
                        _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                case DecoderStateTables.CII_UTF16_MEDIUM_LENGTH:
                    _octetBufferLength = read() + EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_SMALL_LIMIT;
                    decodeUtf16StringAsCharBuffer();
                    if ((_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0) {
                        _characterContentChunkTable.add(_charBuffer, _charBufferLength);
                    }
                    
                    try {
                        _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                case DecoderStateTables.CII_UTF16_LARGE_LENGTH:
                    _octetBufferLength = ((read() << 24) |
                            (read() << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.OCTET_STRING_LENGTH_7TH_BIT_MEDIUM_LIMIT;
                    decodeUtf16StringAsCharBuffer();
                    if ((_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0) {
                        _characterContentChunkTable.add(_charBuffer, _charBufferLength);
                    }
                    
                    try {
                        _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                case DecoderStateTables.CII_RA:
                {
                    final boolean addToTable = (_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0;
                    
                    // Decode resitricted alphabet integer
                    _identifier = (_b & 0x02) << 6;
                    _b = read();
                    _identifier |= (_b & 0xFC) >> 2;
                    
                    decodeOctetsOnSeventhBitOfNonIdentifyingStringOnThirdBit(_b);
                    
                    decodeRestrictedAlphabetAsCharBuffer();
                    
                    if (addToTable) {
                        _characterContentChunkTable.add(_charBuffer, _charBufferLength);
                    }
                    
                    try {
                        _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.CII_EA:
                {
                    final boolean addToTable = (_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0;
                    // Decode encoding algorithm integer
                    _identifier = (_b & 0x02) << 6;
                    _b = read();
                    _identifier |= (_b & 0xFC) >> 2;
                    
                    decodeOctetsOnSeventhBitOfNonIdentifyingStringOnThirdBit(_b);
                    
                    processCIIEncodingAlgorithm(addToTable);
                    break;
                }
                case DecoderStateTables.CII_INDEX_SMALL:
                {
                    final int index = _b & EncodingConstants.INTEGER_4TH_BIT_SMALL_MASK;
                    try {
                        _contentHandler.characters(_characterContentChunkTable._array,
                                _characterContentChunkTable._offset[index],
                                _characterContentChunkTable._length[index]);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.CII_INDEX_MEDIUM:
                {
                    final int index = (((_b & EncodingConstants.INTEGER_4TH_BIT_MEDIUM_MASK) << 8) | read())
                    + EncodingConstants.INTEGER_4TH_BIT_SMALL_LIMIT;
                    try {
                        _contentHandler.characters(_characterContentChunkTable._array,
                                _characterContentChunkTable._offset[index],
                                _characterContentChunkTable._length[index]);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.CII_INDEX_LARGE:
                {
                    final int index = (((_b & EncodingConstants.INTEGER_4TH_BIT_LARGE_MASK) << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.INTEGER_4TH_BIT_MEDIUM_LIMIT;
                    
                    try {
                        _contentHandler.characters(_characterContentChunkTable._array,
                                _characterContentChunkTable._offset[index],
                                _characterContentChunkTable._length[index]);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.CII_INDEX_LARGE_LARGE:
                {
                    final int index = ((read() << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.INTEGER_4TH_BIT_LARGE_LIMIT;
                    
                    try {
                        _contentHandler.characters(_characterContentChunkTable._array,
                                _characterContentChunkTable._offset[index],
                                _characterContentChunkTable._length[index]);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processCII", e);
                    }
                    break;
                }
                case DecoderStateTables.COMMENT_II:
                    processCommentII();
                    break;
                case DecoderStateTables.PROCESSING_INSTRUCTION_II:
                    processProcessingII();
                    break;
                case DecoderStateTables.UNEXPANDED_ENTITY_REFERENCE_II:
                {
                    String entity_reference_name = decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherNCName);
                    
                    String system_identifier = ((_b & EncodingConstants.UNEXPANDED_ENTITY_SYSTEM_IDENTIFIER_FLAG) > 0)
                    ? decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherURI) : "";
                    String public_identifier = ((_b & EncodingConstants.UNEXPANDED_ENTITY_PUBLIC_IDENTIFIER_FLAG) > 0)
                    ? decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherURI) : "";
                    
                    try {
                        /*
                         * TODO
                         * Need to verify if the skippedEntity method:
                         * http://java.sun.com/j2se/1.4.2/docs/api/org/xml/sax/ContentHandler.html#skippedEntity(java.lang.String)
                         * is the correct method to call. It appears so but a more extensive
                         * check is necessary.
                         */
                        _contentHandler.skippedEntity(entity_reference_name);
                    } catch (SAXException e) {
                        throw new FastInfosetException("processUnexpandedEntityReferenceII", e);
                    }
                    break;
                }
                case DecoderStateTables.TERMINATOR_DOUBLE:
                    _doubleTerminate = true;
                case DecoderStateTables.TERMINATOR_SINGLE:
                    _terminate = true;
                    break;
                default:
                    throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.IllegalStateDecodingEII"));
            }
        }
        
        _terminate = _doubleTerminate;
        _doubleTerminate = false;
        
        try {
            _contentHandler.endElement(name.namespaceName, name.localName, name.qName);
        } catch (SAXException e) {
            throw new FastInfosetException("processEII", e);
        }
    }
    
    private final void processUtf8CharacterString() throws FastInfosetException, IOException {
        if ((_b & EncodingConstants.CHARACTER_CHUNK_ADD_TO_TABLE_FLAG) > 0) {
            _characterContentChunkTable.ensureSize(_octetBufferLength);
            final int charactersOffset = _characterContentChunkTable._arrayIndex;
            decodeUtf8StringAsCharBuffer(_characterContentChunkTable._array, charactersOffset);
            _characterContentChunkTable.add(_charBufferLength);
            try {
                _contentHandler.characters(_characterContentChunkTable._array, charactersOffset, _charBufferLength);
            } catch (SAXException e) {
                throw new FastInfosetException("processCII", e);
            }
        } else {
            decodeUtf8StringAsCharBuffer();
            try {
                _contentHandler.characters(_charBuffer, 0, _charBufferLength);
            } catch (SAXException e) {
                throw new FastInfosetException("processCII", e);
            }
        }
    }
    
    protected final void processEIIWithNamespaces() throws FastInfosetException, IOException {
        final boolean hasAttributes = (_b & EncodingConstants.ELEMENT_ATTRIBUTE_FLAG) > 0;
        
        _clearAttributes = (_namespacePrefixesFeature) ? true : false;
        
        if (++_prefixTable._declarationId == Integer.MAX_VALUE) {
            _prefixTable.clearDeclarationIds();
        }
        
        String prefix = "", namespaceName = "";
        final int start = _namespacePrefixesIndex;
        int b = read();
        while ((b & EncodingConstants.NAMESPACE_ATTRIBUTE_MASK) == EncodingConstants.NAMESPACE_ATTRIBUTE) {
            if (_namespacePrefixesIndex == _namespacePrefixes.length) {
                final int[] namespaceAIIs = new int[_namespacePrefixesIndex * 3 / 2 + 1];
                System.arraycopy(_namespacePrefixes, 0, namespaceAIIs, 0, _namespacePrefixesIndex);
                _namespacePrefixes = namespaceAIIs;
            }
            
            switch (b & EncodingConstants.NAMESPACE_ATTRIBUTE_PREFIX_NAME_MASK) {
                // no prefix, no namespace
                // Undeclaration of default namespace
                case 0:
                    prefix = namespaceName = "";
                    _namespaceNameIndex = _prefixIndex = _namespacePrefixes[_namespacePrefixesIndex++] = -1;
                    break;
                    // no prefix, namespace
                    // Declaration of default namespace
                case 1:
                    prefix = "";
                    namespaceName = decodeIdentifyingNonEmptyStringOnFirstBitAsNamespaceName(false);
                    
                    _prefixIndex = _namespacePrefixes[_namespacePrefixesIndex++] = -1;
                    break;
                    // prefix, no namespace
                    // Undeclaration of namespace
                case 2:
                    prefix = decodeIdentifyingNonEmptyStringOnFirstBitAsPrefix(false);
                    namespaceName = "";
                    
                    _namespaceNameIndex = -1;
                    _namespacePrefixes[_namespacePrefixesIndex++] = _prefixIndex;
                    break;
                    // prefix, namespace
                    // Declaration of prefixed namespace
                case 3:
                    prefix = decodeIdentifyingNonEmptyStringOnFirstBitAsPrefix(true);
                    namespaceName = decodeIdentifyingNonEmptyStringOnFirstBitAsNamespaceName(true);
                    
                    _namespacePrefixes[_namespacePrefixesIndex++] = _prefixIndex;
                    break;
            }
            
            _prefixTable.pushScope(_prefixIndex, _namespaceNameIndex);
            
            if (_namespacePrefixesFeature) {
                // Add the namespace delcaration as an attribute
                if (prefix != "") {
                    _attributes.addAttribute(new QualifiedName(
                            EncodingConstants.XMLNS_NAMESPACE_PREFIX,
                            EncodingConstants.XMLNS_NAMESPACE_NAME,
                            prefix),
                            namespaceName);
                } else {
                    _attributes.addAttribute(EncodingConstants.DEFAULT_NAMESPACE_DECLARATION,
                            namespaceName);
                }
            }
            
            try {
                _contentHandler.startPrefixMapping(prefix, namespaceName);
            } catch (SAXException e) {
                throw new IOException("processStartNamespaceAII");
            }
            
            b = read();
        }
        if (b != EncodingConstants.TERMINATOR) {
            throw new IOException(CommonResourceBundle.getInstance().getString("message.EIInamespaceNameNotTerminatedCorrectly"));
        }
        final int end = _namespacePrefixesIndex;
        
        _b = read();
        switch(DecoderStateTables.EII(_b)) {
            case DecoderStateTables.EII_NO_AIIS_INDEX_SMALL:
                processEII(_elementNameTable._array[_b], hasAttributes);
                break;
            case DecoderStateTables.EII_INDEX_MEDIUM:
                processEII(decodeEIIIndexMedium(), hasAttributes);
                break;
            case DecoderStateTables.EII_INDEX_LARGE:
                processEII(decodeEIIIndexLarge(), hasAttributes);
                break;
            case DecoderStateTables.EII_LITERAL:
            {
                final QualifiedName qn = decodeLiteralQualifiedName(
                        _b & EncodingConstants.LITERAL_QNAME_PREFIX_NAMESPACE_NAME_MASK,
                        _elementNameTable.getNext());
                _elementNameTable.add(qn);
                processEII(qn, hasAttributes);
                break;
            }
            default:
                throw new IOException(CommonResourceBundle.getInstance().getString("message.IllegalStateDecodingEIIAfterAIIs"));
        }
        
        try {
            for (int i = end - 1; i >= start; i--) {
                final int prefixIndex = _namespacePrefixes[i];
                _prefixTable.popScope(prefixIndex);
                prefix = (prefixIndex > 0) ? _prefixTable.get(prefixIndex - 1) :
                    (prefixIndex == -1) ? "" : EncodingConstants.XML_NAMESPACE_PREFIX;
                _contentHandler.endPrefixMapping(prefix);
            }
            _namespacePrefixesIndex = start;
        } catch (SAXException e) {
            throw new IOException("processStartNamespaceAII");
        }
    }
    
    protected final void processAIIs() throws FastInfosetException, IOException {
        QualifiedName name;
        int b;
        String value;
        
        _clearAttributes = true;
        
        if (++_duplicateAttributeVerifier._currentIteration == Integer.MAX_VALUE) {
            _duplicateAttributeVerifier.clear();
        }
        
        do {
            // AII qualified name
            b = read();
            switch (DecoderStateTables.AII(b)) {
                case DecoderStateTables.AII_INDEX_SMALL:
                    name = _attributeNameTable._array[b];
                    break;
                case DecoderStateTables.AII_INDEX_MEDIUM:
                {
                    final int i = (((b & EncodingConstants.INTEGER_2ND_BIT_MEDIUM_MASK) << 8) | read())
                    + EncodingConstants.INTEGER_2ND_BIT_SMALL_LIMIT;
                    name = _attributeNameTable._array[i];
                    break;
                }
                case DecoderStateTables.AII_INDEX_LARGE:
                {
                    final int i = (((b & EncodingConstants.INTEGER_2ND_BIT_LARGE_MASK) << 16) | (read() << 8) | read())
                    + EncodingConstants.INTEGER_2ND_BIT_MEDIUM_LIMIT;
                    name = _attributeNameTable._array[i];
                    break;
                }
                case DecoderStateTables.AII_LITERAL:
                    name = decodeLiteralQualifiedName(
                            b & EncodingConstants.LITERAL_QNAME_PREFIX_NAMESPACE_NAME_MASK,
                            _attributeNameTable.getNext());
                    name.createAttributeValues(_duplicateAttributeVerifier.MAP_SIZE);
                    _attributeNameTable.add(name);
                    break;
                case DecoderStateTables.AII_TERMINATOR_DOUBLE:
                    _doubleTerminate = true;
                case DecoderStateTables.AII_TERMINATOR_SINGLE:
                    _terminate = true;
                    // AIIs have finished break out of loop
                    continue;
                default:
                    throw new IOException(CommonResourceBundle.getInstance().getString("message.decodingAIIs"));
            }
            
            if (name.prefixIndex > 0 && _prefixTable._currentInScope[name.prefixIndex] != name.namespaceNameIndex) {
                throw new FastInfosetException(CommonResourceBundle.getInstance().getString("message.AIIqNameNotInScope"));
            }
            
            _duplicateAttributeVerifier.checkForDuplicateAttribute(name.attributeHash, name.attributeId);
            
            // [normalized value] of AII
            
            b = read();
            switch(DecoderStateTables.NISTRING(b)) {
                case DecoderStateTables.NISTRING_UTF8_SMALL_LENGTH:
                    _octetBufferLength = (b & EncodingConstants.OCTET_STRING_LENGTH_5TH_BIT_SMALL_MASK) + 1;
                    value = decodeUtf8StringAsString();
                    if ((b & EncodingConstants.NISTRING_ADD_TO_TABLE_FLAG) > 0) {
                        _attributeValueTable.add(value);
                    }
                    
                    _attributes.addAttribute(name, value);
                    break;
                case DecoderStateTables.NISTRING_UTF8_MEDIUM_LENGTH:
                    _octetBufferLength = read() + EncodingConstants.OCTET_STRING_LENGTH_5TH_BIT_SMALL_LIMIT;
                    value = decodeUtf8StringAsString();
                    if ((b & EncodingConstants.NISTRING_ADD_TO_TABLE_FLAG) > 0) {
                        _attributeValueTable.add(value);
                    }
                    
                    _attributes.addAttribute(name, value);
                    break;
                case DecoderStateTables.NISTRING_UTF8_LARGE_LENGTH:
                    _octetBufferLength = ((read() << 24) |
                            (read() << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.OCTET_STRING_LENGTH_5TH_BIT_MEDIUM_LIMIT;
                    value = decodeUtf8StringAsString();
                    if ((b & EncodingConstants.NISTRING_ADD_TO_TABLE_FLAG) > 0) {
                        _attributeValueTable.add(value);
                    }
                    
                    _attributes.addAttribute(name, value);
                    break;
                case DecoderStateTables.NISTRING_UTF16_SMALL_LENGTH:
                    _octetBufferLength = (b & EncodingConstants.OCTET_STRING_LENGTH_5TH_BIT_SMALL_MASK) + 1;
                    value = decodeUtf16StringAsString();
                    if ((b & EncodingConstants.NISTRING_ADD_TO_TABLE_FLAG) > 0) {
                        _attributeValueTable.add(value);
                    }
                    
                    _attributes.addAttribute(name, value);
                    break;
                case DecoderStateTables.NISTRING_UTF16_MEDIUM_LENGTH:
                    _octetBufferLength = read() + EncodingConstants.OCTET_STRING_LENGTH_5TH_BIT_SMALL_LIMIT;
                    value = decodeUtf16StringAsString();
                    if ((b & EncodingConstants.NISTRING_ADD_TO_TABLE_FLAG) > 0) {
                        _attributeValueTable.add(value);
                    }
                    
                    _attributes.addAttribute(name, value);
                    break;
                case DecoderStateTables.NISTRING_UTF16_LARGE_LENGTH:
                    _octetBufferLength = ((read() << 24) |
                            (read() << 16) |
                            (read() << 8) |
                            read())
                            + EncodingConstants.OCTET_STRING_LENGTH_5TH_BIT_MEDIUM_LIMIT;
                    value = decodeUtf16StringAsString();
                    if ((b & EncodingConstants.NISTRING_ADD_TO_TABLE_FLAG) > 0) {
                        _attributeValueTable.add(value);
                    }
                    
                    _attributes.addAttribute(name, value);
                    break;
                case DecoderStateTables.NISTRING_RA:
                {
                    final boolean addToTable = (b & EncodingConstants.NISTRING_ADD_TO_TABLE_FLAG) > 0;
                    // Decode resitricted alphabet integer
                    _identifier = (b & 0x0F) << 4;
                    b = read();
                    _identifier |= (b & 0xF0) >> 4;
                    
                    decodeOctetsOnFifthBitOfNonIdentifyingStringOnFirstBit(b);
                    
                    value = decodeRestrictedAlphabetAsString();
                    if (addToTable) {
                        _attributeValueTable.add(value);
                    }
                    
                    _attributes.addAttribute(name, value);
                    break;
                }
                case DecoderStateTables.NISTRING_EA:
                {
                    final boolean addToTable = (b & EncodingConstants.NISTRING_ADD_TO_TABLE_FLAG) > 0;

                    _identifier = (b & 0x0F) << 4;
                    b = read();
                    _identifier |= (b & 0xF0) >> 4;
                    
                    decodeOctetsOnFifthBitOfNonIdentifyingStringOnFirstBit(b);
                    
                    processAIIEncodingAlgorithm(name, addToTable);
                    break;
                }
                case DecoderStateTables.NISTRING_INDEX_SMALL:
                    _attributes.addAttribute(name,
                            _attributeValueTable._array[b & EncodingConstants.INTEGER_2ND_BIT_SMALL_MASK]);
                    break;
                case DecoderStateTables.NISTRING_INDEX_MEDIUM:
                {
                    final int index = (((b & EncodingConstants.INTEGER_2ND_BIT_MEDIUM_MASK) << 8) | read())
                    + EncodingConstants.INTEGER_2ND_BIT_SMALL_LIMIT;
                    
                    _attributes.addAttribute(name,
                            _attributeValueTable._array[index]);
                    break;
                }
                case DecoderStateTables.NISTRING_INDEX_LARGE:
                {
                    final int index = (((b & EncodingConstants.INTEGER_2ND_BIT_LARGE_MASK) << 16) | (read() << 8) | read())
                    + EncodingConstants.INTEGER_2ND_BIT_MEDIUM_LIMIT;
                    
                    _attributes.addAttribute(name,
                            _attributeValueTable._array[index]);
                    break;
                }
                case DecoderStateTables.NISTRING_EMPTY:
                    _attributes.addAttribute(name, "");
                    break;
                default:
                    throw new IOException(CommonResourceBundle.getInstance().getString("message.decodingAIIValue"));
            }
            
        } while (!_terminate);
        
        // Reset duplication attribute verfifier
        _duplicateAttributeVerifier._poolCurrent = _duplicateAttributeVerifier._poolHead;
        
        _terminate = _doubleTerminate;
        _doubleTerminate = false;
    }
    
    protected final void processCommentII() throws FastInfosetException, IOException {
        switch(decodeNonIdentifyingStringOnFirstBit()) {
            case NISTRING_STRING:
                if (_addToTable) {
                    _v.otherString.add(new CharArray(_charBuffer, 0, _charBufferLength, true));
                }
                
                try {
                    _lexicalHandler.comment(_charBuffer, 0, _charBufferLength);
                } catch (SAXException e) {
                    throw new FastInfosetException("processCommentII", e);
                }
                break;
            case NISTRING_ENCODING_ALGORITHM:
                throw new IOException(CommonResourceBundle.getInstance().getString("message.commentIIAlgorithmNotSupported"));
            case NISTRING_INDEX:
                final CharArray ca = _v.otherString.get(_integer);
                
                try {
                    _lexicalHandler.comment(ca.ch, ca.start, ca.length);
                } catch (SAXException e) {
                    throw new FastInfosetException("processCommentII", e);
                }
                break;
            case NISTRING_EMPTY_STRING:
                try {
                    _lexicalHandler.comment(_charBuffer, 0, 0);
                } catch (SAXException e) {
                    throw new FastInfosetException("processCommentII", e);
                }
                break;
        }
    }
    
    protected final void processProcessingII() throws FastInfosetException, IOException {
        final String target = decodeIdentifyingNonEmptyStringOnFirstBit(_v.otherNCName);
        
        switch(decodeNonIdentifyingStringOnFirstBit()) {
            case NISTRING_STRING:
                final String data = new String(_charBuffer, 0, _charBufferLength);
                if (_addToTable) {
                    _v.otherString.add(new CharArrayString(data));
                }
                try {
                    _contentHandler.processingInstruction(target, data);
                } catch (SAXException e) {
                    throw new FastInfosetException("processProcessingII", e);
                }
                break;
            case NISTRING_ENCODING_ALGORITHM:
                throw new IOException(CommonResourceBundle.getInstance().getString("message.processingIIWithEncodingAlgorithm"));
            case NISTRING_INDEX:
                try {
                    _contentHandler.processingInstruction(target, _v.otherString.get(_integer).toString());
                } catch (SAXException e) {
                    throw new FastInfosetException("processProcessingII", e);
                }
                break;
            case NISTRING_EMPTY_STRING:
                try {
                    _contentHandler.processingInstruction(target, "");
                } catch (SAXException e) {
                    throw new FastInfosetException("processProcessingII", e);
                }
                break;
        }
    }
    
    protected final void processCIIEncodingAlgorithm(boolean addToTable) throws FastInfosetException, IOException {
        if (_identifier < EncodingConstants.ENCODING_ALGORITHM_BUILTIN_END) {
            if (_primitiveHandler != null) {
                processCIIBuiltInEncodingAlgorithmAsPrimitive();
            } else if (_algorithmHandler != null) {
                Object array = processBuiltInEncodingAlgorithmAsObject();
                
                try {
                    _algorithmHandler.object(null, _identifier, array);
                } catch (SAXException e) {
                    throw new FastInfosetException(e);
                }
            } else {
                StringBuffer buffer = new StringBuffer();
                processBuiltInEncodingAlgorithmAsCharacters(buffer);
                
                try {
                    _contentHandler.characters(buffer.toString().toCharArray(), 0, buffer.length());
                } catch (SAXException e) {
                    throw new FastInfosetException(e);
                }
            }
            
            if (addToTable) {
                StringBuffer buffer = new StringBuffer();
                processBuiltInEncodingAlgorithmAsCharacters(buffer);
                _characterContentChunkTable.add(buffer.toString().toCharArray(), buffer.length());
            }
        } else if (_identifier == EncodingAlgorithmIndexes.CDATA) {
            // Set back buffer position to start of encoded string
            _octetBufferOffset -= _octetBufferLength;
            decodeUtf8StringIntoCharBuffer();
            
            try {
                _lexicalHandler.startCDATA();
                _contentHandler.characters(_charBuffer, 0, _charBufferLength);
                _lexicalHandler.endCDATA();
            } catch (SAXException e) {
                throw new FastInfosetException(e);
            }
            
            if (addToTable) {
                _characterContentChunkTable.add(_charBuffer, _charBufferLength);
            }
        } else if (_identifier >= EncodingConstants.ENCODING_ALGORITHM_APPLICATION_START && _algorithmHandler != null) {
            final String URI = _v.encodingAlgorithm.get(_identifier - EncodingConstants.ENCODING_ALGORITHM_APPLICATION_START);
            if (URI == null) {
                throw new EncodingAlgorithmException(CommonResourceBundle.getInstance().
                        getString("message.URINotPresent", new Object[]{Integer.valueOf(_identifier)}));
            }
            
            final EncodingAlgorithm ea = (EncodingAlgorithm)_registeredEncodingAlgorithms.get(URI);
            if (ea != null) {
                final Object data = ea.decodeFromBytes(_octetBuffer, _octetBufferStart, _octetBufferLength);
                try {
                    _algorithmHandler.object(URI, _identifier, data);
                } catch (SAXException e) {
                    throw new FastInfosetException(e);
                }
            } else {
                try {
                    _algorithmHandler.octets(URI, _identifier, _octetBuffer, _octetBufferStart, _octetBufferLength);
                } catch (SAXException e) {
                    throw new FastInfosetException(e);
                }
            }
            if (addToTable) {
                throw new EncodingAlgorithmException(CommonResourceBundle.getInstance().getString("message.addToTableNotSupported"));
            }
        } else if (_identifier >= EncodingConstants.ENCODING_ALGORITHM_APPLICATION_START) {
            // TODO should have property to ignore
            throw new EncodingAlgorithmException(
                    CommonResourceBundle.getInstance().getString("message.algorithmDataCannotBeReported"));
        } else {
            // Reserved built-in algorithms for future use
            // TODO should use sax property to decide if event will be
            // reported, allows for support through handler if required.
            throw new EncodingAlgorithmException(CommonResourceBundle.getInstance().getString("message.identifiers10to31Reserved"));
        }
    }
    
    protected final void processCIIBuiltInEncodingAlgorithmAsPrimitive() throws FastInfosetException, IOException {
        try {
            int length;
            switch(_identifier) {
                case EncodingAlgorithmIndexes.HEXADECIMAL:
                case EncodingAlgorithmIndexes.BASE64:
                    _primitiveHandler.bytes(_octetBuffer, _octetBufferStart, _octetBufferLength);
                    break;
                case EncodingAlgorithmIndexes.SHORT:
                    length = BuiltInEncodingAlgorithmFactory.shortEncodingAlgorithm.
                            getPrimtiveLengthFromOctetLength(_octetBufferLength);
                    if (length > builtInAlgorithmState.shortArray.length) {
                        final short[] array = new short[length * 3 / 2 + 1];
                        System.arraycopy(builtInAlgorithmState.shortArray, 0,
                                array, 0, builtInAlgorithmState.shortArray.length);
                        builtInAlgorithmState.shortArray = array;
                    }
                    
                    BuiltInEncodingAlgorithmFactory.shortEncodingAlgorithm.
                            decodeFromBytesToShortArray(builtInAlgorithmState.shortArray, 0,
                            _octetBuffer, _octetBufferStart, _octetBufferLength);
                    _primitiveHandler.shorts(builtInAlgorithmState.shortArray, 0, length);
                    break;
                case EncodingAlgorithmIndexes.INT:
                    length = BuiltInEncodingAlgorithmFactory.intEncodingAlgorithm.
                            getPrimtiveLengthFromOctetLength(_octetBufferLength);
                    if (length > builtInAlgorithmState.intArray.length) {
                        final int[] array = new int[length * 3 / 2 + 1];
                        System.arraycopy(builtInAlgorithmState.intArray, 0,
                                array, 0, builtInAlgorithmState.intArray.length);
                        builtInAlgorithmState.intArray = array;
                    }
                    
                    BuiltInEncodingAlgorithmFactory.intEncodingAlgorithm.
                            decodeFromBytesToIntArray(builtInAlgorithmState.intArray, 0,
                            _octetBuffer, _octetBufferStart, _octetBufferLength);
                    _primitiveHandler.ints(builtInAlgorithmState.intArray, 0, length);
                    break;
                case EncodingAlgorithmIndexes.LONG:
                    length = BuiltInEncodingAlgorithmFactory.longEncodingAlgorithm.
                            getPrimtiveLengthFromOctetLength(_octetBufferLength);
                    if (length > builtInAlgorithmState.longArray.length) {
                        final long[] array = new long[length * 3 / 2 + 1];
                        System.arraycopy(builtInAlgorithmState.longArray, 0,
                                array, 0, builtInAlgorithmState.longArray.length);
                        builtInAlgorithmState.longArray = array;
                    }
                    
                    BuiltInEncodingAlgorithmFactory.longEncodingAlgorithm.
                            decodeFromBytesToLongArray(builtInAlgorithmState.longArray, 0,
                            _octetBuffer, _octetBufferStart, _octetBufferLength);
                    _primitiveHandler.longs(builtInAlgorithmState.longArray, 0, length);
                    break;
                case EncodingAlgorithmIndexes.BOOLEAN:
                    length = BuiltInEncodingAlgorithmFactory.booleanEncodingAlgorithm.
                            getPrimtiveLengthFromOctetLength(_octetBufferLength, _octetBuffer[_octetBufferStart] & 0xFF);
                    if (length > builtInAlgorithmState.booleanArray.length) {
                        final boolean[] array = new boolean[length * 3 / 2 + 1];
                        System.arraycopy(builtInAlgorithmState.booleanArray, 0,
                                array, 0, builtInAlgorithmState.booleanArray.length);
                        builtInAlgorithmState.booleanArray = array;
                    }
                    
                    BuiltInEncodingAlgorithmFactory.booleanEncodingAlgorithm.
                            decodeFromBytesToBooleanArray(
                            builtInAlgorithmState.booleanArray, 0, length,
                            _octetBuffer, _octetBufferStart, _octetBufferLength);
                    _primitiveHandler.booleans(builtInAlgorithmState.booleanArray, 0, length);
                    break;
                case EncodingAlgorithmIndexes.FLOAT:
                    length = BuiltInEncodingAlgorithmFactory.floatEncodingAlgorithm.
                            getPrimtiveLengthFromOctetLength(_octetBufferLength);
                    if (length > builtInAlgorithmState.floatArray.length) {
                        final float[] array = new float[length * 3 / 2 + 1];
                        System.arraycopy(builtInAlgorithmState.floatArray, 0,
                                array, 0, builtInAlgorithmState.floatArray.length);
                        builtInAlgorithmState.floatArray = array;
                    }
                    
                    BuiltInEncodingAlgorithmFactory.floatEncodingAlgorithm.
                            decodeFromBytesToFloatArray(builtInAlgorithmState.floatArray, 0,
                            _octetBuffer, _octetBufferStart, _octetBufferLength);
                    _primitiveHandler.floats(builtInAlgorithmState.floatArray, 0, length);
                    break;
                case EncodingAlgorithmIndexes.DOUBLE:
                    length = BuiltInEncodingAlgorithmFactory.doubleEncodingAlgorithm.
                            getPrimtiveLengthFromOctetLength(_octetBufferLength);
                    if (length > builtInAlgorithmState.doubleArray.length) {
                        final double[] array = new double[length * 3 / 2 + 1];
                        System.arraycopy(builtInAlgorithmState.doubleArray, 0,
                                array, 0, builtInAlgorithmState.doubleArray.length);
                        builtInAlgorithmState.doubleArray = array;
                    }
                    
                    BuiltInEncodingAlgorithmFactory.doubleEncodingAlgorithm.
                            decodeFromBytesToDoubleArray(builtInAlgorithmState.doubleArray, 0,
                            _octetBuffer, _octetBufferStart, _octetBufferLength);
                    _primitiveHandler.doubles(builtInAlgorithmState.doubleArray, 0, length);
                    break;
                case EncodingAlgorithmIndexes.UUID:
                    length = BuiltInEncodingAlgorithmFactory.uuidEncodingAlgorithm.
                            getPrimtiveLengthFromOctetLength(_octetBufferLength);
                    if (length > builtInAlgorithmState.longArray.length) {
                        final long[] array = new long[length * 3 / 2 + 1];
                        System.arraycopy(builtInAlgorithmState.longArray, 0,
                                array, 0, builtInAlgorithmState.longArray.length);
                        builtInAlgorithmState.longArray = array;
                    }
                    
                    BuiltInEncodingAlgorithmFactory.uuidEncodingAlgorithm.
                            decodeFromBytesToLongArray(builtInAlgorithmState.longArray, 0,
                            _octetBuffer, _octetBufferStart, _octetBufferLength);
                    _primitiveHandler.uuids(builtInAlgorithmState.longArray, 0, length);
                    break;
                case EncodingAlgorithmIndexes.CDATA:
                    throw new UnsupportedOperationException("CDATA");
                default:
                    throw new FastInfosetException(CommonResourceBundle.getInstance().
                            getString("message.unsupportedAlgorithm", new Object[]{Integer.valueOf(_identifier)}));
            }
        } catch (SAXException e) {
            throw new FastInfosetException(e);
        }
    }
    
    
    protected final void processAIIEncodingAlgorithm(QualifiedName name, boolean addToTable) throws FastInfosetException, IOException {
        if (_identifier < EncodingConstants.ENCODING_ALGORITHM_BUILTIN_END) {
            if (_primitiveHandler != null || _algorithmHandler != null) {
                Object data = processBuiltInEncodingAlgorithmAsObject();
                _attributes.addAttributeWithAlgorithmData(name, null, _identifier, data);
            } else {
                StringBuffer buffer = new StringBuffer();
                processBuiltInEncodingAlgorithmAsCharacters(buffer);
                _attributes.addAttribute(name, buffer.toString());
            }
        } else if (_identifier >= EncodingConstants.ENCODING_ALGORITHM_APPLICATION_START && _algorithmHandler != null) {
            final String URI = _v.encodingAlgorithm.get(_identifier - EncodingConstants.ENCODING_ALGORITHM_APPLICATION_START);
            if (URI == null) {
                throw new EncodingAlgorithmException(CommonResourceBundle.getInstance().
                        getString("message.URINotPresent", new Object[]{Integer.valueOf(_identifier)}));
            }
            
            final EncodingAlgorithm ea = (EncodingAlgorithm)_registeredEncodingAlgorithms.get(URI);
            if (ea != null) {
                final Object data = ea.decodeFromBytes(_octetBuffer, _octetBufferStart, _octetBufferLength);
                _attributes.addAttributeWithAlgorithmData(name, URI, _identifier, data);
            } else {
                final byte[] data = new byte[_octetBufferLength];
                System.arraycopy(_octetBuffer, _octetBufferStart, data, 0, _octetBufferLength);
                _attributes.addAttributeWithAlgorithmData(name, URI, _identifier, data);
            }
        } else if (_identifier >= EncodingConstants.ENCODING_ALGORITHM_APPLICATION_START) {
            // TODO should have property to ignore
            throw new EncodingAlgorithmException(
                    CommonResourceBundle.getInstance().getString("message.algorithmDataCannotBeReported"));
        } else if (_identifier == EncodingAlgorithmIndexes.CDATA) {
            throw new EncodingAlgorithmException(CommonResourceBundle.getInstance().getString("message.CDATAAlgorithmNotSupported"));
        } else {
            // Reserved built-in algorithms for future use
            // TODO should use sax property to decide if event will be
            // reported, allows for support through handler if required.
            throw new EncodingAlgorithmException(CommonResourceBundle.getInstance().getString("message.identifiers10to31Reserved"));
        }

        if (addToTable) {
            _attributeValueTable.add(_attributes.getValue(_attributes.getIndex(name.qName)));
        }
    }
    
    protected final void processBuiltInEncodingAlgorithmAsCharacters(StringBuffer buffer) throws FastInfosetException, IOException {
        // TODO not very efficient, need to reuse buffers
        Object array = BuiltInEncodingAlgorithmFactory.getAlgorithm(_identifier).
                decodeFromBytes(_octetBuffer, _octetBufferStart, _octetBufferLength);
        
        BuiltInEncodingAlgorithmFactory.getAlgorithm(_identifier).convertToCharacters(array,  buffer);
    }
    
    protected final Object processBuiltInEncodingAlgorithmAsObject() throws FastInfosetException, IOException {
        return BuiltInEncodingAlgorithmFactory.getAlgorithm(_identifier).
                decodeFromBytes(_octetBuffer, _octetBufferStart, _octetBufferLength);
    }
}
