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.krms.impl.repository.mock;
17  
18  import java.lang.reflect.InvocationTargetException;
19  import java.math.BigDecimal;
20  import java.math.BigInteger;
21  import java.util.ArrayList;
22  import java.util.Calendar;
23  import java.util.Collection;
24  import java.util.Date;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.concurrent.atomic.AtomicInteger;
29  import java.util.concurrent.atomic.AtomicLong;
30  import java.util.regex.Pattern;
31  import org.apache.commons.beanutils.NestedNullException;
32  import org.apache.commons.beanutils.PropertyUtils;
33  import org.joda.time.DateTime;
34  import org.kuali.rice.core.api.criteria.AndPredicate;
35  import org.kuali.rice.core.api.criteria.EqualPredicate;
36  import org.kuali.rice.core.api.criteria.GreaterThanOrEqualPredicate;
37  import org.kuali.rice.core.api.criteria.GreaterThanPredicate;
38  import org.kuali.rice.core.api.criteria.LessThanOrEqualPredicate;
39  import org.kuali.rice.core.api.criteria.LessThanPredicate;
40  import org.kuali.rice.core.api.criteria.LikePredicate;
41  import org.kuali.rice.core.api.criteria.OrPredicate;
42  import org.kuali.rice.core.api.criteria.Predicate;
43  import org.kuali.rice.core.api.criteria.QueryByCriteria;
44  
45  /**
46   * A helper class for the Mock implementation to match criteria to values on the
47   * object
48   *
49   * @author nwright
50   */
51  public class CriteriaMatcherInMemory<T> {
52  
53      public CriteriaMatcherInMemory() {
54          super();
55      }
56      private QueryByCriteria criteria;
57  
58      public QueryByCriteria getCriteria() {
59          return criteria;
60      }
61  
62      public void setCriteria(QueryByCriteria criteria) {
63          this.criteria = criteria;
64      }
65  
66      /**
67       * finds all of the supplied objects that match the specified criteria
68       *
69       * @param all
70       * @return filtered list
71       */
72      public Collection<T> findMatching(Collection<T> all) {
73          // no criteria means get all
74          if (criteria == null) {
75              return all;
76          }
77          int count = -1;
78          int startAt = 0;
79          if (this.criteria.getStartAtIndex() != null) {
80              startAt = this.criteria.getStartAtIndex();
81          }
82          int maxResults = Integer.MAX_VALUE;
83          if (this.criteria.getMaxResults() != null) {
84              maxResults = this.criteria.getMaxResults();
85          }
86          List<T> selected = new ArrayList<T>();
87          for (T obj : all) {
88              if (matches(obj)) {
89                  count++;
90                  if (count < startAt) {
91                      continue;
92                  }
93                  selected.add(obj);
94                  if (count > maxResults) {
95                      break;
96                  }
97              }
98          }
99          return selected;
100     }
101 
102     /**
103      * Checks if an object matches the criteria
104      *
105      * @param infoObject
106      * @return
107      */
108     public boolean matches(T infoObject) {
109         return matches(infoObject, this.criteria.getPredicate());
110     }
111 
112     /**
113      * protected for testing
114      */
115     protected boolean matches(T infoObject, Predicate predicate) {
116         // no predicate matches everyting
117         if (predicate == null) {
118             return true;
119         }
120         if (predicate instanceof OrPredicate) {
121             return matchesOr(infoObject, (OrPredicate) predicate);
122         }
123         if (predicate instanceof AndPredicate) {
124             return matchesAnd(infoObject, (AndPredicate) predicate);
125         }
126         if (predicate instanceof EqualPredicate) {
127             return matchesEqual(infoObject, (EqualPredicate) predicate);
128         }
129         if (predicate instanceof LessThanPredicate) {
130             return matchesLessThan(infoObject, (LessThanPredicate) predicate);
131         }
132         if (predicate instanceof LessThanOrEqualPredicate) {
133             return matchesLessThanOrEqual(infoObject, (LessThanOrEqualPredicate) predicate);
134         }
135         if (predicate instanceof GreaterThanPredicate) {
136             return matchesGreaterThan(infoObject, (GreaterThanPredicate) predicate);
137         }
138         if (predicate instanceof GreaterThanOrEqualPredicate) {
139             return matchesGreaterThanOrEqual(infoObject, (GreaterThanOrEqualPredicate) predicate);
140         }
141         if (predicate instanceof LikePredicate) {
142             return matchesLike(infoObject, (LikePredicate) predicate);
143         }
144         throw new UnsupportedOperationException("predicate type not supported yet in in-memory mathcer" + predicate.getClass().getName());
145     }
146 
147     private boolean matchesOr(T infoObject, OrPredicate predicate) {
148         for (Predicate subPred : predicate.getPredicates()) {
149             if (matches(infoObject, subPred)) {
150                 return true;
151             }
152         }
153         return false;
154     }
155 
156     private boolean matchesAnd(T infoObject, AndPredicate predicate) {
157         for (Predicate subPred : predicate.getPredicates()) {
158             if (!matches(infoObject, subPred)) {
159                 return false;
160             }
161         }
162         return true;
163     }
164 
165     private boolean matchesEqual(T infoObject, EqualPredicate predicate) {
166         Object dataValue = extractValue(predicate.getPropertyPath(), infoObject);
167         return matchesEqual(dataValue, predicate.getValue().getValue());
168     }
169 
170     private boolean matchesLessThan(T infoObject, LessThanPredicate predicate) {
171         Object dataValue = extractValue(predicate.getPropertyPath(), infoObject);
172         return matchesLessThan(dataValue, predicate.getValue().getValue());
173     }
174 
175     private boolean matchesLessThanOrEqual(T infoObject, LessThanOrEqualPredicate predicate) {
176         Object dataValue = extractValue(predicate.getPropertyPath(), infoObject);
177         if (matchesLessThan(dataValue, predicate.getValue().getValue())) {
178             return true;
179         }
180         return matchesEqual(dataValue, predicate.getValue().getValue());
181     }
182 
183     private boolean matchesGreaterThan(T infoObject, GreaterThanPredicate predicate) {
184         Object dataValue = extractValue(predicate.getPropertyPath(), infoObject);
185         return matchesGreaterThan(dataValue, predicate.getValue().getValue());
186     }
187 
188     private boolean matchesGreaterThanOrEqual(T infoObject, GreaterThanOrEqualPredicate predicate) {
189         Object dataValue = extractValue(predicate.getPropertyPath(), infoObject);
190         if (matchesGreaterThan(dataValue, predicate.getValue().getValue())) {
191             return true;
192         }
193         return matchesEqual(dataValue, predicate.getValue().getValue());
194     }
195 
196     private boolean matchesLike(T infoObject, LikePredicate predicate) {
197         Object dataValue = extractValue(predicate.getPropertyPath(), infoObject);
198         return matchesLike(dataValue, predicate.getValue().getValue());
199     }
200 
201     protected static Object extractValue(String fieldPath, Object infoObject) {
202 
203         try {
204             if (infoObject == null) {
205                 return null;
206             }
207             Object value = PropertyUtils.getNestedProperty(infoObject, fieldPath);
208             // translate boolean to string so we can compare
209             // Have to do this because RICE's predicate does not support boolean 
210             // because it is database oriented and most DB do not support booleans natively.
211             if (value instanceof Boolean) {
212                 return value.toString();
213             }
214             // See Rice's CriteriaSupportUtils.determineCriteriaValue where data normalized 
215             // translate date to joda DateTime because that is what RICE PredicateFactory does 
216             // similar to rest of the types 
217             if (value instanceof Date) {
218                 return new DateTime ((Date) value);
219             }
220             if (value instanceof Calendar) {
221                 return new DateTime ((Calendar) value);
222             }
223             if (value instanceof Short) {
224                 return BigInteger.valueOf(((Short) value).longValue());
225             }
226             if (value instanceof AtomicLong) {
227                 return BigInteger.valueOf(((AtomicLong) value).longValue());
228             }
229             if (value instanceof AtomicInteger) {
230                 return BigInteger.valueOf(((AtomicInteger) value).longValue());
231             }
232             if (value instanceof Integer) {
233                 return BigInteger.valueOf(((Integer)value).longValue());
234             }
235             if (value instanceof Long) {
236                 return BigInteger.valueOf(((Long)value).longValue());
237             }
238             if (value instanceof Float) {
239                 return BigDecimal.valueOf(((Float)value).doubleValue());
240             }
241             if (value instanceof Double) {
242                 return BigDecimal.valueOf(((Double)value).doubleValue());
243             }
244             return value;
245         } catch (NestedNullException ex) {
246             return null;
247         }  catch (IllegalAccessException ex) {
248             throw new IllegalArgumentException(fieldPath, ex);
249         } catch (InvocationTargetException ex) {
250             throw new IllegalArgumentException(fieldPath, ex);
251         } catch (NoSuchMethodException ex) {
252             throw new IllegalArgumentException(fieldPath, ex);
253         }
254 //        }
255 //        return value;
256     }
257 
258     public static boolean matchesEqual(Object dataValue, Object criteriaValue) {
259         if (dataValue == criteriaValue) {
260             return true;
261         }
262         if (dataValue == null && criteriaValue == null) {
263             return true;
264         }
265         if (dataValue == null) {
266             return false;
267         }
268         return dataValue.equals(criteriaValue);
269     }
270 
271     public static boolean matchesLessThan(Object dataValue, Object criteriaValue) {
272         if (dataValue == criteriaValue) {
273             return false;
274         }
275         if (dataValue == null && criteriaValue == null) {
276             return false;
277         }
278         if (dataValue == null) {
279             return false;
280         }
281         if (criteriaValue instanceof Comparable) {
282             Comparable comp1 = (Comparable) dataValue;
283             Comparable comp2 = (Comparable) criteriaValue;
284             if (comp1.compareTo(comp2) < 0) {
285                 return true;
286             }
287             return false;
288         }
289         throw new IllegalArgumentException("The values are not comparable " + criteriaValue);
290     }
291 
292     public static boolean matchesGreaterThan(Object dataValue, Object criteriaValue) {
293         if (dataValue == criteriaValue) {
294             return false;
295         }
296         if (dataValue == null && criteriaValue == null) {
297             return false;
298         }
299         if (dataValue == null) {
300             return false;
301         }
302         if (criteriaValue instanceof Comparable) {
303             Comparable comp1 = (Comparable) dataValue;
304             Comparable comp2 = (Comparable) criteriaValue;
305             if (comp1.compareTo(comp2) > 0) {
306                 return true;
307             }
308             return false;
309         }
310         throw new IllegalArgumentException("The values are not comparable " + criteriaValue);
311     }
312     // cache
313     private transient Map<String, Pattern> patternCache = new HashMap<String, Pattern>();
314 
315     private Pattern getPattern(String expr) {
316         Pattern p = patternCache.get(expr);
317         if (p == null) {
318             p = compilePattern(expr);
319             patternCache.put(expr, p);
320         }
321         return p;
322     }
323 
324     public boolean matchesLike(Object dataValue, Object criteriaValue) {
325         if (dataValue == criteriaValue) {
326             return false;
327         }
328         if (dataValue == null && criteriaValue == null) {
329             return false;
330         }
331         if (dataValue == null) {
332             return false;
333         }
334         return matchesLikeCachingPattern(dataValue.toString(), criteriaValue.toString());
335     }
336 
337     /**
338      * this was taken from
339      * http://stackoverflow.com/questions/898405/how-to-implement-a-sql-like-like-operator-in-java
340      */
341     public boolean matchesLikeCachingPattern(final String str, final String expr) {
342         return matchesLike(str, getPattern(expr));
343     }
344 
345     private static Pattern compilePattern(final String expr) {
346         String regex = quotemeta(expr);
347         regex = regex.replace("_", ".").replace("%", ".*?");
348         Pattern p = Pattern.compile(regex, Pattern.DOTALL);
349         return p;
350     }
351 
352     /**
353      * This was taken from
354      *
355      * http://stackoverflow.com/questions/898405/how-to-implement-a-sql-like-like-operator-in-java
356      */
357     public static boolean matchesLike(final String str, final String expr) {
358         Pattern p = compilePattern(expr);
359         return matchesLike(str, p);
360     }
361 
362     private static boolean matchesLike(final String str, final Pattern p) {
363         return p.matcher(str).matches();
364     }
365 
366     private static String quotemeta(String s) {
367         if (s == null) {
368             throw new IllegalArgumentException("String cannot be null");
369         }
370 
371         int len = s.length();
372         if (len == 0) {
373             return "";
374         }
375 
376         StringBuilder sb = new StringBuilder(len * 2);
377         for (int i = 0; i < len; i++) {
378             char c = s.charAt(i);
379             if ("[](){}.*+?$^|#\\".indexOf(c) != -1) {
380                 sb.append("\\");
381             }
382             sb.append(c);
383         }
384         return sb.toString();
385     }
386 }