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