View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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.kuali.rice.core.api.search;
17  
18  import com.google.common.base.Function;
19  import com.google.common.base.Joiner;
20  import com.google.common.collect.Iterables;
21  import org.apache.commons.lang.StringUtils;
22  import org.kuali.rice.core.api.criteria.Predicate;
23  
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  import java.util.regex.Pattern;
32  
33  import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
34  
35  
36  /**
37   * Canonical utilities for parsing incoming string search expressions
38   */
39  public class SearchExpressionUtils {
40      /**
41       * Binary range operators, must be used to split an expression into values
42       */
43      static final Collection<SearchOperator> BINARY_RANGE_OPERATORS =
44          Collections.unmodifiableCollection(Arrays.asList(SearchOperator.BETWEEN, SearchOperator.BETWEEN_EXCLUSIVE_LOWER, SearchOperator.BETWEEN_EXCLUSIVE_UPPER,
45                                                           SearchOperator.BETWEEN_EXCLUSIVE_UPPER2, SearchOperator.BETWEEN_EXCLUSIVE));
46  
47      /**
48       * Clause operators just join multiple expressions
49       */
50      public static final Collection<SearchOperator> CLAUSE_OPERATORS = Collections.unmodifiableCollection(Arrays.asList(SearchOperator.AND, SearchOperator.OR));
51      private static final Pattern CLAUSE_OPERATORS_PATTERN = generateSplitPattern(CLAUSE_OPERATORS);
52  
53      /**
54       * Operators that can be trimmed from the start of an expression to yield the value.
55       */
56      private final Collection<SearchOperator> PREFIX_UNARY_OPERATORS =
57              Collections.unmodifiableCollection(Arrays.asList(SearchOperator.GREATER_THAN, SearchOperator.LESS_THAN, SearchOperator.GREATER_THAN_EQUAL, SearchOperator.LESS_THAN_EQUAL,
58                                                               SearchOperator.NOT));
59      /**
60       * Operators that can be trimmed from the end of an expression to yield the value.
61       */
62      private final Collection<SearchOperator> POSTFIX_UNARY_OPERATORS =
63              Collections.unmodifiableCollection(Arrays.asList(SearchOperator.LIKE_ONE, SearchOperator.LIKE_MANY, SearchOperator.LIKE_MANY_P));
64  
65  
66      private SearchExpressionUtils() {
67          throw new UnsupportedOperationException("do not call");
68      }
69  
70      public static String parsePrefixUnaryOperatorValue(SearchOperator operator, String expression) {
71          return StringUtils.removeStart(expression.trim(), operator.op()).trim();
72      }
73  
74      public static String parsePostfixUnaryOperatorValue(SearchOperator operator, String expression) {
75          return StringUtils.removeEnd(expression.trim(), operator.op()).trim();
76      }
77  
78      public static String[] parseBinaryOperatorValues(SearchOperator operator, String expression) {
79          return StringUtils.splitByWholeSeparator(expression.trim(), operator.op(), 2);
80      }
81  
82      /**
83       * Returns a regular expression that splits a string on the given operators
84       * @param operators the specified operators
85       * @return a pattern which splits a string on the operators
86       */
87      private static Pattern generateSplitPattern(Collection<SearchOperator> operators) {
88          return Pattern.compile(Joiner.on("|").join(Iterables.transform(operators,
89                  new Function<SearchOperator, String>() {
90                      public String apply(SearchOperator op) { return "(?:\\s*" + Pattern.quote(op.op()) + "\\s*)"; }
91                  })));
92      }
93  
94      /**
95       * Splits the expression by AND and OR operators.  Does not trim empty clauses (these are probably syntax errors)
96       * @param expression the expression to parse
97       * @return string array of clauses, not including AND or OR operators
98       */
99      public static String[] splitOnClauses(String expression) {
100         return CLAUSE_OPERATORS_PATTERN.split(expression);
101     }
102 
103     /**
104      * Strips AND and OR operators from the expression string
105      * @param expression the expression to parse
106      * @return string not including AND or OR operators
107      */
108     public static String stripClauseOperators(String expression) {
109         return CLAUSE_OPERATORS_PATTERN.matcher(expression).replaceAll("");
110     }
111 
112     /**
113      * Splits the expression by the given operators.  Does not trim empty parts.
114      * @param expression the expression to parse
115      * @return string array of clauses, not including specified operators
116      */
117     public static String[] splitOnOperators(String expression, Collection<SearchOperator> operators) {
118         return generateSplitPattern(operators).split(expression);
119     }
120 
121     /**
122      * Splits the expression by the given operators.  Does not trim empty parts.
123      * @param expression the expression to parse
124      * @return string array of clauses, not including specified operators
125      */
126     public static String[] splitOnOperators(String expression, SearchOperator... operators) {
127         return generateSplitPattern(Arrays.asList(operators)).split(expression);
128     }
129 
130     /**
131      * Strips AND and OR operators from the expression string
132      * @param expression the expression to parse
133      * @return string not including AND or OR operators
134      */
135     public static String stripOperators(String expression, Collection<SearchOperator> operators) {
136         return generateSplitPattern(operators).matcher(expression).replaceAll("");
137     }
138 
139     /**
140      * Strips AND and OR operators from the expression string
141      * @param expression the expression to parse
142      * @return string not including AND or OR operators
143      */
144     public static String stripOperators(String expression, SearchOperator... operators) {
145         return generateSplitPattern(Arrays.asList(operators)).matcher(expression).replaceAll("");
146     }
147 
148     public static Range parseRange(String rangeString) {
149         if (StringUtils.isBlank(rangeString)) {
150             throw new IllegalArgumentException("rangeString was null or blank");
151         }
152         Range range = new Range();
153         rangeString = rangeString.trim();
154         if (rangeString.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
155             rangeString = parsePrefixUnaryOperatorValue(SearchOperator.LESS_THAN_EQUAL, rangeString);
156             range.setUpperBoundValue(rangeString);
157             range.setUpperBoundInclusive(true);
158         } else if (rangeString.startsWith(SearchOperator.LESS_THAN.op())) {
159             rangeString = parsePrefixUnaryOperatorValue(SearchOperator.LESS_THAN, rangeString);
160             range.setUpperBoundValue(rangeString);
161             range.setUpperBoundInclusive(false);
162         } else if (rangeString.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
163             rangeString = parsePrefixUnaryOperatorValue(SearchOperator.GREATER_THAN_EQUAL, rangeString);
164             range.setLowerBoundValue(rangeString);
165             range.setLowerBoundInclusive(true);
166         } else if (rangeString.startsWith(SearchOperator.GREATER_THAN.op())) {
167             rangeString = parsePrefixUnaryOperatorValue(SearchOperator.GREATER_THAN, rangeString);
168             range.setLowerBoundValue(rangeString);
169             range.setLowerBoundInclusive(false);
170 
171         // these range separators are subsets of each other, so we need to be sure to check them in the right order
172         } else if (rangeString.contains(SearchOperator.BETWEEN_EXCLUSIVE.op())) {
173             String[] rangeBounds = parseBinaryOperatorValues(SearchOperator.BETWEEN_EXCLUSIVE, rangeString);
174             range.setLowerBoundValue(rangeBounds[0]);
175             range.setLowerBoundInclusive(false);
176             range.setUpperBoundValue(rangeBounds[1]);
177             range.setUpperBoundInclusive(false);
178         } else if (rangeString.contains(SearchOperator.BETWEEN_EXCLUSIVE_UPPER.op())) {
179             String[] rangeBounds = parseBinaryOperatorValues(SearchOperator.BETWEEN_EXCLUSIVE_UPPER, rangeString);
180             range.setLowerBoundValue(rangeBounds[0]);
181             range.setLowerBoundInclusive(true);
182             range.setUpperBoundValue(rangeBounds[1]);
183             range.setUpperBoundInclusive(false);
184         } else if (rangeString.contains(SearchOperator.BETWEEN_EXCLUSIVE_UPPER2.op())) {
185             String[] rangeBounds = parseBinaryOperatorValues(SearchOperator.BETWEEN_EXCLUSIVE_UPPER2, rangeString);
186             range.setLowerBoundValue(rangeBounds[0]);
187             range.setLowerBoundInclusive(true);
188             range.setUpperBoundValue(rangeBounds[1]);
189             range.setUpperBoundInclusive(false);
190         } else if (rangeString.contains(SearchOperator.BETWEEN_EXCLUSIVE_LOWER.op())) {
191             String[] rangeBounds = parseBinaryOperatorValues(SearchOperator.BETWEEN_EXCLUSIVE_LOWER, rangeString);
192             range.setLowerBoundValue(rangeBounds[0]);
193             range.setLowerBoundInclusive(false);
194             range.setUpperBoundValue(rangeBounds[1]);
195             range.setUpperBoundInclusive(true);
196         } else if (rangeString.contains(SearchOperator.BETWEEN.op())) {
197             String[] rangeBounds = parseBinaryOperatorValues(SearchOperator.BETWEEN, rangeString);
198             range.setLowerBoundValue(rangeBounds[0]);
199             range.setLowerBoundInclusive(true);
200             range.setUpperBoundValue(rangeBounds[1]);
201             range.setUpperBoundInclusive(true);
202         } else {
203             // if it has no range specification, return null
204             return null;
205         }
206         return range;
207     }
208 
209     public static Predicate parsePredicates(String expression, String property) {
210         Set<Predicate> ored_predicates = new HashSet<Predicate>();
211         for (String or_clause: splitOnOperators(expression, SearchOperator.OR)) {
212             Set<Predicate> anded_predicates = new HashSet<Predicate>();
213             for (String and_clause: splitOnOperators(or_clause, SearchOperator.AND)) {
214                 anded_predicates.add(parseSimplePredicate(property, and_clause));
215             }
216             ored_predicates.add(and(anded_predicates.toArray(new Predicate[0])));
217         }
218         return or(ored_predicates.toArray(new Predicate[0]));
219     }
220 
221     public static Predicate parseSimplePredicate(String property, String value) {
222         if (value.contains(SearchOperator.NULL.op())) {
223             if (isNot(value)) {
224                 return isNotNull(property);
225             } else {
226                 return isNull(property);
227             }
228         } else if (value.contains(SearchOperator.BETWEEN_EXCLUSIVE_UPPER.op())) {
229             String[] betweenVals = parseBinaryOperatorValues(SearchOperator.BETWEEN_EXCLUSIVE_UPPER, value);
230             return and(greaterThanOrEqual(property, betweenVals[0]), lessThan(property, betweenVals[1]));
231         } else if (value.contains(SearchOperator.BETWEEN.op())) {
232             String[] betweenVals = parseBinaryOperatorValues(SearchOperator.BETWEEN, value);
233              return and(greaterThanOrEqual(property, betweenVals[0]), lessThanOrEqual(property, betweenVals[1]));
234         } else if (value.contains(SearchOperator.GREATER_THAN_EQUAL.op())) {
235             return greaterThanOrEqual(property, stripOperators(value, SearchOperator.GREATER_THAN_EQUAL));
236         } else if (value.contains(SearchOperator.LESS_THAN_EQUAL.op())) {
237             return lessThanOrEqual(property, stripOperators(value, SearchOperator.LESS_THAN_EQUAL));
238         } else if (value.contains(SearchOperator.GREATER_THAN.op())) {
239             return greaterThan(property, stripOperators(value, SearchOperator.GREATER_THAN));
240         } else if (value.contains(SearchOperator.LESS_THAN.op())) {
241             return lessThan(property, stripOperators(value, SearchOperator.LESS_THAN));
242         } else if (value.contains(SearchOperator.NOT.op())) {
243             String[] notValues = splitOnOperators(value, SearchOperator.NOT);
244             List<Predicate> notPreds = new ArrayList<Predicate>(notValues.length);
245             for (String notValue : notValues) {
246                 notPreds.add(notEqual(property, SearchExpressionUtils.stripOperators(notValue, SearchOperator.NOT)));
247             }
248             return and(notPreds.toArray(new Predicate[notPreds.size()]));
249         } else if (value.contains(SearchOperator.LIKE_MANY.op()) || (value.contains(SearchOperator.LIKE_ONE.op()))) {
250             if (isNot(value)) {
251                 return notLike(property, value);
252             } else {
253                 return like(property, value );
254             }
255         } else {
256             if (isNot(value)) {
257                 return notEqual(property, value);
258             } else {
259                 return equal(property, value);
260             }
261         }
262     }
263 
264     private static boolean isNot(String value) {
265         if (value == null) {
266             return false;
267         }
268         return value.contains(SearchOperator.NOT.op());
269     }
270 
271     /**
272     * Splits the valueEntered on locical operators and, or, and between
273     *
274     * @param valueEntered
275     * @return
276     */
277     private static List<String> getSearchableValues(String valueEntered) {
278         List<String> lRet = new ArrayList<String>();
279         getSearchableValueRecursive(valueEntered, lRet);
280         return lRet;
281     }
282 
283     private static void getSearchableValueRecursive(String valueEntered, List lRet) {
284         if(valueEntered == null) {
285             return;
286         }
287 
288         valueEntered = valueEntered.trim();
289 
290         if(lRet == null){
291             throw new NullPointerException("The list passed in is by reference and should never be null.");
292         }
293 
294         if (StringUtils.contains(valueEntered, SearchOperator.BETWEEN.op())) {
295             List<String> l = Arrays.asList(SearchExpressionUtils.parseBinaryOperatorValues(SearchOperator.BETWEEN, valueEntered));
296             for(String value : l){
297                 getSearchableValueRecursive(value,lRet);
298             }
299             return;
300         }
301         if (StringUtils.contains(valueEntered, SearchOperator.OR.op())) {
302             List<String> l = Arrays.asList(StringUtils.split(valueEntered, SearchOperator.OR.op()));
303             for(String value : l){
304                 getSearchableValueRecursive(value,lRet);
305             }
306             return;
307         }
308         if (StringUtils.contains(valueEntered, SearchOperator.AND.op())) {
309             //splitValueList.addAll(Arrays.asList(StringUtils.split(valueEntered, KRADConstants.AND.op())));
310             List<String> l = Arrays.asList(StringUtils.split(valueEntered, SearchOperator.AND.op()));
311             for(String value : l){
312                 getSearchableValueRecursive(value,lRet);
313             }
314             return;
315         }
316 
317         // lRet is pass by ref and should NEVER be null
318         lRet.add(valueEntered);
319    }
320 }