Coverage Report - org.kuali.student.enrollment.lpr.mock.CriteriaMatcherInMemory
 
Classes in this File Line Coverage Branch Coverage Complexity
CriteriaMatcherInMemory
0%
0/133
0%
0/86
4.333
CriteriaMatcherInMemory$1
0%
0/1
N/A
4.333
 
 1  
  /*
 2  
  * Copyright 2011 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.osedu.org/licenses/ECL-2.0
 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.student.enrollment.lpr.mock;
 17  
 
 18  
 import java.util.ArrayList;
 19  
 import java.util.Collection;
 20  
 import java.util.HashMap;
 21  
 import java.util.List;
 22  
 import java.util.Map;
 23  
 import java.util.regex.Pattern;
 24  
 import org.kuali.rice.kns.datadictionary.AttributeDefinition;
 25  
 import org.kuali.rice.kns.datadictionary.DataObjectEntry;
 26  
 import org.kuali.rice.kns.datadictionary.validation.DictionaryObjectAttributeValueReader;
 27  
 import org.kuali.student.common.exceptions.InvalidParameterException;
 28  
 import org.kuali.student.common.exceptions.OperationFailedException;
 29  
 import org.kuali.student.common.infc.ComparisonInfc;
 30  
 import org.kuali.student.common.infc.CriteriaInfc;
 31  
 import org.kuali.student.datadictionary.Student2RiceDictionaryEntryConverter;
 32  
 import org.kuali.student.datadictionary.infc.DictionaryEntryInfc;
 33  
 import org.kuali.student.enrollment.lpr.mock.CriteriaValidatorParser.Operator;
 34  
 
 35  
 /**
 36  
  * A helper class for the Mock implementation to match criteria to values on the object
 37  
  * @author nwright
 38  
  */
 39  
 public class CriteriaMatcherInMemory<T> {
 40  
 
 41  
     public CriteriaMatcherInMemory() {
 42  0
         super();
 43  0
     }
 44  
     private transient DataObjectEntry riceDictionaryEntry;
 45  
     private DictionaryEntryInfc dictionaryEntry;
 46  
         private CriteriaInfc criteria;
 47  
     private transient List<Object> parsedValues;
 48  
     private transient List<CriteriaValidatorParser.Operator> parsedOperators;
 49  
 
 50  
     public void setDictionaryEntry(DictionaryEntryInfc dictionaryEntry) {
 51  0
         this.dictionaryEntry = dictionaryEntry;
 52  0
         if (dictionaryEntry == null) {
 53  0
             this.riceDictionaryEntry = null;
 54  0
             return;
 55  
         }
 56  0
         this.riceDictionaryEntry = new Student2RiceDictionaryEntryConverter().convert(dictionaryEntry);
 57  0
     }
 58  
 
 59  
     public DictionaryEntryInfc getDictionaryEntry() {
 60  0
         return dictionaryEntry;
 61  
     }
 62  
 
 63  
     public CriteriaInfc getCriteria() {
 64  0
         return criteria;
 65  
     }
 66  
 
 67  
     public void setCriteria(CriteriaInfc criteria) {
 68  0
         this.criteria = criteria;
 69  0
     }
 70  
 
 71  
     public List<Operator> getParsedOperators() {
 72  0
         return parsedOperators;
 73  
     }
 74  
 
 75  
     public void setParsedOperators(List<Operator> parsedOperators) {
 76  0
         this.parsedOperators = parsedOperators;
 77  0
     }
 78  
 
 79  
     public List<Object> getParsedValues() {
 80  0
         return parsedValues;
 81  
     }
 82  
 
 83  
     public void setParsedValues(List<Object> parsedValues) {
 84  0
         this.parsedValues = parsedValues;
 85  0
     }
 86  
 
 87  
     
 88  
 
 89  
     private boolean calcIsList(AttributeDefinition ad) {
 90  
         // TODO: Deal with lists
 91  
 //        if (ad.getMaximumNumberOfElements() == null) {
 92  
 //            return false;
 93  
 //        }
 94  
 //        if (ad.getMaximumNumberOfElements() <= 1) {
 95  
 //            return false;
 96  
 //        }
 97  0
         return true;
 98  
 
 99  
     }
 100  
 
 101  
     private AttributeDefinition getAttributeDefinition(String fk)
 102  
             throws InvalidParameterException,
 103  
             OperationFailedException {
 104  0
         return this.riceDictionaryEntry.getAttributeDefinition(fk);
 105  
     }
 106  
 
 107  
     /**
 108  
      * finds all of the supplied objects that match the specified criteria
 109  
      * 
 110  
      * @param all
 111  
      * @return filtered list
 112  
      */
 113  
     public List<T> findMatching(Collection<T> all) {
 114  0
         List<T> selected = new ArrayList<T>();
 115  0
         for (T obj : all) {
 116  0
             if (matches(obj)) {
 117  0
                 selected.add(obj);
 118  0
                 if (this.criteria.getMaxResults() != null) {
 119  0
                     if (selected.size() >= this.criteria.getMaxResults().intValue()) {
 120  0
                         break;
 121  
                     }
 122  
                 }
 123  
             }
 124  
         }
 125  0
         return selected;
 126  
     }
 127  
 
 128  
     /**
 129  
      * Checks if an object matches the criteria
 130  
      * @param infoObject
 131  
      * @return
 132  
      */
 133  
     public boolean matches(Object infoObject) {
 134  
         // logically and them together
 135  0
         int i = 0;
 136  0
         for (ComparisonInfc comparison : this.getCriteria().getComparisons()) {
 137  0
             if (!matches(i, infoObject, comparison)) {
 138  0
                 return false;
 139  
             }
 140  0
             i++;
 141  
         }
 142  0
         return true;
 143  
     }
 144  
 
 145  
     private boolean matches(int i, Object infoObject, ComparisonInfc comparison) {
 146  0
         AttributeDefinition ad = null;
 147  
         try {
 148  0
             ad = this.getAttributeDefinition(comparison.getFieldKey());
 149  0
         } catch (InvalidParameterException ex) {
 150  0
             throw new IllegalArgumentException(ex);
 151  0
         } catch (OperationFailedException ex) {
 152  0
             throw new IllegalArgumentException(ex);
 153  0
         }
 154  0
         Object parsedValue = this.getParsedValues().get(i);
 155  0
         CriteriaValidatorParser.Operator op = this.getParsedOperators().get(i);
 156  0
         Object dataValue = this.extractValue(i, comparison.getFieldKey(), infoObject);
 157  0
         if (comparison.isIgnoreCase()) {
 158  0
             if (dataValue instanceof String) {
 159  0
                 dataValue = ((String) dataValue).toLowerCase();
 160  
             }
 161  
         }
 162  0
         if (matches(op, parsedValue, dataValue)) {
 163  0
             return true;
 164  
         }
 165  0
         return false;
 166  
     }
 167  
 
 168  
     private Object extractValue(int i, String fk, Object infoObject) {
 169  
         // TODO: make sure the key is the same as the name
 170  0
         String dictionaryEntryKey = this.riceDictionaryEntry.getName();
 171  0
         DictionaryObjectAttributeValueReader reader =
 172  
                 new DictionaryObjectAttributeValueReader(infoObject, dictionaryEntryKey, this.riceDictionaryEntry);
 173  0
         Object value = reader.getValue(fk);
 174  0
         return value;
 175  
     }
 176  
 
 177  
     private boolean matches(CriteriaValidatorParser.Operator op, Object dataValue, Object criteriaValue) {
 178  0
         switch (op) {
 179  
             case EQ:
 180  0
                 return matchesEquals(dataValue, criteriaValue);
 181  
             case LT:
 182  0
                 if (matchesEquals(dataValue, criteriaValue)) {
 183  0
                     return false;
 184  
                 }
 185  0
                 return matchesLessThan(dataValue, criteriaValue);
 186  
             case GT:
 187  0
                 if (matchesEquals(dataValue, criteriaValue)) {
 188  0
                     return false;
 189  
                 }
 190  0
                 return !matchesLessThan(dataValue, criteriaValue);
 191  
 
 192  
             case LTE:
 193  0
                 if (matchesEquals(dataValue, criteriaValue)) {
 194  0
                     return true;
 195  
                 }
 196  0
                 return matchesLessThan(dataValue, criteriaValue);
 197  
 
 198  
             case GTE:
 199  0
                 if (matchesEquals(dataValue, criteriaValue)) {
 200  0
                     return true;
 201  
                 }
 202  0
                 return !matchesLessThan(dataValue, criteriaValue);
 203  
             case NEQ:
 204  0
                 return !matchesEquals(dataValue, criteriaValue);
 205  
             case BETWEEN: {
 206  0
                 List<Object> values = (List<Object>) criteriaValue;
 207  0
                 Object fromValue = values.get(0);
 208  0
                 Object thruValue = values.get(1);
 209  0
                 if (matchesEquals(dataValue, fromValue)) {
 210  0
                     return true;
 211  
                 }
 212  0
                 if (matchesEquals(dataValue, thruValue)) {
 213  0
                     return true;
 214  
                 }
 215  0
                 if (matchesLessThan(dataValue, thruValue)) {
 216  0
                     if (!matchesLessThan(dataValue, fromValue)) {
 217  0
                         return true;
 218  
                     }
 219  
                 }
 220  0
                 return false;
 221  
             }
 222  
             case IN: {
 223  0
                 List<Object> values = (List<Object>) criteriaValue;
 224  0
                 for (Object value : values) {
 225  0
                     if (matchesEquals(dataValue, value)) {
 226  0
                         return true;
 227  
                     }
 228  
                 }
 229  0
                 return false;
 230  
             }
 231  
             case LIKE:
 232  0
                 return matchesLike((String) dataValue, (String) criteriaValue);
 233  
             default:
 234  0
                 throw new IllegalArgumentException("unknown/unhandled operation");
 235  
         }
 236  
     }
 237  
 
 238  
     public static boolean matchesEquals(Object dataValue, Object criteriaValue) {
 239  0
         if (dataValue == criteriaValue) {
 240  0
             return true;
 241  
         }
 242  0
         if (dataValue == null && criteriaValue == null) {
 243  0
             return true;
 244  
         }
 245  0
         if (dataValue == null) {
 246  0
             return false;
 247  
         }
 248  0
         return dataValue.equals(criteriaValue);
 249  
     }
 250  
 
 251  
     public static boolean matchesLessThan(Object dataValue, Object criteriaValue) {
 252  0
         if (dataValue == criteriaValue) {
 253  0
             return false;
 254  
         }
 255  0
         if (dataValue == null && criteriaValue == null) {
 256  0
             return false;
 257  
         }
 258  0
         if (dataValue == null) {
 259  0
             return false;
 260  
         }
 261  0
         if (criteriaValue instanceof Comparable) {
 262  0
             Comparable comp1 = (Comparable) dataValue;
 263  0
             Comparable comp2 = (Comparable) criteriaValue;
 264  0
             if (comp1.compareTo(comp2) < 0) {
 265  0
                 return true;
 266  
             }
 267  0
             return false;
 268  
         }
 269  0
         throw new IllegalArgumentException("The values are not comparable " + criteriaValue);
 270  
     }
 271  
     // cache
 272  0
     private transient Map<String, Pattern> patternCache = new HashMap<String, Pattern>();
 273  
 
 274  
     private Pattern getPattern(String expr) {
 275  0
         Pattern p = patternCache.get(expr);
 276  0
         if (p == null) {
 277  0
             p = compilePattern(expr);
 278  0
             patternCache.put(expr, p);
 279  
         }
 280  0
         return p;
 281  
     }
 282  
 
 283  
     /**
 284  
      * this was taken from
 285  
      * http://stackoverflow.com/questions/898405/how-to-implement-a-sql-like-like-operator-in-java
 286  
      */
 287  
     public boolean matchesLikeCachingPattern(final String str, final String expr) {
 288  0
         return matchesLike(str, getPattern(expr));
 289  
     }
 290  
 
 291  
     private static Pattern compilePattern(final String expr) {
 292  0
         String regex = quotemeta(expr);
 293  0
         regex = regex.replace("_", ".").replace("%", ".*?");
 294  0
         Pattern p = Pattern.compile(regex, Pattern.DOTALL);
 295  0
         return p;
 296  
     }
 297  
 
 298  
     /**
 299  
      * This was taken from
 300  
      * 
 301  
      * http://stackoverflow.com/questions/898405/how-to-implement-a-sql-like-like-operator-in-java
 302  
      */
 303  
     public static boolean matchesLike(final String str, final String expr) {
 304  0
         Pattern p = compilePattern(expr);
 305  0
         return matchesLike(str, p);
 306  
     }
 307  
 
 308  
     private static boolean matchesLike(final String str, final Pattern p) {
 309  0
         return p.matcher(str).matches();
 310  
     }
 311  
 
 312  
     private static String quotemeta(String s) {
 313  0
         if (s == null) {
 314  0
             throw new IllegalArgumentException("String cannot be null");
 315  
         }
 316  
 
 317  0
         int len = s.length();
 318  0
         if (len == 0) {
 319  0
             return "";
 320  
         }
 321  
 
 322  0
         StringBuilder sb = new StringBuilder(len * 2);
 323  0
         for (int i = 0; i < len; i++) {
 324  0
             char c = s.charAt(i);
 325  0
             if ("[](){}.*+?$^|#\\".indexOf(c) != -1) {
 326  0
                 sb.append("\\");
 327  
             }
 328  0
             sb.append(c);
 329  
         }
 330  0
         return sb.toString();
 331  
     }
 332  
 }
 333