/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* $Id: AxesWalker.java 513117 2007-03-01 03:28:52Z minchau $
*/
package org.apache.xpath.axes;
import java.util.Vector;
import org.apache.xalan.res.XSLMessages;
import org.apache.xml.dtm.DTM;
import org.apache.xml.dtm.DTMAxisTraverser;
import org.apache.xml.dtm.DTMIterator;
import org.apache.xpath.Expression;
import org.apache.xpath.ExpressionOwner;
import org.apache.xpath.XPathContext;
import org.apache.xpath.XPathVisitor;
import org.apache.xpath.compiler.Compiler;
import org.apache.xpath.res.XPATHErrorResources;
/**
* Serves as common interface for axes Walkers, and stores common
* state variables.
*/
public class AxesWalker extends PredicatedNodeTest
implements Cloneable, PathComponent, ExpressionOwner
{
static final long serialVersionUID = -2966031951306601247L;
/**
* Construct an AxesWalker using a LocPathIterator.
*
* @param locPathIterator non-null reference to the parent iterator.
*/
public AxesWalker(LocPathIterator locPathIterator, int axis)
{
super( locPathIterator );
m_axis = axis;
}
public final WalkingIterator wi()
{
return (WalkingIterator)m_lpi;
}
/**
* Initialize an AxesWalker during the parse of the XPath expression.
*
* @param compiler The Compiler object that has information about this
* walker in the op map.
* @param opPos The op code position of this location step.
* @param stepType The type of location step.
*
* @throws javax.xml.transform.TransformerException
*/
public void init(Compiler compiler, int opPos, int stepType)
throws javax.xml.transform.TransformerException
{
initPredicateInfo(compiler, opPos);
// int testType = compiler.getOp(nodeTestOpPos);
}
/**
* Get a cloned AxesWalker.
*
* @return A new AxesWalker that can be used without mutating this one.
*
* @throws CloneNotSupportedException
*/
public Object clone() throws CloneNotSupportedException
{
// Do not access the location path itterator during this operation!
AxesWalker clone = (AxesWalker) super.clone();
//clone.setCurrentNode(clone.m_root);
// clone.m_isFresh = true;
return clone;
}
/**
* Do a deep clone of this walker, including next and previous walkers.
* If the this AxesWalker is on the clone list, don't clone but
* return the already cloned version.
*
* @param cloneOwner non-null reference to the cloned location path
* iterator to which this clone will be added.
* @param cloneList non-null vector of sources in odd elements, and the
* corresponding clones in even vectors.
*
* @return non-null clone, which may be a new clone, or may be a clone
* contained on the cloneList.
*/
AxesWalker cloneDeep(WalkingIterator cloneOwner, Vector cloneList)
throws CloneNotSupportedException
{
AxesWalker clone = findClone(this, cloneList);
if(null != clone)
return clone;
clone = (AxesWalker)this.clone();
clone.setLocPathIterator(cloneOwner);
if(null != cloneList)
{
cloneList.addElement(this);
cloneList.addElement(clone);
}
if(wi().m_lastUsedWalker == this)
cloneOwner.m_lastUsedWalker = clone;
if(null != m_nextWalker)
clone.m_nextWalker = m_nextWalker.cloneDeep(cloneOwner, cloneList);
// If you don't check for the cloneList here, you'll go into an
// recursive infinate loop.
if(null != cloneList)
{
if(null != m_prevWalker)
clone.m_prevWalker = m_prevWalker.cloneDeep(cloneOwner, cloneList);
}
else
{
if(null != m_nextWalker)
clone.m_nextWalker.m_prevWalker = clone;
}
return clone;
}
/**
* Find a clone that corresponds to the key argument.
*
* @param key The original AxesWalker for which there may be a clone.
* @param cloneList vector of sources in odd elements, and the
* corresponding clones in even vectors, may be null.
*
* @return A clone that corresponds to the key, or null if key not found.
*/
static AxesWalker findClone(AxesWalker key, Vector cloneList)
{
if(null != cloneList)
{
// First, look for clone on list.
int n = cloneList.size();
for (int i = 0; i < n; i+=2)
{
if(key == cloneList.elementAt(i))
return (AxesWalker)cloneList.elementAt(i+1);
}
}
return null;
}
/**
* Detaches the walker from the set which it iterated over, releasing
* any computational resources and placing the iterator in the INVALID
* state.
*/
public void detach()
{
m_currentNode = DTM.NULL;
m_dtm = null;
m_traverser = null;
m_isFresh = true;
m_root = DTM.NULL;
}
//=============== TreeWalker Implementation ===============
/**
* The root node of the TreeWalker, as specified in setRoot(int root).
* Note that this may actually be below the current node.
*
* @return The context node of the step.
*/
public int getRoot()
{
return m_root;
}
/**
* Get the analysis bits for this walker, as defined in the WalkerFactory.
* @return One of WalkerFactory#BIT_DESCENDANT, etc.
*/
public int getAnalysisBits()
{
int axis = getAxis();
int bit = WalkerFactory.getAnalysisBitFromAxes(axis);
return bit;
}
/**
* Set the root node of the TreeWalker.
* (Not part of the DOM2 TreeWalker interface).
*
* @param root The context node of this step.
*/
public void setRoot(int root)
{
// %OPT% Get this directly from the lpi.
XPathContext xctxt = wi().getXPathContext();
m_dtm = xctxt.getDTM(root);
m_traverser = m_dtm.getAxisTraverser(m_axis);
m_isFresh = true;
m_foundLast = false;
m_root = root;
m_currentNode = root;
if (DTM.NULL == root)
{
throw new RuntimeException(
XSLMessages.createXPATHMessage(XPATHErrorResources.ER_SETTING_WALKER_ROOT_TO_NULL, null)); //"\n !!!! Error! Setting the root of a walker to null!!!");
}
resetProximityPositions();
}
/**
* The node at which the TreeWalker is currently positioned.
*
The value must not be null. Alterations to the DOM tree may cause
* the current node to no longer be accepted by the TreeWalker's
* associated filter. currentNode may also be explicitly set to any node,
* whether or not it is within the subtree specified by the root node or
* would be accepted by the filter and whatToShow flags. Further
* traversal occurs relative to currentNode even if it is not part of the
* current view by applying the filters in the requested direction (not
* changing currentNode where no traversal is possible).
*
* @return The node at which the TreeWalker is currently positioned, only null
* if setRoot has not yet been called.
*/
public final int getCurrentNode()
{
return m_currentNode;
}
/**
* Set the next walker in the location step chain.
*
*
* @param walker Reference to AxesWalker derivative, or may be null.
*/
public void setNextWalker(AxesWalker walker)
{
m_nextWalker = walker;
}
/**
* Get the next walker in the location step chain.
*
*
* @return Reference to AxesWalker derivative, or null.
*/
public AxesWalker getNextWalker()
{
return m_nextWalker;
}
/**
* Set or clear the previous walker reference in the location step chain.
*
*
* @param walker Reference to previous walker reference in the location
* step chain, or null.
*/
public void setPrevWalker(AxesWalker walker)
{
m_prevWalker = walker;
}
/**
* Get the previous walker reference in the location step chain.
*
*
* @return Reference to previous walker reference in the location
* step chain, or null.
*/
public AxesWalker getPrevWalker()
{
return m_prevWalker;
}
/**
* This is simply a way to bottle-neck the return of the next node, for
* diagnostic purposes.
*
* @param n Node to return, or null.
*
* @return The argument.
*/
private int returnNextNode(int n)
{
return n;
}
/**
* Get the next node in document order on the axes.
*
* @return the next node in document order on the axes, or null.
*/
protected int getNextNode()
{
if (m_foundLast)
return DTM.NULL;
if (m_isFresh)
{
m_currentNode = m_traverser.first(m_root);
m_isFresh = false;
}
// I shouldn't have to do this the check for current node, I think.
// numbering\numbering24.xsl fails if I don't do this. I think
// it occurs as the walkers are backing up. -sb
else if(DTM.NULL != m_currentNode)
{
m_currentNode = m_traverser.next(m_root, m_currentNode);
}
if (DTM.NULL == m_currentNode)
this.m_foundLast = true;
return m_currentNode;
}
/**
* Moves the TreeWalker
to the next visible node in document
* order relative to the current node, and returns the new node. If the
* current node has no next node, or if the search for nextNode attempts
* to step upward from the TreeWalker's root node, returns
* null
, and retains the current node.
* @return The new node, or null
if the current node has no
* next node in the TreeWalker's logical view.
*/
public int nextNode()
{
int nextNode = DTM.NULL;
AxesWalker walker = wi().getLastUsedWalker();
while (true)
{
if (null == walker)
break;
nextNode = walker.getNextNode();
if (DTM.NULL == nextNode)
{
walker = walker.m_prevWalker;
}
else
{
if (walker.acceptNode(nextNode) != DTMIterator.FILTER_ACCEPT)
{
continue;
}
if (null == walker.m_nextWalker)
{
wi().setLastUsedWalker(walker);
// return walker.returnNextNode(nextNode);
break;
}
else
{
AxesWalker prev = walker;
walker = walker.m_nextWalker;
walker.setRoot(nextNode);
walker.m_prevWalker = prev;
continue;
}
} // if(null != nextNode)
} // while(null != walker)
return nextNode;
}
//============= End TreeWalker Implementation =============
/**
* Get the index of the last node that can be itterated to.
*
*
* @param xctxt XPath runtime context.
*
* @return the index of the last node that can be itterated to.
*/
public int getLastPos(XPathContext xctxt)
{
int pos = getProximityPosition();
AxesWalker walker;
try
{
walker = (AxesWalker) clone();
}
catch (CloneNotSupportedException cnse)
{
return -1;
}
walker.setPredicateCount(m_predicateIndex);
walker.setNextWalker(null);
walker.setPrevWalker(null);
WalkingIterator lpi = wi();
AxesWalker savedWalker = lpi.getLastUsedWalker();
try
{
lpi.setLastUsedWalker(walker);
int next;
while (DTM.NULL != (next = walker.nextNode()))
{
pos++;
}
// TODO: Should probably save this in the iterator.
}
finally
{
lpi.setLastUsedWalker(savedWalker);
}
// System.out.println("pos: "+pos);
return pos;
}
//============= State Data =============
/**
* The DTM for the root. This can not be used, or must be changed,
* for the filter walker, or any walker that can have nodes
* from multiple documents.
* Never, ever, access this value without going through getDTM(int node).
*/
private DTM m_dtm;
/**
* Set the DTM for this walker.
*
* @param dtm Non-null reference to a DTM.
*/
public void setDefaultDTM(DTM dtm)
{
m_dtm = dtm;
}
/**
* Get the DTM for this walker.
*
* @return Non-null reference to a DTM.
*/
public DTM getDTM(int node)
{
//
return wi().getXPathContext().getDTM(node);
}
/**
* Returns true if all the nodes in the iteration well be returned in document
* order.
* Warning: This can only be called after setRoot has been called!
*
* @return true as a default.
*/
public boolean isDocOrdered()
{
return true;
}
/**
* Returns the axis being iterated, if it is known.
*
* @return Axis.CHILD, etc., or -1 if the axis is not known or is of multiple
* types.
*/
public int getAxis()
{
return m_axis;
}
/**
* This will traverse the heararchy, calling the visitor for
* each member. If the called visitor method returns
* false, the subtree should not be called.
*
* @param owner The owner of the visitor, where that path may be
* rewritten if needed.
* @param visitor The visitor whose appropriate method will be called.
*/
public void callVisitors(ExpressionOwner owner, XPathVisitor visitor)
{
if(visitor.visitStep(owner, this))
{
callPredicateVisitors(visitor);
if(null != m_nextWalker)
{
m_nextWalker.callVisitors(this, visitor);
}
}
}
/**
* @see ExpressionOwner#getExpression()
*/
public Expression getExpression()
{
return m_nextWalker;
}
/**
* @see ExpressionOwner#setExpression(Expression)
*/
public void setExpression(Expression exp)
{
exp.exprSetParent(this);
m_nextWalker = (AxesWalker)exp;
}
/**
* @see Expression#deepEquals(Expression)
*/
public boolean deepEquals(Expression expr)
{
if (!super.deepEquals(expr))
return false;
AxesWalker walker = (AxesWalker)expr;
if(this.m_axis != walker.m_axis)
return false;
return true;
}
/**
* The root node of the TreeWalker, as specified when it was created.
*/
transient int m_root = DTM.NULL;
/**
* The node at which the TreeWalker is currently positioned.
*/
private transient int m_currentNode = DTM.NULL;
/** True if an itteration has not begun. */
transient boolean m_isFresh;
/** The next walker in the location step chain.
* @serial */
protected AxesWalker m_nextWalker;
/** The previous walker in the location step chain, or null.
* @serial */
AxesWalker m_prevWalker;
/** The traversal axis from where the nodes will be filtered. */
protected int m_axis = -1;
/** The DTM inner traversal class, that corresponds to the super axis. */
protected DTMAxisTraverser m_traverser;
}