package org.anodyneos.xpImpl.runtime;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.EmptyStackException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections.ArrayStack;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.helpers.NamespaceSupport;
/**
* Functions similarly to org.xml.sax.NamespaceSupport, except for the
* following:
*
* 1. declarePrefix() first checks to see if the prefix is already mapped to the
* uri (and not masked), and if so, does nothing.
*
* 2. namespace context versions are tracked efficiently. If
* getNamespaceContextVersion() returns an <code>int</code> that can be
* compared to a value returned from a previous call to find out if the current
* mappings are identical to those at the time of the previous call. IMPORTANT
* NOTE: versions will only be tracked for each push()/pop() depth, so the save
* version number may be returned even after new prefix mappings have been been
* made if push() or pop() has not been called since the last new prefix
* mapping.
*
* 3. Extra checking is performed to ensure the NamespaceSupport contract
* disallowing declarePrefix() after pop() but before push() is followed.
*
* 4. popContext2() returns all prefix declarations made in the popped context.
*
* BUG: apidocs for NamespaceSupport version "xml-commons-external-1_2_01
* (revision: 1.2.6.2)" for declarePrefix() states: "IllegalStateException when
* a prefix is declared after looking up a name in the context, or after pushing
* another context on top of it." This would be a problem if getURI() prevented
* future calls to declarePrefix(), but the current code does not seem to mind.
*
* ISSUE: This may not be the most performant code, for example,
* NamespaceSupport uses Vectors!
*
* @author jvas
*/
public final class NamespaceMappings {
private static final Log logger = LogFactory.getLog(NamespaceMappings.class);
private NamespaceSupport namespaceSupport = new NamespaceSupport();
private int ancestorsWithPrefixDeclarations = 0;
private int ancestorsWithPrefixMasking = 0;
private boolean declareOk = true;
private BitSet prefixesDeclaredAtDepth = new BitSet();
private BitSet prefixesMaskedAtDepth = new BitSet();
/** current depth of context stack - tracks push/pop on namespaceSupport object */
private int contextDepth = 0;
/** stores version numbers for namespace contexts */
private IntStack versionStack;
/** nextVersionNum always increments - values are unique and stored in versionStack */
private int nextVersionNum = 0;
/** Used to hold return value for popContext2() */
private ArrayList tmpPrefixes = new ArrayList();
// this only holds items when it is being used. It holds null values
// whenever possible.
// Entries are stacks that hold String[2] entries for prefix->uri mappings.
private ArrayStack phantomPrefixes = new ArrayStack();
public NamespaceMappings() {
versionStack = new IntStack(3);
versionStack.push(nextVersionNum++);
}
public void pushPhantomPrefix(String prefix, String uri) {
// This may be called when phantomPrefixes is empty - if so, add an
// entry for the current context.
// If not empty, the contents may be null. If so, pop the null and push
// a new ArrayStack.
// Note: we could add a boolean argument that if true would tell us
// to promote the phantom to a real
// mapping immediately. In this case we would call declarePrefix()
// on our own and add a null to the prefixStack instead of the
// String[2]. We would only do this if it would not mask a currently
// declared prefix.
ArrayStack prefixStack;
if (phantomPrefixes.isEmpty()) {
prefixStack = new ArrayStack();
phantomPrefixes.push(prefixStack);
} else {
prefixStack = (ArrayStack) phantomPrefixes.peek();
if (null == prefixStack) {
prefixStack = new ArrayStack();
phantomPrefixes.pop();
phantomPrefixes.push(prefixStack);
}
}
prefixStack.push(new String[] { prefix, uri });
}
public void popPhantomPrefix() throws EmptyStackException {
// we will require no arguments and provide no checking - we trust the
// calling code.
ArrayStack prefixStack = (ArrayStack) phantomPrefixes.peek();
prefixStack.pop();
}
/*
public void promotePhantomPrefix(String prefix) {
final String declaredURI = getURI(prefix, false);
final String currentURI = getURI(prefix, true);
// do not allow promotions if real prefix already exists and is mapped to diff URI
if (null != declaredURI && ! declaredURI.equals(currentURI)) {
throw new IllegalStateException("Cannot promote phantom prefix '"+prefix+"'; prefix already exists.");
}
// do not allow promotions if phantom prefix does not exist
if (null == currentURI) {
throw new IllegalStateException("Phantom prefix '"+prefix+"' does not exist.");
}
declarePrefix(prefix, currentURI);
}
*/
public boolean declarePrefix(String prefix, String uri) {
// Note: calls to declarePrefix may over-write phantomPrefixes that were
// added during push(). This should
// be OK since obviously the calling code no longer cares about the
// phantomPrefix.
if (!declareOk) {
throw new IllegalStateException("Cannot declarePrefix after pop() without first calling push()");
}
// only perform op if prefix to uri is not alread set.
String oldUriForPrefix = namespaceSupport.getURI(prefix);
if (!uri.equals(oldUriForPrefix)) {
if (namespaceSupport.declarePrefix(prefix, uri)) {
// if we haven't already flagged this depth for
// prefixesDeclared, do so:
if (!prefixesDeclaredAtDepth.get(contextDepth)) {
ancestorsWithPrefixDeclarations++;
versionStack.push(nextVersionNum++);
if (logger.isDebugEnabled()) {
logger.debug(" NamespaceMappings ancestorsWithPrefixDeclarations set to "
+ ancestorsWithPrefixDeclarations);
logger.debug(" NamespaceMappings namespaceVersion set to " + versionStack.peek());
}
prefixesDeclaredAtDepth.set(contextDepth);
}
// if we are masking a value, update the compatibility version:
if (oldUriForPrefix != null && !prefixesMaskedAtDepth.get(contextDepth)) {
ancestorsWithPrefixMasking++;
if (logger.isDebugEnabled()) {
logger.debug(" NamespaceMappings ancestorsWithPrefixMasking set to "
+ ancestorsWithPrefixMasking);
}
prefixesMaskedAtDepth.set(contextDepth);
}
return true;
} else {
return false;
}
}
// NamespaceSupport returns false for "xml" and "xmlns". We should
// not have to test for "xmlns" since that prefix is never tracked
// by namespaceSupport and we would have already returned false
// in the above code.
if ("xml".equals(prefix)) {
return false;
} else {
return true;
}
}
private boolean phantomsExistForCurrentContext() {
return (!phantomPrefixes.isEmpty() && null != phantomPrefixes.peek());
// This check is not necessary - we will never have an empty stack
// ((ArrayStack) phantomPrefixes.peek()).isEmpty());
}
public Enumeration getDeclaredPrefixes(boolean includePhantom) {
// Consider changing this class to return both prefixes and uris since
// that would be the normal
// use case.
if (!includePhantom || !phantomsExistForCurrentContext()) {
return namespaceSupport.getDeclaredPrefixes();
} else {
// if a prefix is declared multiple times in phantom or in both
// phantom and namespaceSupport, that
// is ok, but just return it once.
Set prefixes = new HashSet();
ArrayStack prefixStack = (ArrayStack) phantomPrefixes.peek();
for (int i = 0; i < prefixStack.size(); i++) {
prefixes.add(((String[]) prefixStack.get(i))[0]);
}
Enumeration e = namespaceSupport.getDeclaredPrefixes();
while (e.hasMoreElements()) {
prefixes.add((String) e.nextElement());
}
return new IteratorEnumeration(prefixes.iterator());
}
}
public String getPrefix(String uri, boolean includePhantom) {
// FIXME don't return the default ("") prefix
if (!includePhantom || !phantomsExistForCurrentContext()) {
return namespaceSupport.getPrefix(uri);
} else {
// first search phantoms in LIFO order.
ArrayStack prefixStack = (ArrayStack) phantomPrefixes.peek();
for (int i = prefixStack.size() - 1; i >= 0; i--) {
String[] entry = (String[]) prefixStack.get(i);
if (entry[1].equals(uri)) {
return entry[0];
}
}
// not found yet, try namespaceSupport
return namespaceSupport.getPrefix(uri);
}
}
public Enumeration getPrefixes(boolean includePhantom) {
if (!includePhantom || !phantomsExistForCurrentContext()) {
return namespaceSupport.getPrefixes();
} else {
// if a prefix is declared multiple times in phantom or in both
// phantom and namespaceSupport, that
// is ok, but just return it once.
Set prefixes = new HashSet();
ArrayStack prefixStack = (ArrayStack) phantomPrefixes.peek();
for (int i = 0; i < prefixStack.size(); i++) {
// IMPORTANT: Make sure not to return the "" prefix
String prefix = ((String[]) prefixStack.get(i))[0];
if (null != prefix && prefix.length() > 0) {
prefixes.add(prefix);
}
}
Enumeration e = namespaceSupport.getPrefixes();
while (e.hasMoreElements()) {
prefixes.add((String) e.nextElement());
}
return new IteratorEnumeration(prefixes.iterator());
}
}
public Enumeration getPrefixes(String uri, boolean includePhantom) {
if (!includePhantom || !phantomsExistForCurrentContext()) {
return namespaceSupport.getPrefixes(uri);
} else {
// if a prefix is declared multiple times in phantom or in both
// phantom and namespaceSupport, that
// is ok, but just return it once.
Set prefixes = new HashSet();
ArrayStack prefixStack = (ArrayStack) phantomPrefixes.peek();
for (int i = 0; i < prefixStack.size(); i++) {
// IMPORTANT: Make sure not to return the "" prefix
String prefix = ((String[]) prefixStack.get(i))[0];
String[] entry = ((String[]) prefixStack.get(i));
if (entry[0].length() > 0 && uri.equals(entry[1])) {
prefixes.add(entry[0]);
}
}
Enumeration e = namespaceSupport.getPrefixes(uri);
while (e.hasMoreElements()) {
prefixes.add((String) e.nextElement());
}
return new IteratorEnumeration(prefixes.iterator());
}
}
public String getURI(String prefix, boolean includePhantom) {
if (!includePhantom || !phantomsExistForCurrentContext()) {
return namespaceSupport.getURI(prefix);
} else {
// first search phantoms in LIFO order.
ArrayStack prefixStack = (ArrayStack) phantomPrefixes.peek();
for (int i = prefixStack.size() - 1; i >= 0; i--) {
String[] entry = (String[]) prefixStack.get(i);
if (entry[0].equals(prefix)) {
return entry[1];
}
}
// not found yet, try namespaceSupport
return namespaceSupport.getURI(prefix);
}
}
public void popContext() {
if (logger.isDebugEnabled()) {
logger.debug(" NamespaceMappings popContext()");
}
if (prefixesDeclaredAtDepth.get(contextDepth)) {
ancestorsWithPrefixDeclarations--;
versionStack.pop();
if (logger.isDebugEnabled()) {
logger.debug(" NamespaceMappings ancestorsWithPrefixDeclarations set to "
+ ancestorsWithPrefixDeclarations);
logger.debug(" NamespaceMappings namespaceVersion set to " + versionStack.peek());
}
if (prefixesMaskedAtDepth.get(contextDepth)) {
ancestorsWithPrefixMasking--;
if (logger.isDebugEnabled()) {
logger
.debug(" NamespaceMappings ancestorsWithPrefixMasking set to "
+ ancestorsWithPrefixMasking);
}
}
}
namespaceSupport.popContext();
if (!phantomPrefixes.isEmpty()) {
phantomPrefixes.pop();
}
contextDepth--;
declareOk = false;
}
/**
*
* @return a List containing all prefixes declared in the context being
* popped. The returned List will be reused by this class and must
* not be used by the calling code after the next call to
* popContext2()
*/
public List popContext2() {
// decrement version if the current context has prefix mappings
tmpPrefixes.clear();
if (prefixesDeclaredAtDepth.get(contextDepth)) {
// we only want to return actual declarations, not phantoms
Enumeration e = getDeclaredPrefixes(false);
while (e.hasMoreElements()) {
String prefix = (String) e.nextElement();
tmpPrefixes.add(prefix);
}
}
popContext();
return tmpPrefixes;
}
public void pushContext() {
if (logger.isDebugEnabled()) {
logger.debug(" NamespaceMappings pushContext()");
}
namespaceSupport.pushContext();
contextDepth++;
prefixesDeclaredAtDepth.set(contextDepth, false);
prefixesMaskedAtDepth.set(contextDepth, false);
declareOk = true;
// preload this context with phantoms, then inform phantomPrefixes that
// we have pushed()
if (phantomsExistForCurrentContext()) {
// Add these in FIFO order so that the most recent ones will
// over-write older ones.
ArrayStack prefixStack = (ArrayStack) phantomPrefixes.peek();
for (int i = 0; i < prefixStack.size(); i++) {
String[] entry = (String[]) prefixStack.get(i);
if (null != entry) {
declarePrefix(entry[0], entry[1]);
}
}
}
if (!phantomPrefixes.isEmpty()) {
phantomPrefixes.push(null);
}
}
public int getContextVersion() {
return versionStack.peek();
}
public int getAncestorsWithPrefixMasking() {
return ancestorsWithPrefixMasking;
}
public int getPhantomPrefixCount() {
if (!phantomsExistForCurrentContext()) {
return 0;
} else {
return ((ArrayStack) phantomPrefixes.peek()).size();
}
}
}