org.glassfish.jaxb.runtime.v2.runtime.output.NamespaceContextImpl Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.glassfish.jaxb.runtime.v2.runtime.output;
import com.sun.istack.NotNull;
import com.sun.istack.Nullable;
import org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper;
import org.glassfish.jaxb.core.v2.WellKnownNamespace;
import org.glassfish.jaxb.runtime.v2.runtime.Name;
import org.glassfish.jaxb.runtime.v2.runtime.NamespaceContext2;
import org.glassfish.jaxb.runtime.v2.runtime.XMLSerializer;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
/**
* 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.
*
*
* - If a non-empty prefix is declared, it will never be reassigned to different namespace URIs.
*
*/
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;
}
};
}