/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.xml.bind.serializer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;

import javax.xml.XMLConstants;

import org.xml.sax.SAXException;

/**
 * Implementation of the NamespaceContext2.
 * 
 * This class also provides several utility methods for
 * XMLSerializer-derived classes.
 * 
 * The startElement method and the endElement method need to be called
 * appropriately when used. See javadoc for those methods for details.
 * 
 * @since JAXB1.0
 * @deprecated in JAXB1.0.1
 */
public class NamespaceContextImpl implements NamespaceContext2
{
    /** Sequence generator. */
    private int iota = 1;
    
    
    /**
     * Namespace declaration stack implemented as an array of (prefix,uri) pairs.
     * prefix==null means that it is the default namespace.
     */
    private String[] nsStack = new String[16];
    private int nsLen=0;

    /**
     * A stack of index to nsStack. Namespace declarations for
     * an element (elementStack[2i],elementStack[2i+1]) is
     * from nsStack[idxStack[i]] to nsStack[idxStack[i+1]]
     */
    private int[] idxStack = new int[16];
    private int idxLen=0;
    
    /**
     * This map is keyed by namespace URI and remembers the prefix
     * assigned to it. Since we don't reuse the same prefix for
     * different URIs, this map can be used to reduce the number
     * of distinct prefixes.
     */
    private final HashMap associationHistory = new HashMap();
    
    
    public NamespaceContextImpl() {
        // declare the default namespace binding
        // which are effective because of the way XML1.0 is made
        addBinding(null,"");
        addBinding( "xml",   XMLConstants.XML_NS_URI );
        addBinding( "xmlns", XMLConstants.XMLNS_ATTRIBUTE_NS_URI );
    }

//
//
// public methods of MarshallingContext
//
//
    public String declareNamespace( String namespaceUri, boolean requirePrefix ) {
        // see if it's already declared
        for( int i = nsLen-2; i>=0; i-=2 ) {
            if( nsStack[i+1].equals(namespaceUri) ) {
                if( nsStack[i]!=null || !requirePrefix ) {
                    // assertion check: we use null to denote the default ns.
                    // using the empty string is therefore illegal.
                    if( "".equals(nsStack[i]) )
                        throw new InternalError();
                    return nsStack[i];
                }
            }
        }
        
        // this namespace URI is not bound.
        // assign a new prefix
        String prefix = assignPrefix(namespaceUri,requirePrefix);
        
        addBinding(prefix,namespaceUri);
        
        // assertion check: we use null to denote the default ns.
        // using the empty string is therefore illegal.
        if( "".equals(prefix) )
            throw new InternalError();
        
        return prefix;
    }

    public String getPrefix( String namespaceUri ) {
        // even through the method name is "getPrefix", we 
        // use this method to declare prefixes if necessary.
        return declareNamespace(namespaceUri,false);
    }
    
    /**
     * Obtains the namespace URI currently associated to the given prefix.
     * If no namespace URI is associated, return null.
     */
    public String getNamespaceURI( String prefix ) {
        // shall we throw an exception in case of error?
        
        for( int i = nsLen-2; i>=0; i-=2 ) {
            if( nsStack[i].equals(prefix) )
                return nsStack[i+1];
        }
        
        return null;
    }
    
    public Iterator getPrefixes( String namespaceUri ) {
        // it's important to search the array in the reverse order,
        // as the most recently declared namespace comes the last.
        for( int i = nsLen-2; i>=0; i-=2 ) {
            if( nsStack[i+1].equals(namespaceUri) ) {
                // we don't allocate two prefixes for the same namespace URI,
                // so if we find one, that's the one.
                ArrayList al = new ArrayList();
                al.add(nsStack[i]);
                return Collections.unmodifiableList(al).iterator();
            }
        }
        
        // wrap the collection into unmodifiable list so that the returned
        // iterator will throw UnsupportedOperationException from the remove method.
        
        return Collections.unmodifiableList(new ArrayList()).iterator();  // return the empty iterator
    }

    /**
     * Sets the current bindings aside and starts a new element context.
     * 
     * This method should be called at the beginning of the startElement method
     * of the Serializer implementation.
     */
    public void startElement() {
        // pushes the current value of nsLen into the stack.
        
        if(idxStack.length==idxLen) {
            // reallocate buffer
            int[] buf = new int[idxStack.length*2];
            System.arraycopy( idxStack, 0, buf, 0, idxStack.length );
            idxStack = buf;
        }
        idxStack[idxLen++] = nsLen;
    }
    
    /**
     * Ends the current element context and gets back to the parent context.
     * 
     * This method should be called at the end of the endElement method
     * of derived classes.
     */
    public void endElement() {
        // pop namespace declaration stack
        nsLen = getStartIndex();
        idxLen--;
    }

    
    
//
//
// utility methods for the derived classes
//
//
    /** Iterates all newly declared namespace prefixes for this element. */
    public void iterateDeclaredPrefixes( PrefixCallback callback ) throws SAXException {
        for( int i = getStartIndex(); i<nsLen; i+=2 ) {
            String p = nsStack[i];
            if(p!=null)
                callback.onPrefixMapping( p, nsStack[i+1] );
        }
    }
    
    
//
//
// implementation details
//
//
    /** Adds new binding to the stack. */
    private void addBinding( String prefix, String namespaceUri ) {
        // assertion check: prefix must be null, not "".
        if(prefix!=null && prefix.length()==0)     throw new InternalError();
        
        if(nsStack.length==nsLen) {
            // reallocate buffer
            String[] buf = new String[nsStack.length*2];
            System.arraycopy( nsStack, 0, buf, 0, nsStack.length );
            nsStack = buf;
        }
        
        nsStack[nsLen++] = prefix;
        nsStack[nsLen++] = namespaceUri;
    }
    
    
    private String assignPrefix( String namespaceUri, boolean prefixRequired ) {
        // TODO : allow users to specify their choice of namespace bindings.
        
        String prefix = (String)associationHistory.get(namespaceUri);
        if(prefix==null)
            // assign a new unique prefix
            associationHistory.put( namespaceUri, prefix= "ns"+(iota++) );
            
        return prefix;
    }

    /**
     * Gets the index of the first namespace declaration in the current element.
     * If there is no new declaration in this element, nsLen will be returned.
     */
    private int getStartIndex() { return idxStack[idxLen-1]; }
}
