001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.krad.datadictionary.validation;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.core.api.datetime.DateTimeService;
020    import org.kuali.rice.core.api.uif.DataType;
021    import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
022    import org.kuali.rice.krad.uif.UifConstants;
023    
024    import java.math.BigDecimal;
025    import java.text.ParseException;
026    import java.util.Collection;
027    import java.util.Date;
028    
029    /**
030     * ValidationUtils provides static utility methods for validation processing
031     *
032     * <p>Inherited from Kuali Student and adapted extensively</p>
033     *
034     * @author Kuali Rice Team (rice.collab@kuali.org)
035     */
036    public class ValidationUtils {
037    
038        /**
039         * constructs a path by appending the attribute name to the provided path
040         *
041         * @param attributePath - a string representation of specifically which attribute (at some depth) is being accessed
042         * @param attributeName - the attribute name
043         * @return the path
044         */
045        public static String buildPath(String attributePath, String attributeName) {
046            if (StringUtils.isNotBlank(attributeName)) {
047                if (StringUtils.isNotBlank(attributePath)) {
048                    return new StringBuilder(attributePath).append(".").append(attributeName).toString();
049                }
050    
051                return attributeName;
052            }
053            return attributePath;
054        }
055    
056        /**
057         * Used to get the rightmost index value of an attribute path.
058         *
059         * @param attributePath
060         * @return the right index of value of attribute path, -1 if path has no index
061         */
062        public static int getLastPathIndex(String attributePath) {
063            int index = -1;
064    
065            int leftBracket = attributePath.lastIndexOf("[");
066            int rightBracket = attributePath.lastIndexOf("]");
067    
068            if (leftBracket > 0 && rightBracket > leftBracket) {
069                String indexString = attributePath.substring(leftBracket + 1, rightBracket);
070                try {
071                    index = Integer.valueOf(indexString).intValue();
072                } catch (NumberFormatException e) {
073                    // Will just return -1
074                }
075            }
076    
077            return index;
078        }
079    
080        /**
081         * compares the value provided by the user and the one specified by the {@code WhenConstraint}
082         *
083         * @param fieldValue - the value found in the field specified by a {@code CaseConstraint}'s {@code propertyName}
084         * @param whenValue - the value specified by a {@code WhenConstraint}
085         * @param dataType - the data type of the field which caseConstraint's propertyName refers to
086         * @param operator - the relationship to check between the {@code fieldValue} and the {@code whenValue}
087         * @param isCaseSensitive - whether string comparison will be carried out in a case sensitive fashion
088         * @param dateTimeService - used to convert strings to dates
089         * @return
090         */
091        public static boolean compareValues(Object fieldValue, Object whenValue, DataType dataType, String operator,
092                boolean isCaseSensitive, DateTimeService dateTimeService) {
093    
094            boolean result = false;
095            Integer compareResult = null;
096    
097            if (UifConstants.CaseConstraintOperators.HAS_VALUE.equalsIgnoreCase(operator)) {
098                if (fieldValue == null) {
099                    return "false".equals(whenValue.toString().toLowerCase());
100                }
101                if (fieldValue instanceof String && ((String) fieldValue).isEmpty()) {
102                    return "false".equals(whenValue.toString().toLowerCase());
103                }
104                if (fieldValue instanceof Collection && ((Collection<?>) fieldValue).isEmpty()) {
105                    return "false".equals(whenValue.toString().toLowerCase());
106                }
107                return "true".equals(whenValue.toString().toLowerCase());
108            }
109            // Convert objects into appropriate data types
110            if (null != dataType) {
111                if (DataType.STRING.equals(dataType)) {
112                    String v1 = getString(fieldValue);
113                    String v2 = getString(whenValue);
114    
115                    if (!isCaseSensitive) {
116                        v1 = v1.toUpperCase();
117                        v2 = v2.toUpperCase();
118                    }
119    
120                    compareResult = v1.compareTo(v2);
121                } else if (DataType.INTEGER.equals(dataType)) {
122                    Integer v1 = getInteger(fieldValue);
123                    Integer v2 = getInteger(whenValue);
124                    compareResult = v1.compareTo(v2);
125                } else if (DataType.LONG.equals(dataType)) {
126                    Long v1 = getLong(fieldValue);
127                    Long v2 = getLong(whenValue);
128                    compareResult = v1.compareTo(v2);
129                } else if (DataType.DOUBLE.equals(dataType)) {
130                    Double v1 = getDouble(fieldValue);
131                    Double v2 = getDouble(whenValue);
132                    compareResult = v1.compareTo(v2);
133                } else if (DataType.FLOAT.equals(dataType)) {
134                    Float v1 = getFloat(fieldValue);
135                    Float v2 = getFloat(whenValue);
136                    compareResult = v1.compareTo(v2);
137                } else if (DataType.BOOLEAN.equals(dataType)) {
138                    Boolean v1 = getBoolean(fieldValue);
139                    Boolean v2 = getBoolean(whenValue);
140                    compareResult = v1.compareTo(v2);
141                } else if (DataType.DATE.equals(dataType)) {
142                    Date v1 = getDate(fieldValue, dateTimeService);
143                    Date v2 = getDate(whenValue, dateTimeService);
144                    compareResult = v1.compareTo(v2);
145                }
146            }
147    
148            if (null != compareResult) {
149                if ((UifConstants.CaseConstraintOperators.EQUALS.equalsIgnoreCase(operator) || UifConstants
150                        .CaseConstraintOperators.GREATER_THAN_EQUAL.equalsIgnoreCase(operator) || UifConstants
151                        .CaseConstraintOperators.LESS_THAN_EQUAL.equalsIgnoreCase(operator)) && 0 == compareResult) {
152                    result = true;
153                }
154    
155                if ((UifConstants.CaseConstraintOperators.NOT_EQUAL.equalsIgnoreCase(operator) || UifConstants
156                        .CaseConstraintOperators.NOT_EQUALS.equalsIgnoreCase(operator) || UifConstants
157                        .CaseConstraintOperators.GREATER_THAN.equalsIgnoreCase(operator)) && compareResult >= 1) {
158                    result = true;
159                }
160    
161                if ((UifConstants.CaseConstraintOperators.NOT_EQUAL.equalsIgnoreCase(operator) || UifConstants
162                        .CaseConstraintOperators.NOT_EQUALS.equalsIgnoreCase(operator) || UifConstants
163                        .CaseConstraintOperators.LESS_THAN.equalsIgnoreCase(operator)) && compareResult <= -1) {
164                    result = true;
165                }
166            }
167    
168            return result;
169        }
170    
171        /**
172         * converts the provided object into an integer
173         *
174         * @param o - the object to convert
175         * @return the integer value
176         */
177        public static Integer getInteger(Object o) {
178            Integer result = null;
179            if (o instanceof Integer) {
180                return (Integer) o;
181            }
182            if (o == null) {
183                return null;
184            }
185            if (o instanceof Number) {
186                return ((Number) o).intValue();
187            }
188            String s = o.toString();
189            if (s != null && s.trim().length() > 0) {
190                result = Integer.valueOf(s.trim());
191            }
192            return result;
193        }
194    
195        /**
196         * converts the provided object into a long
197         *
198         * @param o - the object to convert
199         * @return the long value
200         */
201        public static Long getLong(Object o) {
202            Long result = null;
203            if (o instanceof Long) {
204                return (Long) o;
205            }
206            if (o == null) {
207                return null;
208            }
209            if (o instanceof Number) {
210                return ((Number) o).longValue();
211            }
212            String s = o.toString();
213            if (s != null && s.trim().length() > 0) {
214                result = Long.valueOf(s.trim());
215            }
216            return result;
217        }
218    
219        /**
220         * converts the provided object into an float
221         *
222         * @param o - the object to convert
223         * @return the float value
224         */
225        public static Float getFloat(Object o) {
226            Float result = null;
227            if (o instanceof Float) {
228                return (Float) o;
229            }
230            if (o == null) {
231                return null;
232            }
233            if (o instanceof Number) {
234                return ((Number) o).floatValue();
235            }
236            String s = o.toString();
237            if (s != null && s.trim().length() > 0) {
238                result = Float.valueOf(s.trim());
239            }
240            return result;
241        }
242    
243        /**
244         * converts the provided object into a double
245         *
246         * @param o - the object to convert
247         * @return the double value
248         */
249        public static Double getDouble(Object o) {
250            Double result = null;
251            if (o instanceof BigDecimal) {
252                return ((BigDecimal) o).doubleValue();
253            }
254            if (o instanceof Double) {
255                return (Double) o;
256            }
257            if (o == null) {
258                return null;
259            }
260            if (o instanceof Number) {
261                return ((Number) o).doubleValue();
262            }
263            String s = o.toString();
264            if (s != null && s.trim().length() > 0) {
265                result = Double.valueOf(s.trim());
266            }
267            return result;
268        }
269    
270        /**
271         * determines whether the provided object is a date and tries to converts non-date values
272         *
273         * @param object - the object to convert/cast into a date
274         * @param dateTimeService - used to convert strings to dates
275         * @return a date object
276         * @throws IllegalArgumentException
277         */
278        public static Date getDate(Object object, DateTimeService dateTimeService) throws IllegalArgumentException {
279            Date result = null;
280            if (object instanceof Date) {
281                return (Date) object;
282            }
283            if (object == null) {
284                return null;
285            }
286            String s = object.toString();
287            if (s != null && s.trim().length() > 0) {
288                try {
289                    result = dateTimeService.convertToDate(s.trim());
290                } catch (ParseException e) {
291                    throw new IllegalArgumentException(e);
292                }
293            }
294            return result;
295        }
296    
297        /**
298         * converts the provided object into a string
299         *
300         * @param o - the object to convert
301         * @return the string value
302         */
303        public static String getString(Object o) {
304            if (o instanceof String) {
305                return (String) o;
306            }
307            if (o == null) {
308                return null;
309            }
310            return o.toString();
311        }
312    
313        /**
314         * converts the provided object into a boolean
315         *
316         * @param o - the object to convert
317         * @return the boolean value
318         */
319        public static Boolean getBoolean(Object o) {
320            Boolean result = null;
321            if (o instanceof Boolean) {
322                return (Boolean) o;
323            }
324            if (o == null) {
325                return null;
326            }
327            String s = o.toString();
328            if (s != null && s.trim().length() > 0) {
329                result = Boolean.parseBoolean(s.trim());
330            }
331            return result;
332        }
333    
334        /**
335         * checks whether the string contains non-whitespace characters
336         *
337         * @param string
338         * @return true if the string contains at least one none-whitespace character, false otherwise
339         */
340        public static boolean hasText(String string) {
341    
342            if (string == null || string.length() < 1) {
343                return false;
344            }
345            int stringLength = string.length();
346    
347            for (int i = 0; i < stringLength; i++) {
348                char currentChar = string.charAt(i);
349                if (' ' != currentChar || '\t' != currentChar || '\n' != currentChar) {
350                    return true;
351                }
352            }
353    
354            return false;
355        }
356    
357        /**
358         * checks whether the provided object is null or empty
359         *
360         * @param value - the object to check
361         * @return true if the object is null or empty, false otherwise
362         */
363        public static boolean isNullOrEmpty(Object value) {
364            return value == null || (value instanceof String && StringUtils.isBlank(((String) value).trim()));
365        }
366    
367        /**
368         * defines possible result values of a comparison operation
369         */
370        public static enum Result {
371            VALID, INVALID, UNDEFINED
372        }
373    
374        ;
375    
376        /**
377         * attempts to convert the provided value to the given dataType
378         *
379         * @param value - the object to convert
380         * @param dataType - the data type to convert into
381         * @param dateTimeService - used to convert strings to dates
382         * @return the converted value if null or successful, otherwise throws an exception
383         * @throws AttributeValidationException
384         */
385        public static Object convertToDataType(Object value, DataType dataType,
386                DateTimeService dateTimeService) throws AttributeValidationException {
387            Object returnValue = value;
388    
389            if (null == value) {
390                return null;
391            }
392    
393            switch (dataType) {
394                case BOOLEAN:
395                    if (!(value instanceof Boolean)) {
396                        returnValue = Boolean.valueOf(value.toString());
397    
398                        // Since the Boolean.valueOf is exceptionally loose - it basically takes any string and makes it false
399                        if (!value.toString().equalsIgnoreCase("TRUE") && !value.toString().equalsIgnoreCase("FALSE")) {
400                            throw new AttributeValidationException("Value " + value.toString() + " is not a boolean!");
401                        }
402                    }
403                    break;
404                case INTEGER:
405                    if (!(value instanceof Number)) {
406                        returnValue = Integer.valueOf(value.toString());
407                    }
408                    break;
409                case LONG:
410                    if (!(value instanceof Number)) {
411                        returnValue = Long.valueOf(value.toString());
412                    }
413                    break;
414                case DOUBLE:
415                    if (!(value instanceof Number)) {
416                        returnValue = Double.valueOf(value.toString());
417                    }
418                    if (((Double) returnValue).isNaN()) {
419                        throw new AttributeValidationException("Infinite Double values are not valid!");
420                    }
421                    if (((Double) returnValue).isInfinite()) {
422                        throw new AttributeValidationException("Infinite Double values are not valid!");
423                    }
424                    break;
425                case FLOAT:
426                    if (!(value instanceof Number)) {
427                        returnValue = Float.valueOf(value.toString());
428                    }
429                    if (((Float) returnValue).isNaN()) {
430                        throw new AttributeValidationException("NaN Float values are not valid!");
431                    }
432                    if (((Float) returnValue).isInfinite()) {
433                        throw new AttributeValidationException("Infinite Float values are not valid!");
434                    }
435                    break;
436                case TRUNCATED_DATE:
437                case DATE:
438                    if (!(value instanceof Date)) {
439                        try {
440                            returnValue = dateTimeService.convertToDate(value.toString());
441                        } catch (ParseException pe) {
442                            throw new AttributeValidationException("Value " + value.toString() + " is not a date!");
443                        }
444                    }
445                    break;
446                case STRING:
447            }
448    
449            return returnValue;
450        }
451    
452        /**
453         * checks whether the provided value is greater than the limit given
454         *
455         * @param value - the object to check
456         * @param limit - the limit to use
457         * @param <T>
458         * @return one of the values in {@link  Result}
459         */
460        public static <T> Result isGreaterThan(T value, Comparable<T> limit) {
461            return limit == null ? Result.UNDEFINED : (limit.compareTo(value) < 0 ? Result.VALID : Result.INVALID);
462        }
463    
464        /**
465         * checks whether the provided value is greater than or equal to the limit given
466         *
467         * @param value - the object to check
468         * @param limit - the limit to use
469         * @param <T>
470         * @return one of the values in {@link  Result}
471         */
472        public static <T> Result isGreaterThanOrEqual(T value, Comparable<T> limit) {
473            return limit == null ? Result.UNDEFINED : (limit.compareTo(value) <= 0 ? Result.VALID : Result.INVALID);
474        }
475    
476        /**
477         * checks whether the provided value is less than the limit given
478         *
479         * @param value - the object to check
480         * @param limit - the limit to use
481         * @param <T>
482         * @return one of the values in {@link  Result}
483         */
484        public static <T> Result isLessThan(T value, Comparable<T> limit) {
485            return limit == null ? Result.UNDEFINED : (limit.compareTo(value) > 0 ? Result.VALID : Result.INVALID);
486        }
487    
488        /**
489         * checks whether the provided value is greater than the limit given
490         *
491         * @param value - the object to check
492         * @param limit - the limit to use
493         * @param <T>
494         * @return one of the values in {@link  Result}
495         */
496        public static <T> Result isLessThanOrEqual(T value, Comparable<T> limit) {
497            return limit == null ? Result.UNDEFINED : (limit.compareTo(value) >= 0 ? Result.VALID : Result.INVALID);
498        }
499    
500        /**
501         * converts a path into an array of its path components
502         *
503         * @param fieldPath - a string representation of specifically which attribute (at some depth) is being accessed
504         * @return the array of path components
505         */
506        public static String[] getPathTokens(String fieldPath) {
507            return (fieldPath != null && fieldPath.contains(".") ? fieldPath.split("\\.") : new String[]{fieldPath});
508        }
509    
510    }
511