001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krms.impl.repository.mock; 017 018import java.lang.reflect.InvocationTargetException; 019import java.math.BigDecimal; 020import java.math.BigInteger; 021import java.util.ArrayList; 022import java.util.Calendar; 023import java.util.Collection; 024import java.util.Date; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.concurrent.atomic.AtomicInteger; 029import java.util.concurrent.atomic.AtomicLong; 030import java.util.regex.Pattern; 031import org.apache.commons.beanutils.NestedNullException; 032import org.apache.commons.beanutils.PropertyUtils; 033import org.joda.time.DateTime; 034import org.kuali.rice.core.api.criteria.AndPredicate; 035import org.kuali.rice.core.api.criteria.EqualPredicate; 036import org.kuali.rice.core.api.criteria.GreaterThanOrEqualPredicate; 037import org.kuali.rice.core.api.criteria.GreaterThanPredicate; 038import org.kuali.rice.core.api.criteria.LessThanOrEqualPredicate; 039import org.kuali.rice.core.api.criteria.LessThanPredicate; 040import org.kuali.rice.core.api.criteria.LikePredicate; 041import org.kuali.rice.core.api.criteria.OrPredicate; 042import org.kuali.rice.core.api.criteria.Predicate; 043import org.kuali.rice.core.api.criteria.QueryByCriteria; 044 045/** 046 * A helper class for the Mock implementation to match criteria to values on the 047 * object 048 * 049 * @author nwright 050 */ 051public class CriteriaMatcherInMemory<T> { 052 053 public CriteriaMatcherInMemory() { 054 super(); 055 } 056 private QueryByCriteria criteria; 057 058 public QueryByCriteria getCriteria() { 059 return criteria; 060 } 061 062 public void setCriteria(QueryByCriteria criteria) { 063 this.criteria = criteria; 064 } 065 066 /** 067 * finds all of the supplied objects that match the specified criteria 068 * 069 * @param all 070 * @return filtered list 071 */ 072 public Collection<T> findMatching(Collection<T> all) { 073 // no criteria means get all 074 if (criteria == null) { 075 return all; 076 } 077 int count = -1; 078 int startAt = 0; 079 if (this.criteria.getStartAtIndex() != null) { 080 startAt = this.criteria.getStartAtIndex(); 081 } 082 int maxResults = Integer.MAX_VALUE; 083 if (this.criteria.getMaxResults() != null) { 084 maxResults = this.criteria.getMaxResults(); 085 } 086 List<T> selected = new ArrayList<T>(); 087 for (T obj : all) { 088 if (matches(obj)) { 089 count++; 090 if (count < startAt) { 091 continue; 092 } 093 selected.add(obj); 094 if (count > maxResults) { 095 break; 096 } 097 } 098 } 099 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}