- /*
- * Copyright 1999-2004 The Apache Software Foundation
- *
- * Licensed 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.Comparator;
- 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;
-
- /**
- * 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: 1.30 $ $Date: 2004/03/25 05:42:01 $
- */
- public abstract class EvalContext implements ExpressionContext, Iterator {
- protected EvalContext parentContext;
- protected RootContext rootContext;
- protected int position = 0;
- private boolean startedSetIteration = false;
- private boolean done = false;
- private boolean hasPerformedIteratorStep = false;
- private Iterator pointerIterator;
-
- // Sorts in the reverse order to the one defined by the Comparable
- // interface.
- private static final Comparator REVERSE_COMPARATOR = new Comparator() {
- public int compare(Object o1, Object o2) {
- return ((Comparable) o2).compareTo(o1);
- }
- };
-
- 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() {
- if (parentContext != null && parentContext.isChildOrderingRequired()) {
- return 1;
- }
- return 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.
- */
- public boolean isChildOrderingRequired() {
- // Default behavior: if this context needs to be ordered,
- // the children need to be ordered too
- if (getDocumentOrder() != 0) {
- return true;
- }
- return false;
- }
-
- /**
- * Returns true if there are mode nodes matching the context's constraints.
- */
- public boolean hasNext() {
- if (pointerIterator != null) {
- return pointerIterator.hasNext();
- }
-
- if (getDocumentOrder() != 0) {
- return constructIterator();
- }
- else {
- if (!done && !hasPerformedIteratorStep) {
- performIteratorStep();
- }
- return !done;
- }
- }
-
- /**
- * Returns the next node pointer in the context
- */
- public Object next() {
- if (pointerIterator != null) {
- return pointerIterator.next();
- }
-
- if (getDocumentOrder() != 0) {
- if (!constructIterator()) {
- throw new NoSuchElementException();
- }
- return pointerIterator.next();
- }
- else {
- 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
- */
- public void remove() {
- throw new UnsupportedOperationException(
- "JXPath iterators cannot remove nodes");
- }
-
- private boolean constructIterator() {
- HashSet set = new HashSet();
- ArrayList list = new ArrayList();
- while (nextSet()) {
- while (nextNode()) {
- NodePointer pointer = getCurrentNodePointer();
- if (!set.contains(pointer)) {
- // Pointer cln = (Pointer) pointer.clone();
- set.add(pointer);
- list.add(pointer);
- }
- }
- }
- if (list.isEmpty()) {
- return false;
- }
-
- if (getDocumentOrder() == 1) {
- Collections.sort(list);
- }
- else {
- Collections.sort(list, REVERSE_COMPARATOR);
- }
- pointerIterator = list.iterator();
- return true;
- }
-
- /**
- * Returns the list of all Pointers in this context for the current
- * position of the parent context.
- */
- 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.
- */
- 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
- */
- public Object getValue() {
- return getNodeSet();
- }
-
- public String toString() {
- Pointer ptr = getContextNodePointer();
- if (ptr == null) {
- return "Empty expression context";
- }
- else {
- return "Expression context [" + getPosition() + "] " + ptr.asPath();
- }
- }
-
- /**
- * Returns the root context of the path, which provides easy
- * access to variables and functions.
- */
- 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;
- }
-
- public int getCurrentPosition() {
- return position;
- }
-
- /**
- * Returns the first encountered Pointer that matches the current
- * context's criteria.
- */
- public Pointer getSingleNodePointer() {
- reset();
- while (nextSet()) {
- if (nextNode()) {
- return getCurrentNodePointer();
- }
- }
- return null;
- }
-
- /**
- * Returns the current context node. Undefined before the beginning
- * of the iteration.
- */
- public abstract NodePointer getCurrentNodePointer();
-
- /**
- * Returns true if there is another sets of objects to interate over.
- * Resets the current position and node.
- */
- 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.
- */
- 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.
- */
- public boolean setPosition(int position) {
- this.position = position;
- return true;
- }
- }