/* * 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
*
*/
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;
}
};
}