View Javadoc
1   /**
2    * Copyright 2005-2016 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.krad.datadictionary.validation;
17  
18  import java.math.BigDecimal;
19  import java.text.ParseException;
20  import java.util.Collection;
21  import java.util.Date;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.rice.core.api.data.DataType;
28  import org.kuali.rice.core.api.datetime.DateTimeService;
29  import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
30  import org.kuali.rice.krad.uif.UifConstants;
31  
32  /**
33   * ValidationUtils provides static utility methods for validation processing
34   *
35   * <p>Inherited from Kuali Student and adapted extensively</p>
36   *
37   * @author Kuali Rice Team (rice.collab@kuali.org)
38   */
39  public class ValidationUtils {
40  
41      /**
42       * constructs a path by appending the attribute name to the provided path
43       *
44       * @param attributePath - a string representation of specifically which attribute (at some depth) is being accessed
45       * @param attributeName - the attribute name
46       * @return the path
47       */
48      public static String buildPath(String attributePath, String attributeName) {
49          if (StringUtils.isNotBlank(attributeName)) {
50              if (StringUtils.isNotBlank(attributePath)) {
51                  return new StringBuilder(attributePath).append(".").append(attributeName).toString();
52              }
53  
54              return attributeName;
55          }
56          return attributePath;
57      }
58  
59      /**
60       * Used to get the rightmost index value of an attribute path.
61       *
62       * @param attributePath
63       * @return the right index of value of attribute path, -1 if path has no index
64       */
65      public static int getLastPathIndex(String attributePath) {
66          int index = -1;
67  
68          int leftBracket = attributePath.lastIndexOf("[");
69          int rightBracket = attributePath.lastIndexOf("]");
70  
71          if (leftBracket > 0 && rightBracket > leftBracket) {
72              String indexString = attributePath.substring(leftBracket + 1, rightBracket);
73              try {
74                  index = Integer.valueOf(indexString).intValue();
75              } catch (NumberFormatException e) {
76                  // Will just return -1
77              }
78          }
79  
80          return index;
81      }
82  
83      /**
84       * compares the value provided by the user and the one specified by the {@code WhenConstraint}
85       *
86       * @param fieldValue the value found in the field specified by a {@code CaseConstraint}'s {@code propertyName}
87       * @param whenValue the value specified by a {@code WhenConstraint}
88       * @param dataType the data type of the field which caseConstraint's propertyName refers to
89       * @param operator the relationship to check between the {@code fieldValue} and the {@code whenValue}
90       * @param isCaseSensitive whether string comparison will be carried out in a case sensitive fashion
91       * @param dateTimeService used to convert strings to dates
92       * @return true if the value matches the constraint
93       */
94      public static boolean compareValues(Object fieldValue, Object whenValue, DataType dataType, String operator,
95              boolean isCaseSensitive, DateTimeService dateTimeService) {
96  
97          boolean result = false;
98          Integer compareResult = null;
99  
100         if (UifConstants.CaseConstraintOperators.HAS_VALUE.equalsIgnoreCase(operator)) {
101             if (fieldValue == null) {
102                 return "false".equals(whenValue.toString().toLowerCase());
103             }
104             if (fieldValue instanceof String && ((String) fieldValue).isEmpty()) {
105                 return "false".equals(whenValue.toString().toLowerCase());
106             }
107             if (fieldValue instanceof Collection && ((Collection<?>) fieldValue).isEmpty()) {
108                 return "false".equals(whenValue.toString().toLowerCase());
109             }
110             return "true".equals(whenValue.toString().toLowerCase());
111         }
112         // Convert objects into appropriate data types
113         if (null != dataType) {
114             if (DataType.STRING.equals(dataType)) {
115                 String v1 = getString(fieldValue);
116                 String v2 = getString(whenValue);
117 
118                 if (!isCaseSensitive) {
119                     v1 = v1.toUpperCase();
120                     v2 = v2.toUpperCase();
121                 }
122 
123                 compareResult = v1.compareTo(v2);
124             } else if (DataType.INTEGER.equals(dataType)) {
125                 Integer v1 = getInteger(fieldValue);
126                 Integer v2 = getInteger(whenValue);
127                 compareResult = v1.compareTo(v2);
128             } else if (DataType.LONG.equals(dataType)) {
129                 Long v1 = getLong(fieldValue);
130                 Long v2 = getLong(whenValue);
131                 compareResult = v1.compareTo(v2);
132             } else if (DataType.DOUBLE.equals(dataType)) {
133                 Double v1 = getDouble(fieldValue);
134                 Double v2 = getDouble(whenValue);
135                 compareResult = v1.compareTo(v2);
136             } else if (DataType.FLOAT.equals(dataType)) {
137                 Float v1 = getFloat(fieldValue);
138                 Float v2 = getFloat(whenValue);
139                 compareResult = v1.compareTo(v2);
140             } else if (DataType.BOOLEAN.equals(dataType)) {
141                 Boolean v1 = getBoolean(fieldValue);
142                 Boolean v2 = getBoolean(whenValue);
143                 compareResult = v1.compareTo(v2);
144             } else if (DataType.DATE.equals(dataType)) {
145                 Date v1 = getDate(fieldValue, dateTimeService);
146                 Date v2 = getDate(whenValue, dateTimeService);
147                 compareResult = v1.compareTo(v2);
148             }
149         }
150 
151         if (null != compareResult) {
152             if ((UifConstants.CaseConstraintOperators.EQUALS.equalsIgnoreCase(operator) || UifConstants
153                     .CaseConstraintOperators.GREATER_THAN_EQUAL.equalsIgnoreCase(operator) || UifConstants
154                     .CaseConstraintOperators.LESS_THAN_EQUAL.equalsIgnoreCase(operator)) && 0 == compareResult) {
155                 result = true;
156             }
157 
158             if ((UifConstants.CaseConstraintOperators.NOT_EQUAL.equalsIgnoreCase(operator) || UifConstants
159                     .CaseConstraintOperators.NOT_EQUALS.equalsIgnoreCase(operator) || UifConstants
160                     .CaseConstraintOperators.GREATER_THAN.equalsIgnoreCase(operator)) && compareResult >= 1) {
161                 result = true;
162             }
163 
164             if ((UifConstants.CaseConstraintOperators.NOT_EQUAL.equalsIgnoreCase(operator) || UifConstants
165                     .CaseConstraintOperators.NOT_EQUALS.equalsIgnoreCase(operator) || UifConstants
166                     .CaseConstraintOperators.LESS_THAN.equalsIgnoreCase(operator)) && compareResult <= -1) {
167                 result = true;
168             }
169         }
170 
171         return result;
172     }
173 
174     /**
175      * converts the provided object into an integer
176      *
177      * @param o - the object to convert
178      * @return the integer value
179      */
180     public static Integer getInteger(Object o) {
181         Integer result = null;
182         if (o instanceof Integer) {
183             return (Integer) o;
184         }
185         if (o == null) {
186             return null;
187         }
188         if (o instanceof Number) {
189             return ((Number) o).intValue();
190         }
191         String s = o.toString();
192         if (s != null && s.trim().length() > 0) {
193             result = Integer.valueOf(s.trim());
194         }
195         return result;
196     }
197 
198     /**
199      * converts the provided object into a long
200      *
201      * @param o - the object to convert
202      * @return the long value
203      */
204     public static Long getLong(Object o) {
205         Long result = null;
206         if (o instanceof Long) {
207             return (Long) o;
208         }
209         if (o == null) {
210             return null;
211         }
212         if (o instanceof Number) {
213             return ((Number) o).longValue();
214         }
215         String s = o.toString();
216         if (s != null && s.trim().length() > 0) {
217             result = Long.valueOf(s.trim());
218         }
219         return result;
220     }
221 
222     /**
223      * converts the provided object into an float
224      *
225      * @param o - the object to convert
226      * @return the float value
227      */
228     public static Float getFloat(Object o) {
229         Float result = null;
230         if (o instanceof Float) {
231             return (Float) o;
232         }
233         if (o == null) {
234             return null;
235         }
236         if (o instanceof Number) {
237             return ((Number) o).floatValue();
238         }
239         String s = o.toString();
240         if (s != null && s.trim().length() > 0) {
241             result = Float.valueOf(s.trim());
242         }
243         return result;
244     }
245 
246     /**
247      * converts the provided object into a double
248      *
249      * @param o - the object to convert
250      * @return the double value
251      */
252     public static Double getDouble(Object o) {
253         Double result = null;
254         if (o instanceof BigDecimal) {
255             return ((BigDecimal) o).doubleValue();
256         }
257         if (o instanceof Double) {
258             return (Double) o;
259         }
260         if (o == null) {
261             return null;
262         }
263         if (o instanceof Number) {
264             return ((Number) o).doubleValue();
265         }
266         String s = o.toString();
267         if (s != null && s.trim().length() > 0) {
268             result = Double.valueOf(s.trim());
269         }
270         return result;
271     }
272 
273     /**
274      * determines whether the provided object is a date and tries to converts non-date values
275      *
276      * @param object - the object to convert/cast into a date
277      * @param dateTimeService - used to convert strings to dates
278      * @return a date object
279      * @throws IllegalArgumentException
280      */
281     public static Date getDate(Object object, DateTimeService dateTimeService) throws IllegalArgumentException {
282         Date result = null;
283         if (object instanceof Date) {
284             return (Date) object;
285         }
286         if (object == null) {
287             return null;
288         }
289         String s = object.toString();
290         if (s != null && s.trim().length() > 0) {
291             try {
292                 result = dateTimeService.convertToDate(s.trim());
293             } catch (ParseException e) {
294                 throw new IllegalArgumentException(e);
295             }
296         }
297         return result;
298     }
299 
300     /**
301      * converts the provided object into a string
302      *
303      * @param o - the object to convert
304      * @return the string value
305      */
306     public static String getString(Object o) {
307         if (o instanceof String) {
308             return (String) o;
309         }
310         if (o == null) {
311             return null;
312         }
313         return o.toString();
314     }
315 
316     /**
317      * converts the provided object into a boolean
318      *
319      * @param o - the object to convert
320      * @return the boolean value
321      */
322     public static Boolean getBoolean(Object o) {
323         Boolean result = null;
324         if (o instanceof Boolean) {
325             return (Boolean) o;
326         }
327         if (o == null) {
328             return null;
329         }
330         String s = o.toString();
331         if (s != null && s.trim().length() > 0) {
332             result = Boolean.parseBoolean(s.trim());
333         }
334         return result;
335     }
336 
337     /**
338      * checks whether the string contains non-whitespace characters
339      *
340      * @param string
341      * @return true if the string contains at least one none-whitespace character, false otherwise
342      */
343     public static boolean hasText(String string) {
344 
345         if (string == null || string.length() < 1) {
346             return false;
347         }
348         int stringLength = string.length();
349 
350         for (int i = 0; i < stringLength; i++) {
351             char currentChar = string.charAt(i);
352             if (' ' != currentChar || '\t' != currentChar || '\n' != currentChar) {
353                 return true;
354             }
355         }
356 
357         return false;
358     }
359 
360     /**
361      * Checks whether the provided object is null, or if a String, List, Set or Map is empty.
362      *
363      * @param value - the object to check
364      * @return true if the object is null or if a String, List, Set, or Map is empty, false otherwise
365      */
366     public static boolean isNullOrEmpty(Object value) {
367         boolean nullOrEmpty = false;
368         if (value == null) {
369             nullOrEmpty = true;
370         }
371         else if (value instanceof String) {
372             nullOrEmpty = StringUtils.isBlank(((String) value).trim());
373         }
374         else if (value instanceof List) {
375             nullOrEmpty = ((List)value).isEmpty();
376         }
377         else if (value instanceof Set) {
378             nullOrEmpty = ((Set)value).isEmpty();
379         }
380         else if (value instanceof Map) {
381             nullOrEmpty = ((Map)value).isEmpty();
382         }
383 
384         return nullOrEmpty;
385     }
386 
387     /**
388      * defines possible result values of a comparison operation
389      */
390     public static enum Result {
391         VALID, INVALID, UNDEFINED
392     }
393 
394     ;
395 
396     /**
397      * attempts to convert the provided value to the given dataType
398      *
399      * @param value - the object to convert
400      * @param dataType - the data type to convert into
401      * @param dateTimeService - used to convert strings to dates
402      * @return the converted value if null or successful, otherwise throws an exception
403      * @throws AttributeValidationException
404      */
405     public static Object convertToDataType(Object value, DataType dataType,
406             DateTimeService dateTimeService) throws AttributeValidationException {
407         Object returnValue = value;
408 
409         if (null == value) {
410             return null;
411         }
412 
413         switch (dataType) {
414             case BOOLEAN:
415                 if (!(value instanceof Boolean)) {
416                     returnValue = Boolean.valueOf(value.toString());
417 
418                     // Since the Boolean.valueOf is exceptionally loose - it basically takes any string and makes it false
419                     if (!value.toString().equalsIgnoreCase("TRUE") && !value.toString().equalsIgnoreCase("FALSE")) {
420                         throw new AttributeValidationException("Value " + value.toString() + " is not a boolean!");
421                     }
422                 }
423                 break;
424             case INTEGER:
425                 if (!(value instanceof Number)) {
426                     returnValue = Integer.valueOf(value.toString());
427                 }
428                 break;
429             case LONG:
430                 if (!(value instanceof Number)) {
431                     returnValue = Long.valueOf(value.toString());
432                 }
433                 break;
434             case DOUBLE:
435                 if (!(value instanceof Number)) {
436                     returnValue = Double.valueOf(value.toString());
437                 }
438                 if (((Double) returnValue).isNaN()) {
439                     throw new AttributeValidationException("Infinite Double values are not valid!");
440                 }
441                 if (((Double) returnValue).isInfinite()) {
442                     throw new AttributeValidationException("Infinite Double values are not valid!");
443                 }
444                 break;
445             case FLOAT:
446                 if (!(value instanceof Number)) {
447                     returnValue = Float.valueOf(value.toString());
448                 }
449                 if (((Float) returnValue).isNaN()) {
450                     throw new AttributeValidationException("NaN Float values are not valid!");
451                 }
452                 if (((Float) returnValue).isInfinite()) {
453                     throw new AttributeValidationException("Infinite Float values are not valid!");
454                 }
455                 break;
456             case TRUNCATED_DATE:
457             case DATE:
458                 if (!(value instanceof Date)) {
459                     try {
460                         returnValue = dateTimeService.convertToDate(value.toString());
461                     } catch (ParseException pe) {
462                         throw new AttributeValidationException("Value " + value.toString() + " is not a date!");
463                     }
464                 }
465                 break;
466             case STRING:
467         }
468 
469         return returnValue;
470     }
471 
472     /**
473      * checks whether the provided value is greater than the limit given
474      *
475      * @param value - the object to check
476      * @param limit - the limit to use
477      * @param <T>
478      * @return one of the values in {@link  Result}
479      */
480     public static <T> Result isGreaterThan(T value, Comparable<T> limit) {
481         return limit == null ? Result.UNDEFINED : (limit.compareTo(value) < 0 ? Result.VALID : Result.INVALID);
482     }
483 
484     /**
485      * checks whether the provided value is greater than or equal to the limit given
486      *
487      * @param value - the object to check
488      * @param limit - the limit to use
489      * @param <T>
490      * @return one of the values in {@link  Result}
491      */
492     public static <T> Result isGreaterThanOrEqual(T value, Comparable<T> limit) {
493         return limit == null ? Result.UNDEFINED : (limit.compareTo(value) <= 0 ? Result.VALID : Result.INVALID);
494     }
495 
496     /**
497      * checks whether the provided value is less than the limit given
498      *
499      * @param value - the object to check
500      * @param limit - the limit to use
501      * @param <T>
502      * @return one of the values in {@link  Result}
503      */
504     public static <T> Result isLessThan(T value, Comparable<T> limit) {
505         return limit == null ? Result.UNDEFINED : (limit.compareTo(value) > 0 ? Result.VALID : Result.INVALID);
506     }
507 
508     /**
509      * checks whether the provided value is greater than the limit given
510      *
511      * @param value - the object to check
512      * @param limit - the limit to use
513      * @param <T>
514      * @return one of the values in {@link  Result}
515      */
516     public static <T> Result isLessThanOrEqual(T value, Comparable<T> limit) {
517         return limit == null ? Result.UNDEFINED : (limit.compareTo(value) >= 0 ? Result.VALID : Result.INVALID);
518     }
519 
520     /**
521      * converts a path into an array of its path components
522      *
523      * @param fieldPath - a string representation of specifically which attribute (at some depth) is being accessed
524      * @return the array of path components
525      */
526     public static String[] getPathTokens(String fieldPath) {
527         return (fieldPath != null && fieldPath.contains(".") ? fieldPath.split("\\.") : new String[]{fieldPath});
528     }
529 
530 }
531