    1   /*
    2    * Copyright 1999-2004 The Apache Software Foundation
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  package org.apache.commons.jxpath.ri;
    17
    18  import java.util.ArrayList;
    19  import java.util.Collections;
    20  import java.util.Comparator;
    21  import java.util.HashSet;
    22  import java.util.Iterator;
    23  import java.util.List;
    24  import java.util.NoSuchElementException;
    25
    26  import org.apache.commons.jxpath.BasicNodeSet;
    27  import org.apache.commons.jxpath.ExpressionContext;
    28  import org.apache.commons.jxpath.JXPathContext;
    29  import org.apache.commons.jxpath.JXPathException;
    30  import org.apache.commons.jxpath.NodeSet;
    31  import org.apache.commons.jxpath.Pointer;
    32  import org.apache.commons.jxpath.ri.axes.RootContext;
    33  import org.apache.commons.jxpath.ri.model.NodePointer;
    34
    35  /***
    36   * An XPath evaluation context.
    37   *
    38   * When  evaluating a path, a chain of EvalContexts is created, each context in
    39   * the chain representing a step of the path. Subclasses of EvalContext
    40   * implement behavior of various XPath axes: "child::", "parent::" etc.
    41   *
    42   * @author Dmitri Plotnikov
    43   * @version $Revision: 1.30 $ $Date: 2004/03/25 05:42:01 $
    44   */
    45  public abstract class EvalContext implements ExpressionContext, Iterator {
    46      protected EvalContext parentContext;
    47      protected RootContext rootContext;
    48      protected int position = 0;
    49      private boolean startedSetIteration = false;
    50      private boolean done = false;
    51      private boolean hasPerformedIteratorStep = false;
    52      private Iterator pointerIterator;
    53
    54      // Sorts in the reverse order to the one defined by the Comparable
    55      // interface.
    56      private static final Comparator REVERSE_COMPARATOR = new Comparator() {
    57          public int compare(Object o1, Object o2) {
    58              return ((Comparable) o2).compareTo(o1);
    59          }
    60      };
    61
    62      public EvalContext(EvalContext parentContext) {
    63          this.parentContext = parentContext;
    64      }
    65
    66      public Pointer getContextNodePointer() {
    67          return getCurrentNodePointer();
    68      }
    69
    70      public JXPathContext getJXPathContext() {
    71          return getRootContext().getJXPathContext();
    72      }
    73
    74      public int getPosition() {
    75          return position;
    76      }
    77
    78      /***
    79       * Determines the document order for this context.
    80       *
    81       * @return 1 ascending order, -1 descending order,
    82       *  0 - does not require ordering
    83       */
    84      public int getDocumentOrder() {
    85          if (parentContext != null && parentContext.isChildOrderingRequired()) {
    86              return 1;
    87          }
    88          return 0;
    89      }
    90
    91      /***
    92       * Even if this context has the natural ordering and therefore does
    93       * not require collecting and sorting all nodes prior to returning them,
    94       * such operation may be required for any child context.
    95       */
    96      public boolean isChildOrderingRequired() {
    97          // Default behavior: if this context needs to be ordered,
    98          // the children need to be ordered too
    99          if (getDocumentOrder() != 0) {
    100             return true;
    101         }
    102         return false;
    103     }
    104
    105     /***
    106      * Returns true if there are mode nodes matching the context's constraints.
    107      */
    108     public boolean hasNext() {
    109         if (pointerIterator != null) {
    110             return pointerIterator.hasNext();
    111         }
    112
    113         if (getDocumentOrder() != 0) {
    114             return constructIterator();
    115         }
    116         else {
    117             if (!done && !hasPerformedIteratorStep) {
    118                 performIteratorStep();
    119             }
    120             return !done;
    121         }
    122     }
    123
    124     /***
    125      * Returns the next node pointer in the context
    126      */
    127     public Object next() {
    128         if (pointerIterator != null) {
    129             return pointerIterator.next();
    130         }
    131
    132         if (getDocumentOrder() != 0) {
    133             if (!constructIterator()) {
    134                 throw new NoSuchElementException();
    135             }
    136             return pointerIterator.next();
    137         }
    138         else {
    139             if (!done && !hasPerformedIteratorStep) {
    140                 performIteratorStep();
    141             }
    142             if (done) {
    143                 throw new NoSuchElementException();
    144             }
    145             hasPerformedIteratorStep = false;
    146             return getCurrentNodePointer();
    147         }
    148     }
    149
    150     /***
    151      * Moves the iterator forward by one position
    152      */
    153     private void performIteratorStep() {
    154         done = true;
    155         if (position != 0 && nextNode()) {
    156             done = false;
    157         }
    158         else {
    159             while (nextSet()) {
    160                 if (nextNode()) {
    161                     done = false;
    162                     break;
    163                 }
    164             }
    165         }
    166         hasPerformedIteratorStep = true;
    167     }
    168
    169     /***
    170      * Operation is not supported
    171      */
    172     public void remove() {
    173         throw new UnsupportedOperationException(
    174             "JXPath iterators cannot remove nodes");
    175     }
    176
    177     private boolean constructIterator() {
    178         HashSet set = new HashSet();
    179         ArrayList list = new ArrayList();
    180         while (nextSet()) {
    181             while (nextNode()) {
    182                 NodePointer pointer = getCurrentNodePointer();
    183                 if (!set.contains(pointer)) {
    184 //                    Pointer cln = (Pointer) pointer.clone();
    185                     set.add(pointer);
    186                     list.add(pointer);
    187                 }
    188             }
    189         }
    190         if (list.isEmpty()) {
    191             return false;
    192         }
    193
    194         if (getDocumentOrder() == 1) {
    195             Collections.sort(list);
    196         }
    197         else {
    198             Collections.sort(list, REVERSE_COMPARATOR);
    199         }
    200         pointerIterator = list.iterator();
    201         return true;
    202     }
    203
    204     /***
    205      * Returns the list of all Pointers in this context for the current
    206      * position of the parent context.
    207      */
    208     public List getContextNodeList() {
    209         int pos = position;
    210         if (pos != 0) {
    211             reset();
    212         }
    213         List list = new ArrayList();
    214         while (nextNode()) {
    215             list.add(getCurrentNodePointer());
    216         }
    217         if (pos != 0) {
    218             setPosition(pos);
    219         }
    220         else {
    221             reset();
    222         }
    223         return list;
    224     }
    225
    226     /***
    227      * Returns the list of all Pointers in this context for all positions
    228      * of the parent contexts.  If there was an ongoing iteration over
    229      * this context, the method should not be called.
    230      */
    231     public NodeSet getNodeSet() {
    232         if (position != 0) {
    233             throw new JXPathException(
    234                 "Simultaneous operations: "
    235                     + "should not request pointer list while "
    236                     + "iterating over an EvalContext");
    237         }
    238         BasicNodeSet set = new BasicNodeSet();
    239         while (nextSet()) {
    240             while (nextNode()) {
    241                 set.add((Pointer)getCurrentNodePointer().clone());
    242             }
    243         }
    244
    245         return set;
    246     }
    247
    248     /***
    249      * Typically returns the NodeSet by calling getNodeSet(),
    250      * but will be overridden for contexts that more naturally produce
    251      * individual values, e.g. VariableContext
    252      */
    253     public Object getValue() {
    254         return getNodeSet();
    255     }
    256
    257     public String toString() {
    258         Pointer ptr = getContextNodePointer();
    259         if (ptr == null) {
    260             return "Empty expression context";
    261         }
    262         else {
    263             return "Expression context [" + getPosition() + "] " + ptr.asPath();
    264         }
    265     }
    266
    267     /***
    268      * Returns the root context of the path, which provides easy
    269      * access to variables and functions.
    270      */
    271     public RootContext getRootContext() {
    272         if (rootContext == null) {
    273             rootContext = parentContext.getRootContext();
    274         }
    275         return rootContext;
    276     }
    277
    278     /***
    279      * Sets current position = 0, which is the pre-iteration state.
    280      */
    281     public void reset() {
    282         position = 0;
    283     }
    284
    285     public int getCurrentPosition() {
    286         return position;
    287     }
    288
    289     /***
    290      * Returns the first encountered Pointer that matches the current
    291      * context's criteria.
    292      */
    293     public Pointer getSingleNodePointer() {
    294         reset();
    295         while (nextSet()) {
    296             if (nextNode()) {
    297                 return getCurrentNodePointer();
    298             }
    299         }
    300         return null;
    301     }
    302
    303     /***
    304      * Returns the current context node. Undefined before the beginning
    305      * of the iteration.
    306      */
    307     public abstract NodePointer getCurrentNodePointer();
    308
    309     /***
    310      * Returns true if there is another sets of objects to interate over.
    311      * Resets the current position and node.
    312      */
    313     public boolean nextSet() {
    314         reset(); // Restart iteration within the set
    315
    316         // Most of the time you have one set per parent node
    317         // First time this method is called, we should look for
    318         // the first parent set that contains at least one node.
    319         if (!startedSetIteration) {
    320             startedSetIteration = true;
    321             while (parentContext.nextSet()) {
    322                 if (parentContext.nextNode()) {
    323                     return true;
    324                 }
    325             }
    326             return false;
    327         }
    328
    329         // In subsequent calls, we see if the parent context
    330         // has any nodes left in the current set
    331         if (parentContext.nextNode()) {
    332             return true;
    333         }
    334
    335         // If not, we look for the next set that contains
    336         // at least one node
    337         while (parentContext.nextSet()) {
    338             if (parentContext.nextNode()) {
    339                 return true;
    340             }
    341         }
    342         return false;
    343     }
    344
    345     /***
    346      * Returns true if there is another object in the current set.
    347      * Switches the current position and node to the next object.
    348      */
    349     public abstract boolean nextNode();
    350
    351     /***
    352      * Moves the current position to the specified index. Used with integer
    353      * predicates to quickly get to the n'th element of the node set.
    354      * Returns false if the position is out of the node set range.
    355      * You can call it with 0 as the position argument to restart the iteration.
    356      */
    357     public boolean setPosition(int position) {
    358         this.position = position;
    359         return true;
    360     }
    361 }