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