001/** 002 * Copyright 2005-2018 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 */ 016package org.kuali.rice.krad.datadictionary.validation; 017 018import java.math.BigDecimal; 019import java.text.ParseException; 020import java.util.Collection; 021import java.util.Date; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.apache.commons.lang.StringUtils; 027import org.kuali.rice.core.api.data.DataType; 028import org.kuali.rice.core.api.datetime.DateTimeService; 029import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException; 030import org.kuali.rice.krad.uif.UifConstants; 031 032/** 033 * ValidationUtils provides static utility methods for validation processing 034 * 035 * <p>Inherited from Kuali Student and adapted extensively</p> 036 * 037 * @author Kuali Rice Team (rice.collab@kuali.org) 038 */ 039public class ValidationUtils { 040 041 /** 042 * constructs a path by appending the attribute name to the provided path 043 * 044 * @param attributePath - a string representation of specifically which attribute (at some depth) is being accessed 045 * @param attributeName - the attribute name 046 * @return the path 047 */ 048 public static String buildPath(String attributePath, String attributeName) { 049 if (StringUtils.isNotBlank(attributeName)) { 050 if (StringUtils.isNotBlank(attributePath)) { 051 return new StringBuilder(attributePath).append(".").append(attributeName).toString(); 052 } 053 054 return attributeName; 055 } 056 return attributePath; 057 } 058 059 /** 060 * Used to get the rightmost index value of an attribute path. 061 * 062 * @param attributePath 063 * @return the right index of value of attribute path, -1 if path has no index 064 */ 065 public static int getLastPathIndex(String attributePath) { 066 int index = -1; 067 068 int leftBracket = attributePath.lastIndexOf("["); 069 int rightBracket = attributePath.lastIndexOf("]"); 070 071 if (leftBracket > 0 && rightBracket > leftBracket) { 072 String indexString = attributePath.substring(leftBracket + 1, rightBracket); 073 try { 074 index = Integer.valueOf(indexString).intValue(); 075 } catch (NumberFormatException e) { 076 // Will just return -1 077 } 078 } 079 080 return index; 081 } 082 083 /** 084 * compares the value provided by the user and the one specified by the {@code WhenConstraint} 085 * 086 * @param fieldValue the value found in the field specified by a {@code CaseConstraint}'s {@code propertyName} 087 * @param whenValue the value specified by a {@code WhenConstraint} 088 * @param dataType the data type of the field which caseConstraint's propertyName refers to 089 * @param operator the relationship to check between the {@code fieldValue} and the {@code whenValue} 090 * @param isCaseSensitive whether string comparison will be carried out in a case sensitive fashion 091 * @param dateTimeService used to convert strings to dates 092 * @return true if the value matches the constraint 093 */ 094 public static boolean compareValues(Object fieldValue, Object whenValue, DataType dataType, String operator, 095 boolean isCaseSensitive, DateTimeService dateTimeService) { 096 097 boolean result = false; 098 Integer compareResult = null; 099 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