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 }