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

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

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

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

*/ public final class Element { public final NamespaceContextImpl context; /** * {@link Element}s form a doubly-linked list. */ private final Element prev; private Element next; private int oldDefaultNamespaceUriIndex; private int defaultPrefixIndex; /** * The numbe of prefixes declared by ancestor {@link Element}s. */ private int baseIndex; /** * The depth of the {@link Element}. * * This value is equivalent as the result of the following computation. * *
         * int depth() {
         *   int i=-1;
         *   for(Element e=this; e!=null;e=e.prev)
         *     i++;
         *   return i;
         * }
         * 
*/ private final int depth; private int elementNamePrefix; private String elementLocalName; /** * Tag name of this element. * Either this field is used or the {@link #elementNamePrefix} and {@link #elementLocalName} pair. */ private Name elementName; /** * Used for the binder. The JAXB object that corresponds to this element. */ private Object outerPeer; private Object innerPeer; private Element(NamespaceContextImpl context,Element prev) { this.context = context; this.prev = prev; this.depth = (prev==null) ? 0 : prev.depth+1; } /** * Returns true if this {@link Element} represents the root element that * we are marshalling. */ public boolean isRootElement() { return depth==1; } public Element push() { if(next==null) next = new Element(context,this); next.onPushed(); return next; } public Element pop() { if(oldDefaultNamespaceUriIndex>=0) { // restore the old default namespace URI binding context.owner.knownUri2prefixIndexMap[oldDefaultNamespaceUriIndex] = defaultPrefixIndex; } context.size = baseIndex; context.current = prev; // release references to user objects outerPeer = innerPeer = null; return prev; } private void onPushed() { oldDefaultNamespaceUriIndex = defaultPrefixIndex = -1; baseIndex = context.size; context.current = this; } public void setTagName( int prefix, String localName, Object outerPeer ) { assert localName!=null; this.elementNamePrefix = prefix; this.elementLocalName = localName; this.elementName = null; this.outerPeer = outerPeer; } public void setTagName( Name tagName, Object outerPeer ) { assert tagName!=null; this.elementName = tagName; this.outerPeer = outerPeer; } public void startElement(XmlOutput out, Object innerPeer) throws IOException, XMLStreamException { this.innerPeer = innerPeer; if(elementName!=null) { out.beginStartTag(elementName); } else { out.beginStartTag(elementNamePrefix,elementLocalName); } } public void endElement(XmlOutput out) throws IOException, SAXException, XMLStreamException { if(elementName!=null) { out.endTag(elementName); elementName = null; } else { out.endTag(elementNamePrefix,elementLocalName); } } /** * Gets the number of bindings declared on this element. */ public final int count() { return context.size-baseIndex; } /** * Gets the prefix declared in this context. * * @param idx * between 0 and {@link #count()} */ public final String getPrefix(int idx) { return context.prefixes[baseIndex+idx]; } /** * Gets the namespace URI declared in this context. * * @param idx * between 0 and {@link #count()} */ public final String getNsUri(int idx) { return context.nsUris[baseIndex+idx]; } public int getBase() { return baseIndex; } public Object getOuterPeer() { return outerPeer; } public Object getInnerPeer() { return innerPeer; } /** * Gets the parent {@link Element}. */ public Element getParent() { return prev; } } /** * Default {@link NamespacePrefixMapper} implementation used when * it is not specified by the user. */ private static final NamespacePrefixMapper defaultNamespacePrefixMapper = new NamespacePrefixMapper() { public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) { if( namespaceUri.equals(WellKnownNamespace.XML_SCHEMA_INSTANCE) ) return "xsi"; if( namespaceUri.equals(WellKnownNamespace.XML_SCHEMA) ) return "xs"; if( namespaceUri.equals(WellKnownNamespace.XML_MIME_URI) ) return "xmime"; return suggestion; } }; }