View Javadoc
1   /*
2    * Copyright 2000-2002 bob mcwhirter & James Strachan.
3    * All rights reserved.
4    *
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions are
8    * met:
9    * 
10   *   * Redistributions of source code must retain the above copyright
11   *     notice, this list of conditions and the following disclaimer.
12   * 
13   *   * Redistributions in binary form must reproduce the above copyright
14   *     notice, this list of conditions and the following disclaimer in the
15   *     documentation and/or other materials provided with the distribution.
16   * 
17   *   * Neither the name of the Jaxen Project nor the names of its
18   *     contributors may be used to endorse or promote products derived 
19   *     from this software without specific prior written permission.
20   * 
21   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24   * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
25   * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32   *
33   * ====================================================================
34   * This software consists of voluntary contributions made by many
35   * individuals on behalf of the Jaxen Project and was originally
36   * created by bob mcwhirter <bob@werken.com> and
37   * James Strachan <jstrachan@apache.org>.  For more information on the
38   * Jaxen Project, please see <https://github.com/jaxen-xpath/jaxen/>.
39   */
40  
41  
42  
43  package org.jaxen.expr;
44  
45  import java.io.Serializable;
46  import java.util.ArrayList;
47  import java.util.Iterator;
48  import java.util.List;
49  import org.jaxen.Context;
50  import org.jaxen.ContextSupport;
51  import org.jaxen.JaxenException;
52  import org.jaxen.function.BooleanFunction;
53  
54  /**
55   * <p>
56   * Represents the collection of predicates that follow the node-test in a
57   * location path. 
58   * </p>
59   * 
60   * <p>
61   * There is no rule that the same predicate may not 
62   * appear twice in an XPath expression, nor does this class enforce any such rule.
63   * This is implemented more as a list than a set. However, adding the same predicate 
64   * twice should have no effect on the final result other than slowing it down.
65   * </p>
66   */
67  public class PredicateSet implements Serializable
68  {
69  
70      private static final long serialVersionUID = -7166491740228977853L;
71      
72      private List<Predicate> predicates;
73  
74      /**
75       * Create a new empty predicate set.
76       */
77      public PredicateSet()
78      {
79          this.predicates = new ArrayList<Predicate>();
80      }
81  
82      /**
83       * Add a predicate to the set.
84       * 
85       * @param predicate the predicate to be inserted
86       */
87      public void addPredicate(Predicate predicate)
88      {
89          this.predicates.add( predicate );
90      }
91  
92      /**
93       * Returns the list containing the predicates.
94       * This list is live, not a copy.
95       * 
96       * @return a live list of predicates
97       */
98      public List getPredicates()
99      {
100         return this.predicates;
101     }
102 
103     /**
104      * Simplify each of the predicates in the list.
105      * @deprecated never really implemented, and will be removed in Jaxen 3
106      */
107     @Deprecated
108     public void simplify()
109     {
110         Iterator<Predicate>  predIter = this.predicates.iterator();
111         Predicate eachPred = null;
112 
113         while ( predIter.hasNext() )
114         {
115             eachPred = predIter.next();
116             eachPred.simplify();
117         }
118     }
119 
120     /**
121      * Returns the XPath string containing each of the predicates.
122      * 
123      * @return the XPath string containing each of the predicates
124      */
125     public String getText()
126     {
127         StringBuilder stringBuilder = new StringBuilder();
128 
129         Iterator<Predicate>  predIter = this.predicates.iterator();
130         Predicate eachPred = null;
131 
132         while ( predIter.hasNext() )
133         {
134             eachPred = predIter.next();
135             stringBuilder.append( eachPred.getText() );
136         }
137 
138         return stringBuilder.toString();
139     }
140 
141     /**
142      * <p>Returns true if any of the supplied nodes satisfy 
143      * all the predicates in the set. Returns false if none of the supplied
144      * nodes matches all the predicates in the set. Returns false if the 
145      * node-set is empty.</p>
146      * 
147      * @param contextNodeSet the nodes to test against these predicates
148      * @param support ????
149      * @return true if any node in the contextNodeSet matches all the predicates
150      * @throws JaxenException
151      */
152     protected boolean evaluateAsBoolean(List contextNodeSet,
153                                       ContextSupport support) throws JaxenException
154     {
155         return anyMatchingNode( contextNodeSet, support );
156     }
157 
158    private boolean anyMatchingNode(List contextNodeSet, ContextSupport support)
159      throws JaxenException {
160         // Easy way out (necessary)
161         if (predicates.isEmpty()) {
162             return false;
163         }
164         Iterator<Predicate> predIter = predicates.iterator();
165 
166         // initial list to filter
167         List nodes2Filter = contextNodeSet;
168         // apply all predicates
169         while(predIter.hasNext()) {
170             Predicate pred = predIter.next();
171             final int nodes2FilterSize = nodes2Filter.size();
172             List<Object> filteredNodes = new ArrayList<Object>(nodes2FilterSize);
173             // Set up a context with a list to hold each node
174             Context predContext = new Context(support);
175             List<Object> tempList = new ArrayList<Object>(1);
176             predContext.setNodeSet(tempList);
177             // loop through the current nodes to filter and add to the
178             // filtered nodes list if the predicate succeeds
179             for (int i = 0; i < nodes2FilterSize; ++i) {
180                 Object contextNode = nodes2Filter.get(i);
181                 tempList.clear();
182                 tempList.add(contextNode);
183                 predContext.setNodeSet(tempList);
184                 predContext.setPosition(i + 1);
185                 predContext.setSize(nodes2FilterSize);
186                 Object predResult = pred.evaluate(predContext);
187                 if (predResult instanceof Number) {
188                     // Here we assume nodes are in forward or reverse order
189                     // as appropriate for axis
190                     // Use IEEE 754 floating-point equality per XPath 1.0 section 2.4
191                     double proximity = ((Number) predResult).doubleValue();
192                     if (proximity == (double)(i + 1)) {
193                         filteredNodes.add(contextNode);
194                     }
195                 }
196                 else {
197                     Boolean includes =
198                         BooleanFunction.evaluate(predResult,
199                                                 predContext.getNavigator());
200                     if (includes.booleanValue()) {
201                         filteredNodes.add(contextNode);
202                     }
203                 }
204             }
205             if (filteredNodes.isEmpty()) {
206                 return false;
207             }
208             nodes2Filter = filteredNodes;
209         }
210         
211         return !nodes2Filter.isEmpty();
212     }
213    
214     
215     
216     
217    /**
218     * <p>Returns all of the supplied nodes that satisfy 
219     * all the predicates in the set. </p>
220     * 
221     * @param contextNodeSet the nodes to test against these predicates
222     * @param support ????
223     * @return all the nodes that match each of the predicates
224     * @throws JaxenException
225     */
226    protected List evaluatePredicates(List contextNodeSet, ContextSupport support)
227             throws JaxenException {
228         // Easy way out (necessary)
229         if (predicates.size() == 0) {
230             return contextNodeSet;
231         }
232         Iterator<Predicate> predIter = predicates.iterator();
233 
234         // initial list to filter
235         List nodes2Filter = contextNodeSet;
236         // apply all predicates
237         while(predIter.hasNext()) {
238             nodes2Filter =
239                 applyPredicate((Predicate)predIter.next(), nodes2Filter, support);
240         }
241         
242         return nodes2Filter;
243     }
244    
245     public List applyPredicate(Predicate predicate, List nodes2Filter, ContextSupport support)
246             throws JaxenException {
247         final int nodes2FilterSize = nodes2Filter.size();
248         List<Object> filteredNodes = new ArrayList<Object>(nodes2FilterSize);
249         // Set up a context with a list to hold each node
250         Context predContext = new Context(support);
251         List<Object> tempList = new ArrayList<Object>(1);
252         predContext.setNodeSet(tempList);
253         // loop through the current nodes to filter and add to the
254         // filtered nodes list if the predicate succeeds
255         for (int i = 0; i < nodes2FilterSize; ++i) {
256             Object contextNode = nodes2Filter.get(i);
257             tempList.clear();
258             tempList.add(contextNode);
259             predContext.setNodeSet(tempList);
260             predContext.setPosition(i + 1);
261             predContext.setSize(nodes2FilterSize);
262             Object predResult = predicate.evaluate(predContext);
263             if (predResult instanceof Number) {
264                 // Here we assume nodes are in forward or reverse order
265                 // as appropriate for axis
266                 // Use IEEE 754 floating-point equality per XPath 1.0 section 2.4
267                 double proximity = ((Number) predResult).doubleValue();
268                 if (proximity == (double)(i + 1)) {
269                     filteredNodes.add(contextNode);
270                 }
271             }
272             else {
273                 Boolean includes =
274                     BooleanFunction.evaluate(predResult,
275                                             predContext.getNavigator());
276                 if (includes.booleanValue()) {
277                     filteredNodes.add(contextNode);
278                 }
279             }
280         }
281         return filteredNodes;
282     }
283     
284 }