/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2007 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
package org.exist.xquery;
import org.exist.dom.DocumentImpl;
import org.exist.dom.DocumentSet;
import org.exist.dom.ExtNodeSet;
import org.exist.dom.NewArrayNodeSet;
import org.exist.dom.NodeProxy;
import org.exist.dom.NodeSet;
import org.exist.dom.NodeVisitor;
import org.exist.dom.StoredNode;
import org.exist.dom.VirtualNodeSet;
import org.exist.numbering.NodeId;
import org.exist.storage.ElementIndex;
import org.exist.storage.ElementValue;
import org.exist.storage.UpdateListener;
import org.exist.xquery.value.*;
import org.exist.memtree.NodeImpl;
import org.exist.memtree.InMemoryNodeSet;
import org.exist.stax.EmbeddedXMLStreamReader;
import org.exist.stax.StaXUtil;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.stream.StreamFilter;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamException;
import java.util.Iterator;
import java.io.IOException;
/**
* Processes all location path steps (like descendant::*, ancestor::XXX).
*
* The results of the first evaluation of the expression are cached for the
* lifetime of the object and only reloaded if the context sequence (as passed
* to the {@link #eval(Sequence, Item)} method) has changed.
*
* @author wolf
*/
public class LocationStep extends Step {
private final int ATTR_DIRECT_SELECT_THRESHOLD = 10;
protected NodeSet currentSet = null;
protected DocumentSet currentDocs = null;
protected UpdateListener listener = null;
protected Expression parent = null;
// Fields for caching the last result
protected CachedResult cached = null;
protected int parentDeps = Dependency.UNKNOWN_DEPENDENCY;
protected boolean preloadedData = false;
protected boolean optimized = false;
protected boolean inUpdate = false;
protected boolean useDirectAttrSelect = true;
protected boolean useDirectChildSelect = false;
protected boolean applyPredicate = true;
// Cache for the current NodeTest type
private Integer nodeTestType = null;
/**
* Creates a new <code>LocationStep</code> instance.
*
* @param context a <code>XQueryContext</code> value
* @param axis an <code>int</code> value
*/
public LocationStep(XQueryContext context, int axis) {
super(context, axis);
}
/**
* Creates a new <code>LocationStep</code> instance.
*
* @param context a <code>XQueryContext</code> value
* @param axis an <code>int</code> value
* @param test a <code>NodeTest</code> value
*/
public LocationStep(XQueryContext context, int axis, NodeTest test) {
super(context, axis, test);
}
/*
* (non-Javadoc)
*
* @see org.exist.xquery.AbstractExpression#getDependencies()
*/
public int getDependencies() {
int deps = Dependency.CONTEXT_SET;
//self axis has an obvious dependency on the context item
//TODO : I guess every other axis too... so we might consider using Constants.UNKNOWN_AXIS here
//BUT
//in a predicate, the expression can't depend on... itself
if (!this.inPredicate && this.axis == Constants.SELF_AXIS)
deps = deps | Dependency.CONTEXT_ITEM;
//TODO : normally, we should call this one...
//int deps = super.getDependencies(); ???
for (Iterator i = predicates.iterator(); i.hasNext();) {
deps |= ((Predicate) i.next()).getDependencies();
}
//TODO : should we remove the CONTEXT_ITEM dependency returned by the predicates ? See the comment above.
//consider nested predicates however...
return deps;
}
/**
* If the current path expression depends on local variables from a for
* expression, we can optimize by preloading entire element or attribute
* sets.
*
* @return Whether or not we can optimize
*/
protected boolean hasPreloadedData() {
// TODO : log elsewhere ?
if (preloadedData) {
context.getProfiler().message(this, Profiler.OPTIMIZATIONS, null,
"Preloaded NodeSets");
return true;
}
if (inUpdate)
return false;
if ((parentDeps & Dependency.LOCAL_VARS) == Dependency.LOCAL_VARS) {
context.getProfiler().message(this, Profiler.OPTIMIZATIONS, null,
"Preloaded NodeSets");
return true;
}
return false;
}
/**
* The method <code>setPreloadedData</code>
*
* @param docs a <code>DocumentSet</code> value
* @param nodes a <code>NodeSet</code> value
*/
public void setPreloadedData(DocumentSet docs, NodeSet nodes) {
this.preloadedData = true;
this.currentDocs = docs;
this.currentSet = nodes;
this.optimized = true;
}
/**
* The method <code>applyPredicate</code>
*
* @param outerSequence a <code>Sequence</code> value
* @param contextSequence a <code>Sequence</code> value
* @return a <code>Sequence</code> value
* @exception XPathException if an error occurs
*/
protected Sequence applyPredicate(Sequence outerSequence,
Sequence contextSequence) throws XPathException {
if (contextSequence == null)
return Sequence.EMPTY_SEQUENCE;
if (predicates.size() == 0 || !applyPredicate ||
(!(contextSequence instanceof VirtualNodeSet) && contextSequence.isEmpty()))
// Nothing to apply
return contextSequence;
Sequence result;
Predicate pred = (Predicate) predicates.get(0);
// If the current step is an // abbreviated step, we have to treat the predicate
// specially to get the context position right. //a[1] translates to /descendant-or-self::node()/a[1],
// so we need to return the 1st a from any parent of a.
//
// If the predicate is known to return a node set, no special treatment is required.
if (abbreviatedStep &&
(pred.getExecutionMode() != Predicate.NODE || !contextSequence.isPersistentSet())) {
result = new ValueSequence();
if (contextSequence.isPersistentSet()) {
NodeSet contextSet = contextSequence.toNodeSet();
outerSequence = contextSet.getParents(getExpressionId());
for (SequenceIterator i = outerSequence.iterate(); i.hasNext(); ) {
NodeValue node = (NodeValue) i.nextItem();
Sequence newContextSeq =
contextSet.selectParentChild((NodeSet) node, NodeSet.DESCENDANT, getExpressionId());
Sequence temp = processPredicate(outerSequence, newContextSeq);
result.addAll(temp);
}
} else {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
outerSequence = nodes.getParents(new AnyNodeTest());
for (SequenceIterator i = outerSequence.iterate(); i.hasNext(); ) {
NodeValue node = (NodeValue) i.nextItem();
InMemoryNodeSet newSet = new InMemoryNodeSet();
((NodeImpl)node).selectChildren(test, newSet);
Sequence temp = processPredicate(outerSequence, newSet);
result.addAll(temp);
}
}
} else
result = processPredicate(outerSequence, contextSequence);
return result;
}
private Sequence processPredicate(Sequence outerSequence, Sequence contextSequence) throws XPathException {
Predicate pred;
Sequence result = contextSequence;
for (Iterator i = predicates.iterator(); i.hasNext() && (result instanceof VirtualNodeSet || !result.isEmpty());) {
// TODO : log and/or profile ?
pred = (Predicate) i.next();
pred.setContextDocSet(getContextDocSet());
result = pred.evalPredicate(outerSequence, result, axis);
//subsequent predicates operate on the result of the previous one
outerSequence = null;
}
return result;
}
/*
* (non-Javadoc)
*
* @see org.exist.xquery.Step#analyze(org.exist.xquery.Expression)
*/
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
this.parent = contextInfo.getParent();
parentDeps = parent.getDependencies();
if ((contextInfo.getFlags() & IN_UPDATE) > 0)
inUpdate = true;
if ((contextInfo.getFlags() & SINGLE_STEP_EXECUTION) > 0) {
preloadedData = true;
}
if ((contextInfo.getFlags() & NEED_INDEX_INFO) > 0) {
useDirectAttrSelect = false;
}
if ((contextInfo.getFlags() & USE_TREE_TRAVERSAL) > 0) {
useDirectChildSelect = true;
}
// Mark ".", which is expanded as self::node() by the parser
//even though it may *also* be relevant with atomic sequences
if (this.axis == Constants.SELF_AXIS && this.test.getType()== Type.NODE)
contextInfo.addFlag(DOT_TEST);
// TODO : log somewhere ?
super.analyze(contextInfo);
}
/**
* The method <code>eval</code>
*
* @param contextSequence a <code>Sequence</code> value
* @param contextItem an <code>Item</code> value
* @return a <code>Sequence</code> value
* @exception XPathException if an error occurs
*/
public Sequence eval(Sequence contextSequence, Item contextItem)
throws XPathException {
if (context.getProfiler().isEnabled()) {
context.getProfiler().start(this);
context.getProfiler().message(this, Profiler.DEPENDENCIES,
"DEPENDENCIES",
Dependency.getDependenciesName(this.getDependencies()));
if (contextSequence != null)
context.getProfiler().message(this, Profiler.START_SEQUENCES,
"CONTEXT SEQUENCE", contextSequence);
if (contextItem != null)
context.getProfiler().message(this, Profiler.START_SEQUENCES,
"CONTEXT ITEM", contextItem.toSequence());
}
Sequence result;
if (contextItem != null) {
contextSequence = contextItem.toSequence();
}
/*
* if(contextSequence == null) //Commented because this the high level
* result nodeset is *really* null result = NodeSet.EMPTY_SET; //Try to
* return cached results else
*/
// TODO: disabled cache for now as it may cause concurrency issues
// better use compile-time inspection and maybe a pragma to mark those
// sections in the query that can be safely cached
// if (cached != null && cached.isValid(contextSequence, contextItem)) {
//
// // WARNING : commented since predicates are *also* applied below !
// // -pb
// /*
// * if (predicates.size() > 0) { applyPredicate(contextSequence,
// * cached.getResult()); } else {
// */
// result = cached.getResult();
// if (context.getProfiler().isEnabled()) {
// LOG.debug("Using cached results");
// }
// context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
// "Using cached results", result);
//
// // }
if (needsComputation()) {
if (contextSequence == null)
throw new XPathException(this, "XPDY0002 : undefined context sequence for '" + this.toString() + "'");
switch (axis) {
case Constants.DESCENDANT_AXIS:
case Constants.DESCENDANT_SELF_AXIS:
result = getDescendants(context, contextSequence);
break;
case Constants.CHILD_AXIS:
//VirtualNodeSets may have modified the axis ; checking the type
//TODO : further checks ?
if (this.test.getType() == Type.ATTRIBUTE) {
this.axis = Constants.ATTRIBUTE_AXIS;
result = getAttributes(context, contextSequence);
} else {
result = getChildren(context, contextSequence);
}
break;
case Constants.ANCESTOR_SELF_AXIS:
case Constants.ANCESTOR_AXIS:
result = getAncestors(context, contextSequence);
break;
case Constants.PARENT_AXIS:
result = getParents(context, contextSequence);
break;
case Constants.SELF_AXIS:
if (!(contextSequence instanceof VirtualNodeSet)
&& Type.subTypeOf(contextSequence.getItemType(),
Type.ATOMIC)) {
//This test is copied from the legacy method getSelfAtomic()
if (!test.isWildcardTest())
throw new XPathException(this, test.toString()
+ " cannot be applied to an atomic value.");
result = contextSequence;
} else {
result = getSelf(context, contextSequence);
}
break;
case Constants.ATTRIBUTE_AXIS:
case Constants.DESCENDANT_ATTRIBUTE_AXIS:
result = getAttributes(context, contextSequence);
break;
case Constants.PRECEDING_AXIS:
result = getPreceding(context, contextSequence);
break;
case Constants.FOLLOWING_AXIS:
result = getFollowing(context, contextSequence);
break;
case Constants.PRECEDING_SIBLING_AXIS:
case Constants.FOLLOWING_SIBLING_AXIS:
result = getSiblings(context, contextSequence);
break;
default:
throw new IllegalArgumentException("Unsupported axis specified");
}
} else {
result = NodeSet.EMPTY_SET;
}
// Caches the result
if (axis != Constants.SELF_AXIS && contextSequence != null && contextSequence.isCacheable()) {
// TODO : cache *after* removing duplicates ? -pb
cached = new CachedResult(contextSequence, contextItem, result);
registerUpdateListener();
}
// Remove duplicate nodes
result.removeDuplicates();
// Apply the predicate
result = applyPredicate(contextSequence, result);
if (context.getProfiler().isEnabled())
context.getProfiler().end(this, "", result);
//actualReturnType = result.getItemType();
return result;
}
// Avoid unnecessary tests (these should be detected by the parser)
private boolean needsComputation() {
// TODO : log this ?
switch (axis) {
// Certainly not exhaustive
case Constants.ANCESTOR_SELF_AXIS:
case Constants.PARENT_AXIS:
// case Constants.SELF_AXIS:
if (nodeTestType == null)
nodeTestType = new Integer(test.getType());
if (nodeTestType.intValue() != Type.NODE
&& nodeTestType.intValue() != Type.ELEMENT
&& nodeTestType.intValue() != Type.PROCESSING_INSTRUCTION) {
if (context.getProfiler().isEnabled())
context.getProfiler().message(this,
Profiler.OPTIMIZATIONS, "OPTIMIZATION",
"avoid useless computations");
return false;
}
}
return true;
}
/**
* The method <code>getSelf</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence a <code>NodeSet</code> value
* @return a <code>Sequence</code> value
*/
protected Sequence getSelf(XQueryContext context, Sequence contextSequence) throws XPathException {
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
return nodes.getSelf(test);
}
NodeSet contextSet = contextSequence.toNodeSet();
if (test.getType() == Type.PROCESSING_INSTRUCTION) {
VirtualNodeSet vset = new VirtualNodeSet(context.getBroker(), axis, test, contextId, contextSet);
vset.setInPredicate(Expression.NO_CONTEXT_ID != contextId);
return vset;
}
if (test.isWildcardTest()) {
if (nodeTestType == null) {
nodeTestType = new Integer(test.getType());
}
if (Type.subTypeOf(nodeTestType.intValue(), Type.NODE)) {
if (Expression.NO_CONTEXT_ID != contextId) {
if (contextSet instanceof VirtualNodeSet) {
((VirtualNodeSet) contextSet).setInPredicate(true);
((VirtualNodeSet) contextSet).setContextId(contextId);
((VirtualNodeSet) contextSet).setSelfIsContext();
} else if (Type.subTypeOf(contextSet.getItemType(), Type.NODE)) {
NodeProxy p;
for (Iterator i = contextSet.iterator(); i.hasNext();) {
p = (NodeProxy) i.next();
if (test.matches(p))
p.addContextNode(contextId, p);
}
}
}
return contextSet;
} else {
VirtualNodeSet vset = new VirtualNodeSet(context.getBroker(), axis, test, contextId,
contextSet);
vset.setInPredicate(Expression.NO_CONTEXT_ID != contextId);
return vset;
}
} else {
DocumentSet docs = getDocumentSet(contextSet);
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
NodeSelector selector = new SelfSelector(contextSet, contextId);
return index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), selector);
}
}
/**
* The method <code>getAttributes</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence a <code>NodeSet</code> value
* @return a <code>NodeSet</code> value
*/
protected Sequence getAttributes(XQueryContext context, Sequence contextSequence) throws XPathException {
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
if (axis == Constants.DESCENDANT_ATTRIBUTE_AXIS)
return nodes.getDescendantAttributes(test);
else
return nodes.getAttributes(test);
}
NodeSet contextSet = contextSequence.toNodeSet();
boolean selectDirect = false;
if (useDirectAttrSelect && axis == Constants.ATTRIBUTE_AXIS) {
if (contextSet instanceof VirtualNodeSet)
selectDirect = ((VirtualNodeSet) contextSet).preferTreeTraversal()
&& contextSet.getLength() < ATTR_DIRECT_SELECT_THRESHOLD;
else
selectDirect = contextSet.getLength() < ATTR_DIRECT_SELECT_THRESHOLD;
}
if (selectDirect) {
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION", "direct attribute selection");
if (contextSet.isEmpty())
return NodeSet.EMPTY_SET;
//TODO : why only the first node ?
NodeProxy proxy = contextSet.get(0);
if (proxy != null)
return contextSet.directSelectAttribute(context.getBroker(), test, contextId);
}
if (test.isWildcardTest()) {
NodeSet result = new VirtualNodeSet(context.getBroker(), axis, test, contextId, contextSet);
((VirtualNodeSet) result).setInPredicate(Expression.NO_CONTEXT_ID != contextId);
return result;
// if there's just a single known node in the context, it is faster
// do directly search for the attribute in the parent node.
}
if (hasPreloadedData()) {
DocumentSet docs = getDocumentSet(contextSet);
synchronized (context) {
if (currentSet == null || currentDocs == null ||
(!optimized && !(docs == currentDocs || docs.equalDocs(currentDocs)))) {
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
// TODO : why a null selector here ? We have one below !
currentSet = index.findElementsByTagName(
ElementValue.ATTRIBUTE, docs, test.getName(), null);
currentDocs = docs;
registerUpdateListener();
}
switch (axis) {
case Constants.ATTRIBUTE_AXIS:
return currentSet.selectParentChild(contextSet,
NodeSet.DESCENDANT, contextId);
case Constants.DESCENDANT_ATTRIBUTE_AXIS:
return currentSet.selectAncestorDescendant(contextSet,
NodeSet.DESCENDANT, false, contextId, true);
default:
throw new IllegalArgumentException(
"Unsupported axis specified");
}
}
} else {
DocumentSet docs = getDocumentSet(contextSet);
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
if (contextSet instanceof ExtNodeSet && !contextSet.getProcessInReverseOrder()) {
return index.findDescendantsByTagName(ElementValue.ATTRIBUTE, test.getName(), axis,
docs, (ExtNodeSet) contextSet, contextId);
} else {
NodeSelector selector;
switch (axis) {
case Constants.ATTRIBUTE_AXIS:
selector = new ChildSelector(contextSet, contextId);
break;
case Constants.DESCENDANT_ATTRIBUTE_AXIS:
selector = new DescendantSelector(contextSet, contextId);
break;
default:
throw new IllegalArgumentException("Unsupported axis specified");
}
return index.findElementsByTagName(ElementValue.ATTRIBUTE, docs, test.getName(), selector);
}
}
}
/**
* The method <code>getChildren</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence the context sequence
* @return a <code>NodeSet</code> value
*/
protected Sequence getChildren(XQueryContext context, Sequence contextSequence) throws XPathException {
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
return nodes.getChildren(test);
}
NodeSet contextSet = contextSequence.toNodeSet();
//TODO : understand this. I guess comments should be treated in a similar way ? -pb
if (test.isWildcardTest() || test.getType() == Type.PROCESSING_INSTRUCTION) {
// test is one out of *, text(), node() including processing-instruction(targetname)
VirtualNodeSet vset = new VirtualNodeSet(context.getBroker(), axis, test, contextId,
contextSet);
vset.setInPredicate(Expression.NO_CONTEXT_ID != contextId);
return vset;
}
// IndexStatistics stats = (IndexStatistics) context.getBroker().getBrokerPool().
// getIndexManager().getIndexById(IndexStatistics.ID);
// int parentDepth = stats.getMaxParentDepth(test.getName());
// LOG.debug("parentDepth for " + test.getName() + ": " + parentDepth);
if (useDirectChildSelect) {
NewArrayNodeSet result = new NewArrayNodeSet();
for (Iterator i = contextSet.iterator(); i.hasNext(); ) {
NodeProxy p = (NodeProxy) i.next();
result.addAll(p.directSelectChild(test.getName(), contextId));
}
return result;
} else if (hasPreloadedData()) {
DocumentSet docs = getDocumentSet(contextSet);
synchronized (context) {
// TODO : understand why this one is different from the other ones
if (currentSet == null || currentDocs == null ||
(!optimized && !(docs == currentDocs || docs.equalDocs(currentDocs)))) {
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
currentSet = index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), null);
currentDocs = docs;
registerUpdateListener();
}
return currentSet.selectParentChild(contextSet, NodeSet.DESCENDANT,
contextId);
}
} else {
DocumentSet docs = getDocumentSet(contextSet);
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
if (contextSet instanceof ExtNodeSet && !contextSet.getProcessInReverseOrder()) {
return index.findDescendantsByTagName(ElementValue.ELEMENT,
test.getName(), axis, docs, (ExtNodeSet) contextSet, contextId);
} else {
// if (contextSet instanceof VirtualNodeSet)
// ((VirtualNodeSet)contextSet).realize();
NodeSelector selector = new ChildSelector(contextSet, contextId);
return index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), selector);
}
}
}
/**
* The method <code>getDescendants</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence the context sequence
* @return a <code>NodeSet</code> value
*/
protected Sequence getDescendants(XQueryContext context, Sequence contextSequence) throws XPathException {
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
return nodes.getDescendants(axis == Constants.DESCENDANT_SELF_AXIS, test);
}
NodeSet contextSet = contextSequence.toNodeSet();
//TODO : understand this. I guess comments should be treated in a similar way ? -pb
if (test.isWildcardTest() || test.getType() == Type.PROCESSING_INSTRUCTION) {
// test is one out of *, text(), node() including processing-instruction(targetname)
VirtualNodeSet vset = new VirtualNodeSet(context.getBroker(), axis, test, contextId,
contextSet);
vset.setInPredicate(Expression.NO_CONTEXT_ID != contextId);
return vset;
} else if (hasPreloadedData()) {
DocumentSet docs = getDocumentSet(contextSet);
synchronized (context) {
// TODO : understand why this one is different from the other ones
if (currentSet == null || currentDocs == null ||
(!optimized && !(docs == currentDocs || docs.equalDocs(currentDocs)))) {
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
currentSet = index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), null);
currentDocs = docs;
registerUpdateListener();
}
switch (axis) {
case Constants.DESCENDANT_SELF_AXIS:
NodeSet tempSet = currentSet.selectAncestorDescendant(contextSet, NodeSet.DESCENDANT,
true, contextId, true);
return tempSet;
case Constants.DESCENDANT_AXIS:
return currentSet.selectAncestorDescendant(contextSet, NodeSet.DESCENDANT, false,
contextId, true);
default:
throw new IllegalArgumentException(
"Unsupported axis specified");
}
}
} else {
DocumentSet docs = contextSet.getDocumentSet();
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled()) {
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
}
if (contextSet instanceof ExtNodeSet) {
return index.findDescendantsByTagName(ElementValue.ELEMENT,
test.getName(), axis, docs, (ExtNodeSet) contextSet, contextId);
} else {
NodeSelector selector;
switch (axis) {
case Constants.DESCENDANT_SELF_AXIS:
selector = new DescendantOrSelfSelector(contextSet, contextId);
break;
case Constants.DESCENDANT_AXIS:
selector = new DescendantSelector(contextSet, contextId);
break;
default:
throw new IllegalArgumentException("Unsupported axis specified");
}
return index.findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), selector);
}
}
}
/**
* The method <code>getSiblings</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence a <code>NodeSet</code> value
* @return a <code>NodeSet</code> value
*/
protected Sequence getSiblings(XQueryContext context, Sequence contextSequence) throws XPathException {
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
if (axis == Constants.PRECEDING_SIBLING_AXIS)
return nodes.getPrecedingSiblings(test);
else
return nodes.getFollowingSiblings(test);
}
NodeSet contextSet = contextSequence.toNodeSet();
//TODO : understand this. I guess comments should be treated in a similar way ? -pb
if (test.getType() == Type.PROCESSING_INSTRUCTION) {
VirtualNodeSet vset = new VirtualNodeSet(context.getBroker(), axis, test, contextId,
contextSet);
vset.setInPredicate(Expression.NO_CONTEXT_ID != contextId);
return vset;
}
if (test.isWildcardTest()) {
NewArrayNodeSet result = new NewArrayNodeSet(contextSet.getLength());
try {
for (Iterator i = contextSet.iterator(); i.hasNext();) {
NodeProxy current = (NodeProxy) i.next();
NodeProxy parent = new NodeProxy(current.getDocument(), current.getNodeId().getParentId());
StreamFilter filter;
if (axis == Constants.PRECEDING_SIBLING_AXIS)
filter = new PrecedingSiblingFilter(test, current, result, contextId);
else
filter = new FollowingSiblingFilter(test, current, result, contextId);
EmbeddedXMLStreamReader reader = context.getBroker().getXMLStreamReader(parent, false);
reader.filter(filter);
}
} catch (IOException e) {
throw new XPathException(this, e.getMessage(), e);
} catch (XMLStreamException e) {
throw new XPathException(this, e.getMessage(), e);
}
return result;
} else {
//TODO : no test on preloaded data ?
DocumentSet docs = getDocumentSet(contextSet);
synchronized (context) {
if (currentSet == null || currentDocs == null
|| !(docs.equalDocs(currentDocs))) {
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
currentSet = index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), null);
currentDocs = docs;
registerUpdateListener();
}
switch (axis) {
case Constants.PRECEDING_SIBLING_AXIS:
return currentSet.selectPrecedingSiblings(contextSet, contextId);
case Constants.FOLLOWING_SIBLING_AXIS:
return currentSet.selectFollowingSiblings(contextSet, contextId);
default:
throw new IllegalArgumentException(
"Unsupported axis specified");
}
}
}
}
@Deprecated
private class SiblingVisitor implements NodeVisitor {
private ExtNodeSet resultSet;
private NodeProxy contextNode;
public SiblingVisitor(ExtNodeSet resultSet) {
this.resultSet = resultSet;
}
public void setContext(NodeProxy contextNode) {
this.contextNode = contextNode;
}
public boolean visit(StoredNode current) {
if (contextNode.getNodeId().getTreeLevel() == current.getNodeId().getTreeLevel()) {
int cmp = current.getNodeId().compareTo(contextNode.getNodeId());
if (((axis == Constants.FOLLOWING_SIBLING_AXIS && cmp > 0) ||
(axis == Constants.PRECEDING_SIBLING_AXIS && cmp < 0)) &&
test.matches(current)) {
NodeProxy sibling = resultSet.get((DocumentImpl) current.getOwnerDocument(), current.getNodeId());
if (sibling == null) {
sibling = new NodeProxy((DocumentImpl) current.getOwnerDocument(), current.getNodeId(),
current.getInternalAddress());
if (Expression.NO_CONTEXT_ID != contextId) {
sibling.addContextNode(contextId, contextNode);
} else
sibling.copyContext(contextNode);
resultSet.add(sibling);
resultSet.setSorted(sibling.getDocument(), true);
} else if (Expression.NO_CONTEXT_ID != contextId)
sibling.addContextNode(contextId, contextNode);
}
}
return true;
}
}
/**
* The method <code>getPreceding</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence a <code>Sequence</code> value
* @return a <code>NodeSet</code> value
* @exception XPathException if an error occurs
*/
protected Sequence getPreceding(XQueryContext context, Sequence contextSequence)
throws XPathException {
int position = -1;
if (hasPositionalPredicate) {
Predicate pred = (Predicate) predicates.get(0);
Sequence seq = pred.preprocess();
NumericValue v = (NumericValue)seq.itemAt(0);
//Non integers return... nothing, not even an error !
if (!v.hasFractionalPart() && !v.isZero()) {
position = v.getInt();
}
}
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
if (hasPositionalPredicate && position > -1)
applyPredicate = false;
return nodes.getPreceding(test, position);
}
NodeSet contextSet = contextSequence.toNodeSet();
//TODO : understand this. I guess comments should be treated in a similar way ? -pb
if (test.getType() == Type.PROCESSING_INSTRUCTION) {
VirtualNodeSet vset = new VirtualNodeSet(context.getBroker(), axis, test, contextId, contextSet);
vset.setInPredicate(Expression.NO_CONTEXT_ID != contextId);
return vset;
}
if (test.isWildcardTest()) {
try {
NodeSet result = new NewArrayNodeSet();
for (Iterator i = contextSet.iterator(); i.hasNext();) {
NodeProxy next = (NodeProxy) i.next();
NodeList cl = next.getDocument().getChildNodes();
for (int j = 0; j < cl.getLength(); j++) {
StoredNode node = (StoredNode) cl.item(j);
NodeProxy root = new NodeProxy(node);
PrecedingFilter filter = new PrecedingFilter(test, next, result, contextId);
EmbeddedXMLStreamReader reader = context.getBroker().getXMLStreamReader(root, false);
reader.filter(filter);
}
}
return result;
} catch (XMLStreamException e) {
throw new XPathException(this, e.getMessage(), e);
} catch (IOException e) {
throw new XPathException(this, e.getMessage(), e);
}
} else {
//TODO : no test on preloaded data ?
DocumentSet docs = getDocumentSet(contextSet);
synchronized (context) {
if (currentSet == null || currentDocs == null
|| !(docs.equalDocs(currentDocs))) {
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
currentSet = index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), null);
currentDocs = docs;
registerUpdateListener();
}
if (hasPositionalPredicate) {
try {
applyPredicate = false;
return currentSet.selectPreceding(contextSet, position, contextId);
} catch (UnsupportedOperationException e) {
return currentSet.selectPreceding(contextSet, contextId);
}
} else
return currentSet.selectPreceding(contextSet, contextId);
}
}
}
/**
* The method <code>getFollowing</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence a <code>Sequence</code> value
* @return a <code>NodeSet</code> value
* @exception XPathException if an error occurs
*/
protected Sequence getFollowing(XQueryContext context, Sequence contextSequence)
throws XPathException {
int position = -1;
if (hasPositionalPredicate) {
Predicate pred = (Predicate) predicates.get(0);
Sequence seq = pred.preprocess();
NumericValue v = (NumericValue)seq.itemAt(0);
//Non integers return... nothing, not even an error !
if (!v.hasFractionalPart() && !v.isZero()) {
position = v.getInt();
}
}
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
if (hasPositionalPredicate && position > -1)
applyPredicate = false;
return nodes.getFollowing(test, position);
}
NodeSet contextSet = contextSequence.toNodeSet();
//TODO : understand this. I guess comments should be treated in a similar way ? -pb
if (test.getType() == Type.PROCESSING_INSTRUCTION) {
VirtualNodeSet vset = new VirtualNodeSet(context.getBroker(), axis, test, contextId, contextSet);
vset.setInPredicate(Expression.NO_CONTEXT_ID != contextId);
return vset;
}
if (test.isWildcardTest() && test.getType() != Type.PROCESSING_INSTRUCTION) {
// handle wildcard steps like following::node()
try {
NodeSet result = new NewArrayNodeSet();
for (Iterator i = contextSet.iterator(); i.hasNext();) {
NodeProxy next = (NodeProxy) i.next();
NodeList cl = next.getDocument().getChildNodes();
for (int j = 0; j < cl.getLength(); j++) {
StoredNode node = (StoredNode) cl.item(j);
NodeProxy root = new NodeProxy(node);
FollowingFilter filter = new FollowingFilter(test, next, result, contextId);
EmbeddedXMLStreamReader reader = context.getBroker().getXMLStreamReader(root, false);
reader.filter(filter);
}
}
return result;
} catch (XMLStreamException e) {
throw new XPathException(this, e.getMessage(), e);
} catch (IOException e) {
throw new XPathException(this, e.getMessage(), e);
}
} else {
//TODO : no test on preloaded data ?
DocumentSet docs = getDocumentSet(contextSet);
synchronized (context) {
if (currentSet == null || currentDocs == null
|| !(docs.equalDocs(currentDocs))) {
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
currentSet = index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), null);
currentDocs = docs;
registerUpdateListener();
}
if (hasPositionalPredicate) {
try {
applyPredicate = false;
return currentSet.selectFollowing(contextSet, position, contextId);
} catch (UnsupportedOperationException e) {
return currentSet.selectFollowing(contextSet, contextId);
}
} else
return currentSet.selectFollowing(contextSet, contextId);
}
}
}
/**
* The method <code>getAncestors</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence a <code>Sequence</code> value
* @return a <code>NodeSet</code> value
*/
protected Sequence getAncestors(XQueryContext context, Sequence contextSequence) throws XPathException {
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
return nodes.getAncestors(axis == Constants.ANCESTOR_SELF_AXIS, test);
}
NodeSet contextSet = contextSequence.toNodeSet();
if (test.isWildcardTest()) {
NodeSet result = new NewArrayNodeSet();
result.setProcessInReverseOrder(true);
for (Iterator i = contextSet.iterator(); i.hasNext();) {
NodeProxy current = (NodeProxy) i.next();
NodeProxy ancestor;
if (axis == Constants.ANCESTOR_SELF_AXIS
&& test.matches(current)) {
ancestor = new NodeProxy(current.getDocument(), current.getNodeId(),
Node.ELEMENT_NODE, current.getInternalAddress());
NodeProxy t = result.get(ancestor);
if (t == null) {
if (Expression.NO_CONTEXT_ID != contextId)
ancestor.addContextNode(contextId, current);
else
ancestor.copyContext(current);
ancestor.addMatches(current);
result.add(ancestor);
} else {
t.addContextNode(contextId, current);
t.addMatches(current);
}
}
NodeId parentID = current.getNodeId().getParentId();
while (parentID != null) {
ancestor = new NodeProxy(current.getDocument(), parentID, Node.ELEMENT_NODE);
// Filter out the temporary nodes wrapper element
if (parentID != NodeId.DOCUMENT_NODE &&
!(parentID.getTreeLevel() == 1 && current.getDocument().getCollection().isTempCollection())) {
if (test.matches(ancestor)) {
NodeProxy t = result.get(ancestor);
if (t == null) {
if (Expression.NO_CONTEXT_ID != contextId)
ancestor.addContextNode(contextId, current);
else
ancestor.copyContext(current);
ancestor.addMatches(current);
result.add(ancestor);
} else {
t.addContextNode(contextId, current);
t.addMatches(current);
}
}
}
parentID = parentID.getParentId();
}
}
return result;
} else if (hasPreloadedData()) {
DocumentSet docs = getDocumentSet(contextSet);
synchronized (context) {
if (currentSet == null || currentDocs == null ||
(!optimized && !(docs == currentDocs || docs.equalDocs(currentDocs)))) {
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
currentSet = index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), null);
currentDocs = docs;
registerUpdateListener();
}
switch (axis) {
case Constants.ANCESTOR_SELF_AXIS:
return currentSet.selectAncestors(contextSet, true,
contextId);
case Constants.ANCESTOR_AXIS:
return currentSet.selectAncestors(contextSet, false,
contextId);
default:
throw new IllegalArgumentException(
"Unsupported axis specified");
}
}
} else {
DocumentSet docs = getDocumentSet(contextSet);
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
NodeSelector selector;
switch (axis) {
case Constants.ANCESTOR_SELF_AXIS:
selector = new AncestorSelector(contextSet, contextId, true, true);
break;
case Constants.ANCESTOR_AXIS:
selector = new AncestorSelector(contextSet, contextId, false, true);
break;
default:
throw new IllegalArgumentException("Unsupported axis specified");
}
return index.findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), selector);
}
}
/**
* The method <code>getParents</code>
*
* @param context a <code>XQueryContext</code> value
* @param contextSequence a <code>Sequence</code> value
* @return a <code>NodeSet</code> value
*/
protected Sequence getParents(XQueryContext context, Sequence contextSequence) throws XPathException {
if (!contextSequence.isPersistentSet()) {
MemoryNodeSet nodes = contextSequence.toMemNodeSet();
return nodes.getParents(test);
}
NodeSet contextSet = contextSequence.toNodeSet();
if (test.isWildcardTest()) {
NodeSet temp = contextSet.getParents(contextId);
NodeSet result = new NewArrayNodeSet();
NodeProxy p;
for (Iterator i = temp.iterator(); i.hasNext(); ) {
p = (NodeProxy) i.next();
if (test.matches(p)) {
result.add(p);
}
}
return result;
} else if (hasPreloadedData()) {
DocumentSet docs = getDocumentSet(contextSet);
synchronized (context) {
if (currentSet == null || currentDocs == null ||
(!optimized && !(docs == currentDocs || docs.equalDocs(currentDocs)))) {
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
currentSet = index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), null);
currentDocs = docs;
registerUpdateListener();
}
return contextSet.selectParentChild(currentSet, NodeSet.ANCESTOR);
}
} else {
DocumentSet docs = getDocumentSet(contextSet);
ElementIndex index = context.getBroker().getElementIndex();
if (context.getProfiler().isEnabled())
context.getProfiler().message(this, Profiler.OPTIMIZATIONS,
"OPTIMIZATION",
"Using structural index '" + index.toString() + "'");
NodeSelector selector = new ParentSelector(contextSet, contextId);
return index.findElementsByTagName(ElementValue.ELEMENT,
docs, test.getName(), selector);
}
}
/**
* The method <code>getDocumentSet</code>
*
* @param contextSet a <code>NodeSet</code> value
* @return a <code>DocumentSet</code> value
*/
protected DocumentSet getDocumentSet(NodeSet contextSet) {
DocumentSet ds = getContextDocSet();
if (ds == null)
ds = contextSet.getDocumentSet();
return ds;
}
/**
* The method <code>getParent</code>
*
* @return an <code>Expression</code> value
*/
public Expression getParentExpression() {
return this.parent;
}
/**
* The method <code>setUseDirectAttrSelect</code>
*
* @param useDirectAttrSelect a <code>boolean</code> value
*/
public void setUseDirectAttrSelect(boolean useDirectAttrSelect) {
this.useDirectAttrSelect = useDirectAttrSelect;
}
/**
* The method <code>registerUpdateListener</code>
*
*/
protected void registerUpdateListener() {
if (listener == null) {
listener = new UpdateListener() {
public void documentUpdated(DocumentImpl document, int event) {
synchronized (context) {
cached = null;
if (document == null || event == UpdateListener.ADD || event == UpdateListener.REMOVE) {
// clear all
currentDocs = null;
currentSet = null;
} else {
if (currentDocs != null
&& currentDocs.contains(document.getDocId())) {
currentDocs = null;
currentSet = null;
}
}
}
}
public void nodeMoved(NodeId oldNodeId, StoredNode newNode) {
}
public void unsubscribe() {
LocationStep.this.listener = null;
}
public void debug() {
LOG.debug("UpdateListener: Line: " + LocationStep.this.toString() +
"; id: " + LocationStep.this.getExpressionId());
}
};
context.registerUpdateListener(listener);
}
}
/**
* The method <code>accept</code>
*
* @param visitor an <code>ExpressionVisitor</code> value
*/
public void accept(ExpressionVisitor visitor) {
visitor.visitLocationStep(this);
}
/*
* (non-Javadoc)
*
* @see org.exist.xquery.Step#resetState()
*/
public void resetState(boolean postOptimization) {
super.resetState(postOptimization);
if (!postOptimization) {
//TODO : preloadedData = false ?
//No : introduces a regression in testMatchCount
//TODO : Investigate...
currentSet = null;
currentDocs = null;
optimized = false;
cached = null;
listener = null;
}
}
private static class FollowingSiblingFilter implements StreamFilter {
private NodeTest test;
private NodeProxy referenceNode;
private NodeSet result;
private int contextId;
private boolean isAfter = false;
private FollowingSiblingFilter(NodeTest test, NodeProxy referenceNode, NodeSet result, int contextId) {
this.test = test;
this.referenceNode = referenceNode;
this.result = result;
this.contextId = contextId;
}
public boolean accept(XMLStreamReader reader) {
if (reader.getEventType() == XMLStreamReader.END_ELEMENT) {
return true;
}
NodeId refId = referenceNode.getNodeId();
NodeId currentId = (NodeId) reader.getProperty(EmbeddedXMLStreamReader.PROPERTY_NODE_ID);
if (!isAfter) {
isAfter = currentId.equals(refId);
} else if (currentId.getTreeLevel() == refId.getTreeLevel() && test.matches(reader)) {
NodeProxy sibling = result.get(referenceNode.getDocument(), currentId);
if (sibling == null) {
sibling = new NodeProxy(referenceNode.getDocument(), currentId,
StaXUtil.streamType2DOM(reader.getEventType()),
((EmbeddedXMLStreamReader) reader).getCurrentPosition());
if (Expression.IGNORE_CONTEXT != contextId) {
if (Expression.NO_CONTEXT_ID == contextId) {
sibling.copyContext(referenceNode);
} else {
sibling.addContextNode(contextId, referenceNode);
}
}
result.add(sibling);
} else if (Expression.NO_CONTEXT_ID != contextId)
sibling.addContextNode(contextId, referenceNode);
}
return true;
}
}
private static class PrecedingSiblingFilter implements StreamFilter {
private NodeTest test;
private NodeProxy referenceNode;
private NodeSet result;
private int contextId;
private PrecedingSiblingFilter(NodeTest test, NodeProxy referenceNode, NodeSet result, int contextId) {
this.test = test;
this.referenceNode = referenceNode;
this.result = result;
this.contextId = contextId;
}
public boolean accept(XMLStreamReader reader) {
if (reader.getEventType() == XMLStreamReader.END_ELEMENT) {
return true;
}
NodeId refId = referenceNode.getNodeId();
NodeId currentId = (NodeId) reader.getProperty(EmbeddedXMLStreamReader.PROPERTY_NODE_ID);
if (currentId.equals(refId)) {
return false;
} else if (currentId.getTreeLevel() == refId.getTreeLevel() && test.matches(reader)) {
NodeProxy sibling = result.get(referenceNode.getDocument(), currentId);
if (sibling == null) {
sibling = new NodeProxy(referenceNode.getDocument(), currentId,
StaXUtil.streamType2DOM(reader.getEventType()),
((EmbeddedXMLStreamReader)reader).getCurrentPosition());
if (Expression.IGNORE_CONTEXT != contextId) {
if (Expression.NO_CONTEXT_ID == contextId) {
sibling.copyContext(referenceNode);
} else {
sibling.addContextNode(contextId, referenceNode);
}
}
result.add(sibling);
} else if (Expression.NO_CONTEXT_ID != contextId)
sibling.addContextNode(contextId, referenceNode);
}
return true;
}
}
private static class FollowingFilter implements StreamFilter {
private NodeTest test;
private NodeProxy referenceNode;
private NodeSet result;
private int contextId;
private boolean isAfter = false;
private FollowingFilter(NodeTest test, NodeProxy referenceNode, NodeSet result, int contextId) {
this.test = test;
this.referenceNode = referenceNode;
this.result = result;
this.contextId = contextId;
}
public boolean accept(XMLStreamReader reader) {
if (reader.getEventType() == XMLStreamReader.END_ELEMENT)
return true;
NodeId refId = referenceNode.getNodeId();
NodeId currentId = (NodeId) reader.getProperty(EmbeddedXMLStreamReader.PROPERTY_NODE_ID);
if (!isAfter)
isAfter = currentId.compareTo(refId) > 0 && !currentId.isDescendantOf(refId);
if (isAfter && !refId.isDescendantOf(currentId) && test.matches(reader)) {
NodeProxy proxy = new NodeProxy(referenceNode.getDocument(), currentId,
StaXUtil.streamType2DOM(reader.getEventType()),
((EmbeddedXMLStreamReader)reader).getCurrentPosition());
if (Expression.IGNORE_CONTEXT != contextId) {
if (Expression.NO_CONTEXT_ID == contextId) {
proxy.copyContext(referenceNode);
} else {
proxy.addContextNode(contextId, referenceNode);
}
}
result.add(proxy);
}
return true;
}
}
private static class PrecedingFilter implements StreamFilter {
private NodeTest test;
private NodeProxy referenceNode;
private NodeSet result;
private int contextId;
private PrecedingFilter(NodeTest test, NodeProxy referenceNode, NodeSet result, int contextId) {
this.test = test;
this.referenceNode = referenceNode;
this.result = result;
this.contextId = contextId;
}
public boolean accept(XMLStreamReader reader) {
if (reader.getEventType() == XMLStreamReader.END_ELEMENT)
return true;
NodeId refId = referenceNode.getNodeId();
NodeId currentId = (NodeId) reader.getProperty(EmbeddedXMLStreamReader.PROPERTY_NODE_ID);
if (currentId.compareTo(refId) >= 0)
return false;
if (!refId.isDescendantOf(currentId) && test.matches(reader)) {
NodeProxy proxy = new NodeProxy(referenceNode.getDocument(), currentId,
StaXUtil.streamType2DOM(reader.getEventType()),
((EmbeddedXMLStreamReader)reader).getCurrentPosition());
if (Expression.IGNORE_CONTEXT != contextId) {
if (Expression.NO_CONTEXT_ID == contextId) {
proxy.copyContext(referenceNode);
} else {
proxy.addContextNode(contextId, referenceNode);
}
}
result.add(proxy);
}
return true;
}
}
}