View Javadoc

1   /*
2    * Copyright 2004-2009 the original author or authors.
3    *
4    * Licensed under the Apache 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.apache.org/licenses/LICENSE-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  
17  package org.springmodules.validation.valang.predicates;
18  
19  import java.math.BigDecimal;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Date;
23  import java.util.Iterator;
24  import java.util.Set;
25  
26  import org.apache.commons.collections.Predicate;
27  import org.apache.commons.collections.functors.AndPredicate;
28  import org.apache.commons.collections.functors.AnyPredicate;
29  import org.apache.commons.collections.functors.FalsePredicate;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  import org.springframework.util.StringUtils;
33  import org.springmodules.validation.valang.ValangException;
34  import org.springmodules.validation.valang.functions.EnumLiteralFunction;
35  import org.springmodules.validation.valang.functions.Function;
36  import org.springmodules.validation.valang.functions.LiteralFunction;
37  
38  /**
39   * <p>GenericTestPredicate can test if a property value is null or not null.
40   *
41   * @author Steven Devijver
42   * @since 23-04-2005
43   */
44  public class GenericTestPredicate extends AbstractPropertyPredicate {
45  
46      final Logger logger = LoggerFactory.getLogger(GenericTestPredicate.class);
47      
48      /**
49       * <p>Constructor taking two functions and an operator.
50       *
51       * @param leftFunction the left function
52       * @param operator the operator.
53       */
54      public GenericTestPredicate(Function leftFunction, Operator operator, Function rightFunction, int line, int column) {
55          super(leftFunction, operator, rightFunction, line, column);
56      }
57  
58  
59      /**
60       * <p>The evaluate method takes the result of both functions and tests with the operator.
61       *
62       * @param   target      The target bean.
63       * @return  boolean      Whether or not the test passed.
64       */
65      public boolean evaluate(Object target) {
66          Object leftValue = getLeftFunction().getResult(target);
67          Object rightValue = null;
68          boolean dates = false;
69          boolean numbers = false;
70  
71          if (getRightFunction() != null) {
72              rightValue = getRightFunction().getResult(target);
73          }
74  
75          if (leftValue instanceof Number) {
76              leftValue = new BigDecimal(leftValue.toString());
77          }
78          if (rightValue instanceof Number) {
79              rightValue = new BigDecimal(rightValue.toString());
80          }
81  
82          dates = leftValue instanceof Date && rightValue instanceof Date;
83          numbers = leftValue instanceof BigDecimal && rightValue instanceof BigDecimal;
84  
85          if (getOperator() instanceof Operator.NullOperator) {
86              return leftValue == null;
87          } else if (getOperator() instanceof Operator.NotNullOperator) {
88              return leftValue != null;
89          } else if (getOperator() instanceof Operator.EqualsOperator) {
90              if (leftValue instanceof BigDecimal && rightValue instanceof BigDecimal) {
91                  return ((BigDecimal) leftValue).compareTo((BigDecimal) rightValue) == 0;
92              } else if (dates) {
93                  return ((Date) leftValue).getTime() == ((Date) rightValue).getTime();
94              // if one variable is an enum, convert other to a string and try to match
95              } else if (getLeftFunction() instanceof EnumLiteralFunction || getRightFunction() instanceof EnumLiteralFunction) {
96                  Enum<?> enumValue = null;
97                  Enum<?> convertedEnumValue = null;
98                  String value = null;
99                  
100                 try {
101                     if (getRightFunction() instanceof EnumLiteralFunction) {
102                         enumValue = (Enum<?>) leftValue;
103                         value = rightValue.toString().trim();                       
104                     } else if (getLeftFunction() instanceof EnumLiteralFunction) {
105                         enumValue = (Enum<?>) rightValue;
106                         value = leftValue.toString().trim();
107                     }
108 
109                     // tried generating bytecode, but was twice as slow
110                     Class<?> enumClass = enumValue.getClass();
111                     convertedEnumValue = (Enum<?>) enumClass.getField(value).get(null);
112 
113                     return enumValue.equals(convertedEnumValue);
114                 } catch (Throwable e) {
115                     throw new ValangException("Field [" + value + "] isn't an enum value on " + enumValue.getClass().getName() + ".", 
116                                               getLine(), getColumn());
117                 }
118             } else {
119                 return leftValue.equals(rightValue);
120             }
121         } else if (getOperator() instanceof Operator.NotEqualsOperator) {
122             if (leftValue instanceof BigDecimal && rightValue instanceof BigDecimal) {
123                 return ((BigDecimal) leftValue).compareTo((BigDecimal) rightValue) != 0;
124             } else if (dates) {
125                 return ((Date) leftValue).getTime() != ((Date) rightValue).getTime();
126             } else {
127                 return !leftValue.equals(rightValue);
128             }
129         } else if (getOperator() instanceof Operator.LessThanOperator) {
130             if (dates) {
131                 return ((Date) leftValue).getTime() < ((Date) rightValue).getTime();
132             } else if (numbers) {
133                 return ((BigDecimal) leftValue).compareTo((BigDecimal) rightValue) < 0;
134             } else {
135                 throw new ValangException("< operator only supports two date or two number values!", getLine(), getColumn());
136             }
137         } else if (getOperator() instanceof Operator.LessThanOrEqualOperator) {
138             if (dates) {
139                 return ((Date) leftValue).getTime() <= ((Date) rightValue).getTime();
140             } else if (numbers) {
141                 return ((BigDecimal) leftValue).compareTo((BigDecimal) rightValue) <= 0;
142             } else {
143                 throw new ValangException("<= operator only supports two date or two number values!", getLine(), getColumn());
144             }
145         } else if (getOperator() instanceof Operator.MoreThanOperator) {
146             if (dates) {
147                 return ((Date) leftValue).getTime() > ((Date) rightValue).getTime();
148             } else if (numbers) {
149                 return ((BigDecimal) leftValue).compareTo((BigDecimal) rightValue) > 0;
150             } else {
151                 throw new ValangException("> operator only supports two date or two number values!", getLine(), getColumn());
152             }
153         } else if (getOperator() instanceof Operator.MoreThanOrEqualOperator) {
154             if (dates) {
155                 return ((Date) leftValue).getTime() >= ((Date) rightValue).getTime();
156             } else if (numbers) {
157                 return ((BigDecimal) leftValue).compareTo((BigDecimal) rightValue) >= 0;
158             } else {
159                 throw new IllegalArgumentException(">= operator only supports two date or two number values!");
160             }
161         } else if (getOperator() instanceof Operator.InOperator) {
162             Collection<Predicate> predicates = new ArrayList<Predicate>();
163             
164             // FIX ME: make all collections and lists more efficient (also handle arrays)
165             if (rightValue instanceof Set) {
166                 Set<?> lComparisonValues = (Set<?>) rightValue;
167                 
168                 return lComparisonValues.contains(leftValue);
169             } else {
170                 for (Iterator iter = getIterator(rightValue); iter.hasNext();) {
171                     Object o = iter.next();
172                     if (o instanceof Function) {
173                         predicates.add(getPredicate(new LiteralFunction(leftValue), OperatorConstants.EQUALS_OPERATOR, (Function) o, getLine(), getColumn()));
174                     } else {
175                         predicates.add(getPredicate(new LiteralFunction(leftValue), OperatorConstants.EQUALS_OPERATOR, new LiteralFunction(o), getLine(), getColumn()));
176                     }
177                 }
178                 
179                 if (predicates.isEmpty()) {
180                     throw new IllegalStateException("IN expression contains no elements!");
181                 } else if (predicates.size() == 1) {
182                     predicates.add(FalsePredicate.getInstance());
183                 }
184 
185                 return AnyPredicate.getInstance(predicates).evaluate(target);
186             }
187         } else if (getOperator() instanceof Operator.NotInOperator) {
188             Collection<Predicate> predicates = new ArrayList<Predicate>();
189             
190             for (Iterator iter = getIterator(rightValue); iter.hasNext();) {
191                 Object o = iter.next();
192                 if (o instanceof Function) {
193                     predicates.add(getPredicate(new LiteralFunction(leftValue), OperatorConstants.EQUALS_OPERATOR, (Function) o, getLine(), getColumn()));
194                 } else {
195                     predicates.add(getPredicate(new LiteralFunction(leftValue), OperatorConstants.EQUALS_OPERATOR, new LiteralFunction(o), getLine(), getColumn()));
196                 }
197             }
198             if (predicates.isEmpty()) {
199                 throw new IllegalStateException("NOT IN expression contains no elements!");
200             } else if (predicates.size() == 1) {
201                 predicates.add(FalsePredicate.getInstance());
202             }
203             return !AnyPredicate.getInstance(predicates).evaluate(target);
204         } else if (getOperator() instanceof Operator.BetweenOperator) {
205             Object[] array = getArray(rightValue);
206             Predicate predicate1 = getPredicate(new LiteralFunction(leftValue), OperatorConstants.MORE_THAN_OR_EQUAL_OPERATOR, (Function) array[0], getLine(), getColumn());
207             Predicate predicate2 = getPredicate(new LiteralFunction(leftValue), OperatorConstants.LESS_THAN_OR_EQUAL_OPERATOR, (Function) array[1], getLine(), getColumn());
208             return AndPredicate.getInstance(predicate1, predicate2).evaluate(target);
209         } else if (getOperator() instanceof Operator.NotBetweenOperator) {
210             Object[] array = getArray(rightValue);
211             Predicate predicate1 = getPredicate(new LiteralFunction(leftValue), OperatorConstants.MORE_THAN_OR_EQUAL_OPERATOR, (Function) array[0], getLine(), getColumn());
212             Predicate predicate2 = getPredicate(new LiteralFunction(leftValue), OperatorConstants.LESS_THAN_OR_EQUAL_OPERATOR, (Function) array[1], getLine(), getColumn());
213             return !AndPredicate.getInstance(predicate1, predicate2).evaluate(target);
214         } else if (getOperator() instanceof Operator.HasLengthOperator) {
215             return StringUtils.hasLength(leftValue != null ? leftValue.toString() : null);
216         } else if (getOperator() instanceof Operator.HasNoLengthOperator) {
217             return !StringUtils.hasLength(leftValue != null ? leftValue.toString() : null);
218         } else if (getOperator() instanceof Operator.HasTextOperator) {
219             return StringUtils.hasText(leftValue != null ? leftValue.toString() : null);
220         } else if (getOperator() instanceof Operator.HasNoTextOperator) {
221             return !StringUtils.hasText(leftValue != null ? leftValue.toString() : null);
222         } else if (getOperator() instanceof Operator.IsBlankOperator) {
223             return isBlank(leftValue != null ? leftValue.toString() : null);
224         } else if (getOperator() instanceof Operator.IsNotBlankOperator) {
225             return !isBlank(leftValue != null ? leftValue.toString() : null);
226         } else if (getOperator() instanceof Operator.IsWordOperator) {
227             return isWord(leftValue != null ? leftValue.toString() : null);
228         } else if (getOperator() instanceof Operator.IsNotWordOperator) {
229             return !isWord(leftValue != null ? leftValue.toString() : null);
230         } else if (getOperator() instanceof Operator.IsLowerCaseOperator) {
231             return isLowerCase(leftValue != null ? leftValue.toString() : null);
232         } else if (getOperator() instanceof Operator.IsNotLowerCaseOperator) {
233             return !isLowerCase(leftValue != null ? leftValue.toString() : null);
234         } else if (getOperator() instanceof Operator.IsUpperCaseOperator) {
235             return isUpperCase(leftValue != null ? leftValue.toString() : null);
236         } else if (getOperator() instanceof Operator.IsNotUpperCaseOperator) {
237             return !isUpperCase(leftValue != null ? leftValue.toString() : null);
238         }
239 
240         throw new IllegalStateException("Operator class [" + getOperator().getClass().getName() + "] not supported!");
241     }
242 
243     protected Predicate getPredicate(Function leftFunction, Operator operator, Function rightFunction, int line, int column) {
244         return new GenericTestPredicate(leftFunction, operator, rightFunction, line, column);
245     }
246 
247     private boolean isWord(String s) {
248         return s != null && s.length() > 0 &&
249             deleteWhitespace(s).equals(s);
250     }
251 
252     private boolean isLowerCase(String s) {
253         return s.length() > 0 &&
254             s.toLowerCase().equals(s);
255     }
256 
257     private boolean isUpperCase(String s) {
258         return s.length() > 0 &&
259             s.toUpperCase().equals(s);
260     }
261 
262     private boolean isBlank(String s) {
263         return s == null || s.length() == 0;
264     }
265 
266     private String deleteWhitespace(String s) {
267         return StringUtils.deleteAny(s, " \t\r\n\b");
268     }
269 }