| /* |
| * 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. |
| */ |
| package org.apache.commons.jxpath.ri; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| |
| import org.apache.commons.jxpath.BasicNodeSet; |
| import org.apache.commons.jxpath.ExpressionContext; |
| import org.apache.commons.jxpath.JXPathContext; |
| import org.apache.commons.jxpath.JXPathException; |
| import org.apache.commons.jxpath.NodeSet; |
| import org.apache.commons.jxpath.Pointer; |
| import org.apache.commons.jxpath.ri.axes.RootContext; |
| import org.apache.commons.jxpath.ri.model.NodePointer; |
| import org.apache.commons.jxpath.util.ReverseComparator; |
| |
| /** |
| * An XPath evaluation context. |
| * |
| * When evaluating a path, a chain of EvalContexts is created, each context in |
| * the chain representing a step of the path. Subclasses of EvalContext |
| * implement behavior of various XPath axes: "child::", "parent::" etc. |
| * |
| * @author Dmitri Plotnikov |
| * @version $Revision$ $Date$ |
| */ |
| public abstract class EvalContext implements ExpressionContext, Iterator { |
| /** parent context */ |
| protected EvalContext parentContext; |
| |
| /** root context */ |
| protected RootContext rootContext; |
| |
| /** position */ |
| protected int position = 0; |
| |
| private boolean startedSetIteration = false; |
| private boolean done = false; |
| private boolean hasPerformedIteratorStep = false; |
| private Iterator pointerIterator; |
| |
| /** |
| * Create a new EvalContext. |
| * @param parentContext parent context |
| */ |
| public EvalContext(EvalContext parentContext) { |
| this.parentContext = parentContext; |
| } |
| |
| public Pointer getContextNodePointer() { |
| return getCurrentNodePointer(); |
| } |
| |
| public JXPathContext getJXPathContext() { |
| return getRootContext().getJXPathContext(); |
| } |
| |
| public int getPosition() { |
| return position; |
| } |
| |
| /** |
| * Determines the document order for this context. |
| * |
| * @return 1 ascending order, -1 descending order, |
| * 0 - does not require ordering |
| */ |
| public int getDocumentOrder() { |
| return parentContext != null && parentContext.isChildOrderingRequired() ? 1 : 0; |
| } |
| |
| /** |
| * Even if this context has the natural ordering and therefore does |
| * not require collecting and sorting all nodes prior to returning them, |
| * such operation may be required for any child context. |
| * @return boolean |
| */ |
| public boolean isChildOrderingRequired() { |
| // Default behavior: if this context needs to be ordered, |
| // the children need to be ordered too |
| return getDocumentOrder() != 0; |
| } |
| |
| /** |
| * Returns true if there are mode nodes matching the context's constraints. |
| * @return boolean |
| */ |
| public boolean hasNext() { |
| if (pointerIterator != null) { |
| return pointerIterator.hasNext(); |
| } |
| if (getDocumentOrder() != 0) { |
| return constructIterator(); |
| } |
| if (!done && !hasPerformedIteratorStep) { |
| performIteratorStep(); |
| } |
| return !done; |
| } |
| |
| /** |
| * Returns the next node pointer in the context |
| * @return Object |
| */ |
| public Object next() { |
| if (pointerIterator != null) { |
| return pointerIterator.next(); |
| } |
| |
| if (getDocumentOrder() != 0) { |
| if (!constructIterator()) { |
| throw new NoSuchElementException(); |
| } |
| return pointerIterator.next(); |
| } |
| if (!done && !hasPerformedIteratorStep) { |
| performIteratorStep(); |
| } |
| if (done) { |
| throw new NoSuchElementException(); |
| } |
| hasPerformedIteratorStep = false; |
| return getCurrentNodePointer(); |
| } |
| |
| /** |
| * Moves the iterator forward by one position |
| */ |
| private void performIteratorStep() { |
| done = true; |
| if (position != 0 && nextNode()) { |
| done = false; |
| } |
| else { |
| while (nextSet()) { |
| if (nextNode()) { |
| done = false; |
| break; |
| } |
| } |
| } |
| hasPerformedIteratorStep = true; |
| } |
| |
| /** |
| * Operation is not supported |
| * @throws UnsupportedOperationException |
| */ |
| public void remove() { |
| throw new UnsupportedOperationException( |
| "JXPath iterators cannot remove nodes"); |
| } |
| |
| /** |
| * Construct an iterator. |
| * @return whether the Iterator was constructed |
| */ |
| private boolean constructIterator() { |
| HashSet set = new HashSet(); |
| ArrayList list = new ArrayList(); |
| while (nextSet()) { |
| while (nextNode()) { |
| NodePointer pointer = getCurrentNodePointer(); |
| if (!set.contains(pointer)) { |
| set.add(pointer); |
| list.add(pointer); |
| } |
| } |
| } |
| if (list.isEmpty()) { |
| return false; |
| } |
| |
| sortPointers(list); |
| |
| pointerIterator = list.iterator(); |
| return true; |
| } |
| |
| /** |
| * Sort a list of pointers based on document order. |
| * @param l the list to sort. |
| */ |
| protected void sortPointers(List l) { |
| switch (getDocumentOrder()) { |
| case 1: |
| Collections.sort(l); |
| break; |
| case -1: |
| Collections.sort(l, ReverseComparator.INSTANCE); |
| break; |
| default: |
| } |
| } |
| |
| /** |
| * Returns the list of all Pointers in this context for the current |
| * position of the parent context. |
| * @return List |
| */ |
| public List getContextNodeList() { |
| int pos = position; |
| if (pos != 0) { |
| reset(); |
| } |
| List list = new ArrayList(); |
| while (nextNode()) { |
| list.add(getCurrentNodePointer()); |
| } |
| if (pos != 0) { |
| setPosition(pos); |
| } |
| else { |
| reset(); |
| } |
| return list; |
| } |
| |
| /** |
| * Returns the list of all Pointers in this context for all positions |
| * of the parent contexts. If there was an ongoing iteration over |
| * this context, the method should not be called. |
| * @return NodeSet |
| */ |
| public NodeSet getNodeSet() { |
| if (position != 0) { |
| throw new JXPathException( |
| "Simultaneous operations: " |
| + "should not request pointer list while " |
| + "iterating over an EvalContext"); |
| } |
| BasicNodeSet set = new BasicNodeSet(); |
| while (nextSet()) { |
| while (nextNode()) { |
| set.add((Pointer) getCurrentNodePointer().clone()); |
| } |
| } |
| |
| return set; |
| } |
| |
| /** |
| * Typically returns the NodeSet by calling getNodeSet(), |
| * but will be overridden for contexts that more naturally produce |
| * individual values, e.g. VariableContext |
| * @return Object |
| */ |
| public Object getValue() { |
| return getNodeSet(); |
| } |
| |
| public String toString() { |
| Pointer ptr = getContextNodePointer(); |
| return ptr == null ? "Empty expression context" : "Expression context [" + getPosition() |
| + "] " + ptr.asPath(); |
| } |
| |
| /** |
| * Returns the root context of the path, which provides easy |
| * access to variables and functions. |
| * @return RootContext |
| */ |
| public RootContext getRootContext() { |
| if (rootContext == null) { |
| rootContext = parentContext.getRootContext(); |
| } |
| return rootContext; |
| } |
| |
| /** |
| * Sets current position = 0, which is the pre-iteration state. |
| */ |
| public void reset() { |
| position = 0; |
| } |
| |
| /** |
| * Get the current position. |
| * @return int position. |
| */ |
| public int getCurrentPosition() { |
| return position; |
| } |
| |
| /** |
| * Returns the first encountered Pointer that matches the current |
| * context's criteria. |
| * @return Pointer |
| */ |
| public Pointer getSingleNodePointer() { |
| reset(); |
| while (nextSet()) { |
| if (nextNode()) { |
| return getCurrentNodePointer(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the current context node. Undefined before the beginning |
| * of the iteration. |
| * @return NodePoiner |
| */ |
| public abstract NodePointer getCurrentNodePointer(); |
| |
| /** |
| * Returns true if there is another sets of objects to interate over. |
| * Resets the current position and node. |
| * @return boolean |
| */ |
| public boolean nextSet() { |
| reset(); // Restart iteration within the set |
| |
| // Most of the time you have one set per parent node |
| // First time this method is called, we should look for |
| // the first parent set that contains at least one node. |
| if (!startedSetIteration) { |
| startedSetIteration = true; |
| while (parentContext.nextSet()) { |
| if (parentContext.nextNode()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // In subsequent calls, we see if the parent context |
| // has any nodes left in the current set |
| if (parentContext.nextNode()) { |
| return true; |
| } |
| |
| // If not, we look for the next set that contains |
| // at least one node |
| while (parentContext.nextSet()) { |
| if (parentContext.nextNode()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true if there is another object in the current set. |
| * Switches the current position and node to the next object. |
| * @return boolean |
| */ |
| public abstract boolean nextNode(); |
| |
| /** |
| * Moves the current position to the specified index. Used with integer |
| * predicates to quickly get to the n'th element of the node set. |
| * Returns false if the position is out of the node set range. |
| * You can call it with 0 as the position argument to restart the iteration. |
| * @param position to set |
| * @return boolean |
| */ |
| public boolean setPosition(int position) { |
| this.position = position; |
| return true; |
| } |
| } |