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