/*
 * 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.stax;

import com.sun.xml.fastinfoset.Encoder;
import com.sun.xml.fastinfoset.EncodingConstants;
import com.sun.xml.fastinfoset.util.NamespaceContextImplementation;
import java.io.IOException;
import java.io.OutputStream;
import java.util.EmptyStackException;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.jvnet.fastinfoset.EncodingAlgorithmIndexes;
import com.sun.xml.fastinfoset.CommonResourceBundle;
import com.sun.xml.fastinfoset.QualifiedName;
import com.sun.xml.fastinfoset.util.LocalNameQualifiedNamesMap;
import org.jvnet.fastinfoset.stax.LowLevelFastInfosetStreamWriter;

/**
 * The Fast Infoset StAX serializer.
 * <p>
 * Instantiate this serializer to serialize a fast infoset document in accordance 
 * with the StAX API.
 * 
 * <p>
 * More than one fast infoset document may be encoded to the 
 * {@link java.io.OutputStream}.
 */
public class StAXDocumentSerializer extends Encoder 
        implements XMLStreamWriter, LowLevelFastInfosetStreamWriter {
    protected StAXManager _manager;
    
    protected String _encoding;
    /**
     * Local name of current element.
     */
    protected String _currentLocalName;
    
    /**
     * Namespace of current element.
     */
    protected String _currentUri;
    
    /**
     * Prefix of current element.
     */
    protected String _currentPrefix;

   /**
     * This flag indicates when there is a pending start element event.
     */
    protected boolean _inStartElement = false;
    
    /**
     * This flag indicates if the current element is empty.
     */
    protected boolean _isEmptyElement = false;

    /**
     * List of attributes qnames and values defined in the current element.
     */
    protected String[] _attributesArray = new String[4 * 16];
    protected int _attributesArrayIndex = 0;
    
    protected boolean[] _nsSupportContextStack = new boolean[32];
    protected int _stackCount = -1;    
    
    /**
     * Mapping between uris and prefixes.
     */
    protected NamespaceContextImplementation _nsContext = 
            new NamespaceContextImplementation();

    /**
     * List of namespaces defined in the current element.
     */
    protected String[] _namespacesArray = new String[2 * 8];
    protected int _namespacesArrayIndex = 0;
    
    public StAXDocumentSerializer() {
        super(true);
        _manager = new StAXManager(StAXManager.CONTEXT_WRITER);
    }
    
    public StAXDocumentSerializer(OutputStream outputStream) {
        super(true);
        setOutputStream(outputStream);
        _manager = new StAXManager(StAXManager.CONTEXT_WRITER);
    }

    public StAXDocumentSerializer(OutputStream outputStream, StAXManager manager) {
        super(true);
        setOutputStream(outputStream);
        _manager = manager;
    }
    
    public void reset() {
        super.reset();
        
        _attributesArrayIndex = 0;
        _namespacesArrayIndex = 0;
        
        _nsContext.reset();
        _stackCount = -1;
                
        _currentUri = _currentPrefix = null;
        _currentLocalName = null;
        
        _inStartElement = _isEmptyElement = false;
    }
    
    // -- XMLStreamWriter Interface -------------------------------------------
            
    public void writeStartDocument() throws XMLStreamException {
        writeStartDocument("finf", "1.0");
    }
    
    public void writeStartDocument(String version) throws XMLStreamException {
        writeStartDocument("finf", version);
    }
    
    public void writeStartDocument(String encoding, String version)
        throws XMLStreamException
    {
        reset();
        
        try {
            encodeHeader(false);
            encodeInitialVocabulary();
        } catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }
    
    public void writeEndDocument() throws XMLStreamException {
        try {
            
            // terminate all elements not terminated
            // by writeEndElement
            for(;_stackCount >= 0; _stackCount--) {
                writeEndElement();
            }

            encodeDocumentTermination();
        } 
        catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }
    
    public void close() throws XMLStreamException {
        reset();
    }
    
    public void flush() throws XMLStreamException {
        try {
            _s.flush();
        }
        catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }
    
    public void writeStartElement(String localName)
        throws XMLStreamException
    {
        // TODO is it necessary for FI to obtain the default namespace in scope?
        writeStartElement("", localName, "");
    }
    
    public void writeStartElement(String namespaceURI, String localName)
        throws XMLStreamException
    {
        writeStartElement("", localName, namespaceURI);
    }
    
    public void writeStartElement(String prefix, String localName,
        String namespaceURI) throws XMLStreamException
    {
        encodeTerminationAndCurrentElement(false);
              
        _inStartElement = true;
        _isEmptyElement = false;

        _currentLocalName = localName;
        _currentPrefix = prefix;
        _currentUri = namespaceURI;

        _stackCount++;
        if (_stackCount == _nsSupportContextStack.length) {
            boolean[] nsSupportContextStack = new boolean[_stackCount * 2];
            System.arraycopy(_nsSupportContextStack, 0, nsSupportContextStack, 0, _nsSupportContextStack.length);
            _nsSupportContextStack = nsSupportContextStack;
        }
        
        _nsSupportContextStack[_stackCount] = false;
    }
    
    public void writeEmptyElement(String localName)
        throws XMLStreamException
    {
        writeEmptyElement("", localName, "");
    }
    
    public void writeEmptyElement(String namespaceURI, String localName)
        throws XMLStreamException
    {
        writeEmptyElement("", localName, namespaceURI);
    }
    
    public void writeEmptyElement(String prefix, String localName, 
        String namespaceURI) throws XMLStreamException
    {
        encodeTerminationAndCurrentElement(false);
        
        _isEmptyElement = _inStartElement = true;
        
        _currentLocalName = localName;
        _currentPrefix = prefix;
        _currentUri = namespaceURI;
        
        _stackCount++;
        if (_stackCount == _nsSupportContextStack.length) {
            boolean[] nsSupportContextStack = new boolean[_stackCount * 2];
            System.arraycopy(_nsSupportContextStack, 0, nsSupportContextStack, 0, _nsSupportContextStack.length);
            _nsSupportContextStack = nsSupportContextStack;
        }
        
        _nsSupportContextStack[_stackCount] = false;
    }
        
    public void writeEndElement() throws XMLStreamException {
        if (_inStartElement) {
            encodeTerminationAndCurrentElement(false);
        }
            
        try {            
            encodeElementTermination();
            if (_nsSupportContextStack[_stackCount--] == true) {
                _nsContext.popContext();
            }
        }
        catch (IOException e) {
            throw new XMLStreamException(e);
        }
        catch (EmptyStackException e) {
            throw new XMLStreamException(e);
        }
    }

    
    public void writeAttribute(String localName, String value)
        throws XMLStreamException
    {
        writeAttribute("", "", localName, value);
    }
    
    public void writeAttribute(String namespaceURI, String localName,
        String value) throws XMLStreamException
    {
        String prefix = "";
        
        // Find prefix for attribute, ignoring default namespace
        if (namespaceURI.length() > 0) {            
            prefix = _nsContext.getNonDefaultPrefix(namespaceURI);

            // Undeclared prefix or ignorable default ns?
            if (prefix == null || prefix.length() == 0) {
                // Workaround for BUG in SAX NamespaceSupport helper
                // which incorrectly defines namespace declaration URI
                if (namespaceURI == EncodingConstants.XMLNS_NAMESPACE_NAME || 
                        namespaceURI.equals(EncodingConstants.XMLNS_NAMESPACE_NAME)) {
                    // TODO
                    // Need to check carefully the rule for the writing of
                    // namespaces in StAX. Is it safe to ignore such 
                    // attributes, as declarations will be made using the
                    // writeNamespace method
                    return;
                }
                throw new XMLStreamException(CommonResourceBundle.getInstance().getString("message.URIUnbound", new Object[]{namespaceURI}));
            }
        }
        writeAttribute(prefix, namespaceURI, localName, value);
    }
        
    public void writeAttribute(String prefix, String namespaceURI,
        String localName, String value) throws XMLStreamException
    {
        if (!_inStartElement) {
            throw new IllegalStateException(CommonResourceBundle.getInstance().getString("message.attributeWritingNotAllowed"));
        }

        // TODO
        // Need to check carefully the rule for the writing of
        // namespaces in StAX. Is it safe to ignore such 
        // attributes, as declarations will be made using the
        // writeNamespace method
        if (namespaceURI == EncodingConstants.XMLNS_NAMESPACE_NAME || 
                namespaceURI.equals(EncodingConstants.XMLNS_NAMESPACE_NAME)) {
            return;
        }

        if (_attributesArrayIndex == _attributesArray.length) {
            final String[] attributesArray = new String[_attributesArrayIndex * 2];
            System.arraycopy(_attributesArray, 0, attributesArray, 0, _attributesArrayIndex);
            _attributesArray = attributesArray;
        }
        
        _attributesArray[_attributesArrayIndex++] = namespaceURI;
        _attributesArray[_attributesArrayIndex++] = prefix;
        _attributesArray[_attributesArrayIndex++] = localName;
        _attributesArray[_attributesArrayIndex++] = value;
    }
    
    public void writeNamespace(String prefix, String namespaceURI)
        throws XMLStreamException
    {
        if (prefix == null || prefix.length() == 0 || prefix.equals(EncodingConstants.XMLNS_NAMESPACE_PREFIX)) {
            writeDefaultNamespace(namespaceURI);
        }
        else {
            if (!_inStartElement) {
                throw new IllegalStateException(CommonResourceBundle.getInstance().getString("message.attributeWritingNotAllowed"));
            }
            
            if (_namespacesArrayIndex == _namespacesArray.length) {
                final String[] namespacesArray = new String[_namespacesArrayIndex * 2];
                System.arraycopy(_namespacesArray, 0, namespacesArray, 0, _namespacesArrayIndex);
                _namespacesArray = namespacesArray;
            }
            
            _namespacesArray[_namespacesArrayIndex++] = prefix;
            _namespacesArray[_namespacesArrayIndex++] = namespaceURI;
            setPrefix(prefix, namespaceURI);
        }
    }
    
    public void writeDefaultNamespace(String namespaceURI)
        throws XMLStreamException
    {
        if (!_inStartElement) {
            throw new IllegalStateException(CommonResourceBundle.getInstance().getString("message.attributeWritingNotAllowed"));
        }
        
        if (_namespacesArrayIndex == _namespacesArray.length) {
            final String[] namespacesArray = new String[_namespacesArrayIndex * 2];
            System.arraycopy(_namespacesArray, 0, namespacesArray, 0, _namespacesArrayIndex);
            _namespacesArray = namespacesArray;
        }

        _namespacesArray[_namespacesArrayIndex++] = "";
        _namespacesArray[_namespacesArrayIndex++] = namespaceURI;
        setPrefix("", namespaceURI);
    }
    
    public void writeComment(String data) throws XMLStreamException {
        try {
            if (getIgnoreComments()) return;
            
            encodeTerminationAndCurrentElement(true);

            // TODO: avoid array copy here
            encodeComment(data.toCharArray(), 0, data.length());
        }
        catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }
    
    public void writeProcessingInstruction(String target)
        throws XMLStreamException
    {
        writeProcessingInstruction(target, "");
    }
    
    public void writeProcessingInstruction(String target, String data)
        throws XMLStreamException
    {
        try {
            if (getIgnoreProcesingInstructions()) return;
            
            encodeTerminationAndCurrentElement(true);

            encodeProcessingInstruction(target, data);
        }
        catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }
    
    public void writeCData(String text) throws XMLStreamException {
         try {
            final int length = text.length();
            if (length == 0) {
                return;
            } else if (length < _charBuffer.length) {
                if (getIgnoreWhiteSpaceTextContent() &&
                        isWhiteSpace(text)) return;

                // Warning: this method must be called before any state
                // is modified, such as the _charBuffer contents,
                // so the characters of text cannot be copied to _charBuffer
                // before this call
                encodeTerminationAndCurrentElement(true);

                text.getChars(0, length, _charBuffer, 0);
                encodeCIIBuiltInAlgorithmDataAsCDATA(_charBuffer, 0, length);
            } else {
                final char ch[] = text.toCharArray();
                if (getIgnoreWhiteSpaceTextContent() &&
                        isWhiteSpace(ch, 0, length)) return;

                encodeTerminationAndCurrentElement(true);

                encodeCIIBuiltInAlgorithmDataAsCDATA(ch, 0, length);
            }
        } catch (Exception e) {
            throw new XMLStreamException(e);
        }
    }
    
    public void writeDTD(String dtd) throws XMLStreamException {
        throw new UnsupportedOperationException(CommonResourceBundle.getInstance().getString("message.notImplemented"));
    }
    
    public void writeEntityRef(String name) throws XMLStreamException {
        throw new UnsupportedOperationException(CommonResourceBundle.getInstance().getString("message.notImplemented"));
    }
        
    public void writeCharacters(String text) throws XMLStreamException {
         try {
            final int length = text.length();
            if (length == 0) {
                return;
            } else if (length < _charBuffer.length) {
                if (getIgnoreWhiteSpaceTextContent() && 
                        isWhiteSpace(text)) return;
                
                // Warning: this method must be called before any state
                // is modified, such as the _charBuffer contents,
                // so the characters of text cannot be copied to _charBuffer
                // before this call
                encodeTerminationAndCurrentElement(true);
                
                text.getChars(0, length, _charBuffer, 0);
                encodeCharacters(_charBuffer, 0, length);
            } else {
                final char ch[] = text.toCharArray();
                if (getIgnoreWhiteSpaceTextContent() && 
                        isWhiteSpace(ch, 0, length)) return;
                
                encodeTerminationAndCurrentElement(true);
                
                encodeCharactersNoClone(ch, 0, length);
            }
        }
        catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }
    
    public void writeCharacters(char[] text, int start, int len)
        throws XMLStreamException
    {
         try {
            if (len <= 0) {
                return;
            }

            if (getIgnoreWhiteSpaceTextContent() && 
                    isWhiteSpace(text, start, len)) return;
            
            encodeTerminationAndCurrentElement(true);

            encodeCharacters(text, start, len);
        }
        catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }

    public String getPrefix(String uri) throws XMLStreamException {
        return _nsContext.getPrefix(uri);
    }
    
    public void setPrefix(String prefix, String uri) 
        throws XMLStreamException 
    {
        if (_stackCount > -1 && _nsSupportContextStack[_stackCount] == false) {
            _nsSupportContextStack[_stackCount] = true;
            _nsContext.pushContext();
        }
        
        _nsContext.declarePrefix(prefix, uri);
    }
    
    public void setDefaultNamespace(String uri) throws XMLStreamException {
        setPrefix("", uri);
    }
    
    /**
     * Sets the current namespace context for prefix and uri bindings.
     * This context becomes the root namespace context for writing and
     * will replace the current root namespace context.  Subsequent calls
     * to setPrefix and setDefaultNamespace will bind namespaces using
     * the context passed to the method as the root context for resolving
     * namespaces.  This method may only be called once at the start of
     * the document.  It does not cause the namespaces to be declared.
     * If a namespace URI to prefix mapping is found in the namespace
     * context it is treated as declared and the prefix may be used
     * by the StreamWriter.
     * @param context the namespace context to use for this writer, may not be null
     * @throws XMLStreamException
     */
    public void setNamespaceContext(NamespaceContext context)
        throws XMLStreamException 
    {           
        throw new UnsupportedOperationException("setNamespaceContext");
    }
    
    public NamespaceContext getNamespaceContext() {
        return _nsContext;
    }
    
    public Object getProperty(java.lang.String name) 
        throws IllegalArgumentException 
    {
        if (_manager != null) {
            return _manager.getProperty(name);
        }
        return null;
    }

    public void setManager(StAXManager manager) {
        _manager = manager;
    }
    
    public void setEncoding(String encoding) {
        _encoding = encoding;
    }
    

    public void writeOctets(byte[] b, int start, int len)
        throws XMLStreamException
    {
         try {
            if (len == 0) {
                return;
            }

            encodeTerminationAndCurrentElement(true);

            encodeCIIOctetAlgorithmData(EncodingAlgorithmIndexes.BASE64, b, start, len);
        }
        catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }
    
    protected void encodeTerminationAndCurrentElement(boolean terminateAfter) throws XMLStreamException {
        try {
            encodeTermination();
            
            if (_inStartElement) {

                _b = EncodingConstants.ELEMENT;
                if (_attributesArrayIndex > 0) {
                    _b |= EncodingConstants.ELEMENT_ATTRIBUTE_FLAG;
                }

                // Encode namespace decls associated with this element
                if (_namespacesArrayIndex > 0) {
                    write(_b | EncodingConstants.ELEMENT_NAMESPACES_FLAG);
                    for (int i = 0; i < _namespacesArrayIndex;) {
                        encodeNamespaceAttribute(_namespacesArray[i++], _namespacesArray[i++]);
                    }
                    _namespacesArrayIndex = 0;
                    
                    write(EncodingConstants.TERMINATOR);

                    _b = 0;
                }

                // If element's prefix is empty - apply default scope namespace
                if (_currentPrefix.length() == 0) {
                    if (_currentUri.length() == 0) {
                        _currentUri = _nsContext.getNamespaceURI("");
                    } else {
                        String tmpPrefix = getPrefix(_currentUri);
                        if (tmpPrefix != null) {
                            _currentPrefix = tmpPrefix;
                        }
                    }
                }
                
                encodeElementQualifiedNameOnThirdBit(_currentUri, _currentPrefix, _currentLocalName);
                
                for (int i = 0; i < _attributesArrayIndex;) {
                    encodeAttributeQualifiedNameOnSecondBit(
                            _attributesArray[i++], _attributesArray[i++], _attributesArray[i++]);

                    final String value = _attributesArray[i];
                    _attributesArray[i++] = null;
                    final boolean addToTable = isAttributeValueLengthMatchesLimit(value.length());
                    encodeNonIdentifyingStringOnFirstBit(value, _v.attributeValue, addToTable, false);
                    
                    _b = EncodingConstants.TERMINATOR;
                    _terminate = true;
                }
                _attributesArrayIndex = 0;
                _inStartElement = false;

                if (_isEmptyElement) {
                    encodeElementTermination();
                    if (_nsSupportContextStack[_stackCount--] == true) {
                        _nsContext.popContext();
                    }
                    
                    _isEmptyElement = false;
                }
                
                if (terminateAfter) {
                    encodeTermination();
                }
            }
        } catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }

    
    // LowLevelFastInfosetSerializer
    
    public final void initiateLowLevelWriting() throws XMLStreamException {
        encodeTerminationAndCurrentElement(false);
    }
        
    public final int getNextElementIndex() {
        return _v.elementName.getNextIndex();
    }

    public final int getNextAttributeIndex() {
        return _v.attributeName.getNextIndex();
    }
    
    public final int getLocalNameIndex() {
        return _v.localName.getIndex();
    }
    
    public final int getNextLocalNameIndex() {
        return _v.localName.getNextIndex();
    }

    public final void writeLowLevelTerminationAndMark() throws IOException {
        encodeTermination();
        mark();
    }

    public final void writeLowLevelStartElementIndexed(int type, int index) throws IOException {
        _b = type;
        encodeNonZeroIntegerOnThirdBit(index);
    }
    
    public final boolean writeLowLevelStartElement(int type, String prefix, String localName,
            String namespaceURI) throws IOException {
        final boolean isIndexed = encodeElement(type, namespaceURI, prefix, localName);
        
        if (!isIndexed)
            encodeLiteral(type | EncodingConstants.ELEMENT_LITERAL_QNAME_FLAG,
                    namespaceURI, prefix, localName);
        
        return isIndexed;
    }
                
    public final void writeLowLevelStartNamespaces() throws IOException {
        write(EncodingConstants.ELEMENT | EncodingConstants.ELEMENT_NAMESPACES_FLAG);
    }
    
    public final void writeLowLevelNamespace(String prefix, String namespaceName) 
        throws IOException {
        encodeNamespaceAttribute(prefix, namespaceName);
    }
    
    public final void writeLowLevelEndNamespaces() throws IOException {
        write(EncodingConstants.TERMINATOR);
    }
    
    public final void writeLowLevelStartAttributes() throws IOException {
        if (hasMark()) {
            _octetBuffer[_markIndex] |= EncodingConstants.ELEMENT_ATTRIBUTE_FLAG;
            resetMark();
        }
    }
        
    public final void writeLowLevelAttributeIndexed(int index) throws IOException {
        encodeNonZeroIntegerOnSecondBitFirstBitZero(index);
    }
    
    public final boolean writeLowLevelAttribute(String prefix, String namespaceURI, String localName) throws IOException {
        final boolean isIndexed = encodeAttribute(namespaceURI, prefix, localName);

        if (!isIndexed)
            encodeLiteral(EncodingConstants.ATTRIBUTE_LITERAL_QNAME_FLAG, 
                    namespaceURI, prefix, localName);
        
        return isIndexed;
    }
                
    public final void writeLowLevelAttributeValue(String value) throws IOException
    {
        final boolean addToTable = isAttributeValueLengthMatchesLimit(value.length());
        encodeNonIdentifyingStringOnFirstBit(value, _v.attributeValue, addToTable, false);
    }
    
    public final void writeLowLevelStartNameLiteral(int type, String prefix, byte[] utf8LocalName, 
            String namespaceURI) throws IOException {
        encodeLiteralHeader(type, namespaceURI, prefix);
        encodeNonZeroOctetStringLengthOnSecondBit(utf8LocalName.length);
        write(utf8LocalName, 0, utf8LocalName.length);
    }
    
    public final void writeLowLevelStartNameLiteral(int type, String prefix, int localNameIndex, 
            String namespaceURI) throws IOException {
        encodeLiteralHeader(type, namespaceURI, prefix);
        encodeNonZeroIntegerOnSecondBitFirstBitOne(localNameIndex);
    }
     
    public final void writeLowLevelEndStartElement() throws IOException {
        if (hasMark()) {
            resetMark();
        } else {
            // Terminate the attributes
            _b = EncodingConstants.TERMINATOR;
            _terminate = true;
        }
    }
    
    public final void writeLowLevelEndElement() throws IOException {
        encodeElementTermination();
    }
    
    public final void writeLowLevelText(char[] text, int length) throws IOException {
        if (length == 0)
            return;
        
        encodeTermination();
        
        encodeCharacters(text, 0, length);
    }
    
    public final void writeLowLevelText(String text) throws IOException {
        final int length = text.length();
        if (length == 0)
            return;
        
        encodeTermination();
        
        if (length < _charBuffer.length) {
            text.getChars(0, length, _charBuffer, 0);
            encodeCharacters(_charBuffer, 0, length);
        } else {
            final char ch[] = text.toCharArray();
            encodeCharactersNoClone(ch, 0, length);
        }
    }
    
    public final void writeLowLevelOctets(byte[] octets, int length) throws IOException {
        if (length == 0)
            return;
        
        encodeTermination();
        
        encodeCIIOctetAlgorithmData(EncodingAlgorithmIndexes.BASE64, octets, 0, length);
    }

    private boolean encodeElement(int type, String namespaceURI, String prefix, String localName) throws IOException {
        final LocalNameQualifiedNamesMap.Entry entry = _v.elementName.obtainEntry(localName);
        for (int i = 0; i < entry._valueIndex; i++) {
            final QualifiedName name = entry._value[i];
            if ((prefix == name.prefix || prefix.equals(name.prefix))
                    && (namespaceURI == name.namespaceName || namespaceURI.equals(name.namespaceName))) {
                _b = type;
                encodeNonZeroIntegerOnThirdBit(name.index);
                return true;
            }
        }

        entry.addQualifiedName(new QualifiedName(prefix, namespaceURI, localName, "", _v.elementName.getNextIndex()));
        return false;
    }
        
    private boolean encodeAttribute(String namespaceURI, String prefix, String localName) throws IOException {
        final LocalNameQualifiedNamesMap.Entry entry = _v.attributeName.obtainEntry(localName);
        for (int i = 0; i < entry._valueIndex; i++) {
            final QualifiedName name = entry._value[i];
            if ((prefix == name.prefix || prefix.equals(name.prefix))
                    && (namespaceURI == name.namespaceName || namespaceURI.equals(name.namespaceName))) {
                encodeNonZeroIntegerOnSecondBitFirstBitZero(name.index);
                return true;
            }
        }

        entry.addQualifiedName(new QualifiedName(prefix, namespaceURI, localName, "", _v.attributeName.getNextIndex()));
        return false;
    }

    private void encodeLiteralHeader(int type, String namespaceURI, String prefix) throws IOException {
        if (namespaceURI != "") {
            type |= EncodingConstants.LITERAL_QNAME_NAMESPACE_NAME_FLAG;
            if (prefix != "")
                type |= EncodingConstants.LITERAL_QNAME_PREFIX_FLAG;
                        
            write(type);
            if (prefix != "")
                encodeNonZeroIntegerOnSecondBitFirstBitOne(_v.prefix.get(prefix));
            encodeNonZeroIntegerOnSecondBitFirstBitOne(_v.namespaceName.get(namespaceURI));
        } else
            write(type);
    }
        
    private void encodeLiteral(int type, String namespaceURI, String prefix, String localName) throws IOException {
        encodeLiteralHeader(type, namespaceURI, prefix);
        
        final int localNameIndex = _v.localName.obtainIndex(localName);
        if (localNameIndex == -1) {
            encodeNonEmptyOctetStringOnSecondBit(localName);
        } else
            encodeNonZeroIntegerOnSecondBitFirstBitOne(localNameIndex);
    }    
}