aoqi@0: /* aoqi@0: * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.xml.internal.bind.v2.runtime.output; aoqi@0: aoqi@0: import java.io.IOException; aoqi@0: import java.util.Collections; aoqi@0: import java.util.Iterator; aoqi@0: aoqi@0: import javax.xml.XMLConstants; aoqi@0: import javax.xml.stream.XMLStreamException; aoqi@0: aoqi@0: import com.sun.istack.internal.NotNull; aoqi@0: import com.sun.istack.internal.Nullable; aoqi@0: import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper; aoqi@0: import com.sun.xml.internal.bind.v2.WellKnownNamespace; aoqi@0: import com.sun.xml.internal.bind.v2.runtime.Name; aoqi@0: import com.sun.xml.internal.bind.v2.runtime.NamespaceContext2; aoqi@0: import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; aoqi@0: aoqi@0: import org.xml.sax.SAXException; aoqi@0: aoqi@0: /** aoqi@0: * Keeps track of in-scope namespace bindings for the marshaller. aoqi@0: * aoqi@0: *

aoqi@0: * This class is also used to keep track of tag names for each element aoqi@0: * for the marshaller (for the performance reason.) aoqi@0: * aoqi@0: * @author Kohsuke Kawaguchi aoqi@0: */ aoqi@0: public final class NamespaceContextImpl implements NamespaceContext2 { aoqi@0: private final XMLSerializer owner; aoqi@0: aoqi@0: private String[] prefixes = new String[4]; aoqi@0: private String[] nsUris = new String[4]; aoqi@0: // /** aoqi@0: // * True if the correponding namespace declaration is an authentic one that should be printed. aoqi@0: // * aoqi@0: // * False if it's a re-discovered in-scope namespace binding available at the ancestor elements aoqi@0: // * outside this marhsalling. The false value is used to incorporate the in-scope namespace binding aoqi@0: // * information from {@link #inscopeNamespaceContext}. When false, such a declaration does not need aoqi@0: // * to be printed, as it's already available in ancestors. aoqi@0: // */ aoqi@0: // private boolean[] visible = new boolean[4]; aoqi@0: // aoqi@0: // /** aoqi@0: // * {@link NamespaceContext} that informs this {@link XMLSerializer} about the aoqi@0: // * in-scope namespace bindings of the ancestor elements outside this marshalling. aoqi@0: // * aoqi@0: // *

aoqi@0: // * This is used when the marshaller is marshalling into a subtree that has ancestor aoqi@0: // * elements created outside the JAXB marshaller. aoqi@0: // * aoqi@0: // * Its {@link NamespaceContext#getPrefix(String)} is used to discover in-scope namespace aoqi@0: // * binding, aoqi@0: // */ aoqi@0: // private final NamespaceContext inscopeNamespaceContext; aoqi@0: aoqi@0: /** aoqi@0: * Number of URIs declared. Identifies the valid portion of aoqi@0: * the {@link #prefixes} and {@link #nsUris} arrays. aoqi@0: */ aoqi@0: private int size; aoqi@0: aoqi@0: private Element current; aoqi@0: aoqi@0: /** aoqi@0: * This is the {@link Element} whose prev==null. aoqi@0: * This element is used to hold the contextual namespace bindings aoqi@0: * that are assumed to be outside of the document we are marshalling. aoqi@0: * Specifically the xml prefix and any other user-specified bindings. aoqi@0: * aoqi@0: * @see NamespacePrefixMapper#getPreDeclaredNamespaceUris() aoqi@0: */ aoqi@0: private final Element top; aoqi@0: aoqi@0: /** aoqi@0: * Never null. aoqi@0: */ aoqi@0: private NamespacePrefixMapper prefixMapper = defaultNamespacePrefixMapper; aoqi@0: aoqi@0: /** aoqi@0: * True to allow new URIs to be declared. False otherwise. aoqi@0: */ aoqi@0: public boolean collectionMode; aoqi@0: aoqi@0: aoqi@0: public NamespaceContextImpl(XMLSerializer owner) { aoqi@0: this.owner = owner; aoqi@0: aoqi@0: current = top = new Element(this,null); aoqi@0: // register namespace URIs that are implicitly bound aoqi@0: put(XMLConstants.XML_NS_URI,XMLConstants.XML_NS_PREFIX); aoqi@0: } aoqi@0: aoqi@0: public void setPrefixMapper( NamespacePrefixMapper mapper ) { aoqi@0: if(mapper==null) aoqi@0: mapper = defaultNamespacePrefixMapper; aoqi@0: this.prefixMapper = mapper; aoqi@0: } aoqi@0: aoqi@0: public NamespacePrefixMapper getPrefixMapper() { aoqi@0: return prefixMapper; aoqi@0: } aoqi@0: aoqi@0: public void reset() { aoqi@0: current = top; aoqi@0: size = 1; aoqi@0: collectionMode = false; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns the prefix index to the specified URI. aoqi@0: * This method allocates a new URI if necessary. aoqi@0: */ aoqi@0: public int declareNsUri( String uri, String preferedPrefix, boolean requirePrefix ) { aoqi@0: preferedPrefix = prefixMapper.getPreferredPrefix(uri,preferedPrefix,requirePrefix); aoqi@0: aoqi@0: if(uri.length()==0) { aoqi@0: for( int i=size-1; i>=0; i-- ) { aoqi@0: if(nsUris[i].length()==0) aoqi@0: return i; // already declared aoqi@0: if(prefixes[i].length()==0) { aoqi@0: // the default prefix is already taken. aoqi@0: // move that URI to another prefix, then assign "" to the default prefix. aoqi@0: assert current.defaultPrefixIndex==-1 && current.oldDefaultNamespaceUriIndex==-1; aoqi@0: aoqi@0: String oldUri = nsUris[i]; aoqi@0: String[] knownURIs = owner.nameList.namespaceURIs; aoqi@0: aoqi@0: if(current.baseIndex<=i) { aoqi@0: // this default prefix is declared in this context. just reassign it aoqi@0: aoqi@0: nsUris[i] = ""; aoqi@0: aoqi@0: int subst = put(oldUri,null); aoqi@0: aoqi@0: // update uri->prefix table if necessary aoqi@0: for( int j=knownURIs.length-1; j>=0; j-- ) { aoqi@0: if(knownURIs[j].equals(oldUri)) { aoqi@0: owner.knownUri2prefixIndexMap[j] = subst; aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: if (current.elementLocalName != null) { aoqi@0: current.setTagName(subst, current.elementLocalName, current.getOuterPeer()); aoqi@0: } aoqi@0: return i; aoqi@0: } else { aoqi@0: // first, if the previous URI assigned to "" is aoqi@0: // a "known URI", remember what we've reallocated aoqi@0: // so that we can fix it when this context pops. aoqi@0: for( int j=knownURIs.length-1; j>=0; j-- ) { aoqi@0: if(knownURIs[j].equals(oldUri)) { aoqi@0: current.defaultPrefixIndex = i; aoqi@0: current.oldDefaultNamespaceUriIndex = j; aoqi@0: // assert commented out; too strict/not valid any more aoqi@0: // assert owner.knownUri2prefixIndexMap[j]==current.defaultPrefixIndex; aoqi@0: // update the table to point to the prefix we'll declare aoqi@0: owner.knownUri2prefixIndexMap[j] = size; aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: if (current.elementLocalName!=null) { aoqi@0: current.setTagName(size, current.elementLocalName, current.getOuterPeer()); aoqi@0: } aoqi@0: aoqi@0: put(nsUris[i],null); aoqi@0: return put("", ""); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // "" isn't in use aoqi@0: return put("", ""); aoqi@0: } else { aoqi@0: // check for the existing binding aoqi@0: for( int i=size-1; i>=0; i-- ) { aoqi@0: String p = prefixes[i]; aoqi@0: if(nsUris[i].equals(uri)) { aoqi@0: if (!requirePrefix || p.length()>0) aoqi@0: return i; aoqi@0: // declared but this URI is bound to empty. Look further aoqi@0: } aoqi@0: if(p.equals(preferedPrefix)) { aoqi@0: // the suggested prefix is already taken. can't use it aoqi@0: preferedPrefix = null; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: if(preferedPrefix==null && requirePrefix) aoqi@0: // we know we can't bind to "", but we don't have any possible name at hand. aoqi@0: // generate it here to avoid this namespace to be bound to "". aoqi@0: preferedPrefix = makeUniquePrefix(); aoqi@0: aoqi@0: // haven't been declared. allocate a new one aoqi@0: // if the preferred prefix is already in use, it should have been set to null by this time aoqi@0: return put(uri, preferedPrefix); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public int force(@NotNull String uri, @NotNull String prefix) { aoqi@0: // check for the existing binding aoqi@0: aoqi@0: for( int i=size-1; i>=0; i-- ) { aoqi@0: if(prefixes[i].equals(prefix)) { aoqi@0: if(nsUris[i].equals(uri)) aoqi@0: return i; // found duplicate aoqi@0: else aoqi@0: // the prefix is used for another namespace. we need to declare it aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: return put(uri, prefix); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Puts this new binding into the declared prefixes list aoqi@0: * without doing any duplicate check. aoqi@0: * aoqi@0: * This can be used to forcibly set namespace declarations. aoqi@0: * aoqi@0: *

aoqi@0: * Most of the time {@link #declareNamespace(String, String, boolean)} shall be used. aoqi@0: * aoqi@0: * @return aoqi@0: * the index of this new binding. aoqi@0: */ aoqi@0: public int put(@NotNull String uri, @Nullable String prefix) { aoqi@0: if(size==nsUris.length) { aoqi@0: // reallocate aoqi@0: String[] u = new String[nsUris.length*2]; aoqi@0: String[] p = new String[prefixes.length*2]; aoqi@0: System.arraycopy(nsUris,0,u,0,nsUris.length); aoqi@0: System.arraycopy(prefixes,0,p,0,prefixes.length); aoqi@0: nsUris = u; aoqi@0: prefixes = p; aoqi@0: } aoqi@0: if(prefix==null) { aoqi@0: if(size==1) aoqi@0: prefix = ""; // if this is the first user namespace URI we see, use "". aoqi@0: else { aoqi@0: // otherwise make up an unique name aoqi@0: prefix = makeUniquePrefix(); aoqi@0: } aoqi@0: } aoqi@0: nsUris[size] = uri; aoqi@0: prefixes[size] = prefix; aoqi@0: aoqi@0: return size++; aoqi@0: } aoqi@0: aoqi@0: private String makeUniquePrefix() { aoqi@0: String prefix; aoqi@0: prefix = new StringBuilder(5).append("ns").append(size).toString(); aoqi@0: while(getNamespaceURI(prefix)!=null) { aoqi@0: prefix += '_'; // under a rare circumstance there might be existing 'nsNNN', so rename them aoqi@0: } aoqi@0: return prefix; aoqi@0: } aoqi@0: aoqi@0: aoqi@0: public Element getCurrent() { aoqi@0: return current; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns the prefix index of the specified URI. aoqi@0: * It is an error if the URI is not declared. aoqi@0: */ aoqi@0: public int getPrefixIndex( String uri ) { aoqi@0: for( int i=size-1; i>=0; i-- ) { aoqi@0: if(nsUris[i].equals(uri)) aoqi@0: return i; aoqi@0: } aoqi@0: throw new IllegalStateException(); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Gets the prefix from a prefix index. aoqi@0: * aoqi@0: * The behavior is undefined if the index is out of range. aoqi@0: */ aoqi@0: public String getPrefix(int prefixIndex) { aoqi@0: return prefixes[prefixIndex]; aoqi@0: } aoqi@0: aoqi@0: public String getNamespaceURI(int prefixIndex) { aoqi@0: return nsUris[prefixIndex]; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Gets the namespace URI that is bound to the specified prefix. aoqi@0: * aoqi@0: * @return null aoqi@0: * if the prefix is unbound. aoqi@0: */ aoqi@0: public String getNamespaceURI(String prefix) { aoqi@0: for( int i=size-1; i>=0; i-- ) aoqi@0: if(prefixes[i].equals(prefix)) aoqi@0: return nsUris[i]; aoqi@0: return null; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns the prefix of the specified URI, aoqi@0: * or null if none exists. aoqi@0: */ aoqi@0: public String getPrefix( String uri ) { aoqi@0: if(collectionMode) { aoqi@0: return declareNamespace(uri,null,false); aoqi@0: } else { aoqi@0: for( int i=size-1; i>=0; i-- ) aoqi@0: if(nsUris[i].equals(uri)) aoqi@0: return prefixes[i]; aoqi@0: return null; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public Iterator getPrefixes(String uri) { aoqi@0: String prefix = getPrefix(uri); aoqi@0: if(prefix==null) aoqi@0: return Collections.emptySet().iterator(); aoqi@0: else aoqi@0: return Collections.singleton(uri).iterator(); aoqi@0: } aoqi@0: aoqi@0: public String declareNamespace(String namespaceUri, String preferedPrefix, boolean requirePrefix) { aoqi@0: int idx = declareNsUri(namespaceUri,preferedPrefix,requirePrefix); aoqi@0: return getPrefix(idx); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Number of total bindings declared. aoqi@0: */ aoqi@0: public int count() { aoqi@0: return size; aoqi@0: } aoqi@0: aoqi@0: aoqi@0: /** aoqi@0: * This model of namespace declarations maintain the following invariants. aoqi@0: * aoqi@0: *