001 /* 002 * Copyright 2011 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.kfs.sys; 017 018 import java.lang.reflect.InvocationTargetException; 019 import java.lang.reflect.Method; 020 import java.math.BigDecimal; 021 import java.sql.Date; 022 import java.sql.Timestamp; 023 import java.util.ArrayList; 024 import java.util.Arrays; 025 import java.util.HashMap; 026 import java.util.LinkedHashMap; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.Properties; 030 031 import org.apache.commons.beanutils.DynaClass; 032 import org.apache.commons.beanutils.DynaProperty; 033 import org.apache.commons.beanutils.PropertyUtils; 034 import org.apache.commons.beanutils.WrapDynaClass; 035 import org.apache.commons.lang.ObjectUtils; 036 import org.apache.commons.lang.StringUtils; 037 import org.kuali.rice.kns.bo.PersistableBusinessObjectBase; 038 import org.kuali.rice.kns.util.KualiDecimal; 039 import org.kuali.rice.kns.util.KualiInteger; 040 041 /** 042 * This class provides a set of facilities that can be used to manipulate objects, for example, object population 043 */ 044 public class ObjectUtil { 045 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ObjectUtil.class); 046 047 /** 048 * create an object of the specified type 049 * 050 * @param clazz the specified type of the object 051 * @return an object of the specified type 052 */ 053 public static <T> T createObject(Class<T> clazz) { 054 T object = null; 055 056 try { 057 object = clazz.newInstance(); 058 } 059 catch (InstantiationException ie) { 060 LOG.error(ie); 061 throw new RuntimeException(ie); 062 } 063 catch (IllegalAccessException iae) { 064 LOG.error(iae); 065 throw new RuntimeException(iae); 066 } 067 068 return object; 069 } 070 071 /** 072 * Populate the given fields of the target object with the corresponding field values of source object 073 * 074 * @param targetObject the target object 075 * @param sourceObject the source object 076 * @param keyFields the given fields of the target object that need to be popluated 077 */ 078 public static void buildObject(Object targetObject, Object sourceObject, List<String> keyFields) { 079 if (sourceObject.getClass().isArray()) { 080 buildObject(targetObject, sourceObject, keyFields); 081 return; 082 } 083 084 for (String propertyName : keyFields) { 085 if (PropertyUtils.isReadable(sourceObject, propertyName) && PropertyUtils.isWriteable(targetObject, propertyName)) { 086 try { 087 Object propertyValue = PropertyUtils.getProperty(sourceObject, propertyName); 088 PropertyUtils.setProperty(targetObject, propertyName, propertyValue); 089 } 090 catch (Exception e) { 091 LOG.debug(e); 092 } 093 } 094 } 095 } 096 097 /** 098 * Populate the given fields of the target object with the values of an array 099 * 100 * @param targetObject the target object 101 * @param sourceObject the given array 102 * @param keyFields the given fields of the target object that need to be popluated 103 */ 104 public static void buildObject(Object targetObject, Object[] sourceObject, List<String> keyFields) { 105 int indexOfArray = 0; 106 for (String propertyName : keyFields) { 107 if (PropertyUtils.isWriteable(targetObject, propertyName) && indexOfArray < sourceObject.length) { 108 try { 109 Object value = sourceObject[indexOfArray]; 110 String propertyValue = value != null ? value.toString() : StringUtils.EMPTY; 111 112 String type = getSimpleTypeName(targetObject, propertyName); 113 Object realPropertyValue = valueOf(type, propertyValue); 114 115 if (realPropertyValue != null && !StringUtils.isEmpty(realPropertyValue.toString())) { 116 PropertyUtils.setProperty(targetObject, propertyName, realPropertyValue); 117 } 118 else { 119 PropertyUtils.setProperty(targetObject, propertyName, null); 120 } 121 } 122 catch (Exception e) { 123 LOG.debug(e); 124 } 125 } 126 indexOfArray++; 127 } 128 } 129 130 public static String getSimpleTypeName(Object targetObject, String propertyName) { 131 String simpleTypeName = StringUtils.EMPTY; 132 try { 133 simpleTypeName = PropertyUtils.getPropertyType(targetObject, propertyName).getSimpleName(); 134 } 135 catch (Exception e) { 136 LOG.debug(e); 137 } 138 139 return simpleTypeName; 140 } 141 142 /** 143 * Get an object of the given type holding the property value of the specified String. 144 * 145 * @param type the given type of the returning object 146 * @param propertyValue the property value of the specified string 147 * @return an object of the given type holding the property value of the specified String 148 */ 149 public static Object valueOf(String type, String propertyValue) { 150 Object realPropertyValue = null; 151 152 if (type.equals("Integer")) { 153 realPropertyValue = isInteger(propertyValue) ? Integer.valueOf(propertyValue) : null; 154 } 155 else if (type.equals("KualiInteger")) { 156 realPropertyValue = isInteger(propertyValue) ? new KualiInteger(propertyValue) : null; 157 } 158 else if (type.equalsIgnoreCase("Boolean")) { 159 realPropertyValue = Boolean.valueOf(propertyValue); 160 } 161 else if (type.equals("KualiDecimal")) { 162 realPropertyValue = isDecimal(propertyValue) ? new KualiDecimal(propertyValue) : null; 163 } 164 else if (type.equals("Date")) { 165 realPropertyValue = formatDate(propertyValue); 166 } 167 else if (type.equals("BigDecimal")) { 168 realPropertyValue = isDecimal(propertyValue) ? new BigDecimal(propertyValue) : null; 169 } 170 else if (type.equals("Timestamp")) { 171 realPropertyValue = formatTimeStamp(propertyValue); 172 } 173 else { 174 realPropertyValue = propertyValue; 175 } 176 return realPropertyValue; 177 } 178 179 /** 180 * determine if the given string can be converted into an Integer 181 * 182 * @param value the value of the specified string 183 * @return true if the string can be converted into an Integer; otherwise, return false 184 */ 185 public static boolean isInteger(String value) { 186 String pattern = "^(\\+|-)?\\d+$"; 187 return value != null && value.matches(pattern); 188 } 189 190 /** 191 * determine if the given string can be converted into a decimal 192 * 193 * @param value the value of the specified string 194 * @return true if the string can be converted into a decimal; otherwise, return false 195 */ 196 public static boolean isDecimal(String value) { 197 String pattern = "^(((\\+|-)?\\d+(\\.\\d*)?)|((\\+|-)?(\\d*\\.)?\\d+))$"; 198 return value != null && value.matches(pattern); 199 } 200 201 /** 202 * convert the given string into a date 203 * 204 * @param value the given string 205 * @return a date converted from the given string 206 */ 207 public static Date formatDate(String value) { 208 Date formattedDate = null; 209 210 try { 211 formattedDate = Date.valueOf(value); 212 } 213 catch (Exception e) { 214 return formattedDate; 215 } 216 return formattedDate; 217 } 218 219 /** 220 * convert the given string into a timestamp object if the string is in the valid format of timestamp 221 * 222 * @param value the given string 223 * @return a timestamp converted from the given string 224 */ 225 public static Timestamp formatTimeStamp(String value) { 226 Timestamp formattedTimestamp = null; 227 228 String pattern = "^(\\d{1,4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d{1,9})?)$"; 229 boolean isTimestamp = value != null && value.matches(pattern); 230 231 try { 232 if (isTimestamp) { 233 formattedTimestamp = Timestamp.valueOf(value); 234 } 235 else { 236 formattedTimestamp = new Timestamp(formatDate(value).getTime()); 237 } 238 } 239 catch (Exception e) { 240 return formattedTimestamp; 241 } 242 return formattedTimestamp; 243 } 244 245 /** 246 * Populate the target object with the source object 247 * 248 * @param targetObject the target object 249 * @param sourceObject the source object 250 */ 251 public static void buildObject(Object targetObject, Object sourceObject) { 252 DynaClass dynaClass = WrapDynaClass.createDynaClass(targetObject.getClass()); 253 DynaProperty[] properties = dynaClass.getDynaProperties(); 254 255 for (DynaProperty property : properties) { 256 ObjectUtil.setProperty(targetObject, sourceObject, property, false); 257 } 258 } 259 260 /** 261 * Populate the target object with the source object 262 * 263 * @param targetObject the target object 264 * @param sourceObject the source object 265 */ 266 public static void buildObjectWithoutReferenceFields(Object targetObject, Object sourceObject) { 267 DynaClass dynaClass = WrapDynaClass.createDynaClass(targetObject.getClass()); 268 DynaProperty[] properties = dynaClass.getDynaProperties(); 269 270 for (DynaProperty property : properties) { 271 ObjectUtil.setProperty(targetObject, sourceObject, property, true); 272 } 273 } 274 275 /** 276 * Populate the property of the target object with the counterpart of the source object 277 * 278 * @param targetObject the target object 279 * @param sourceObject the source object 280 * @param property the specified propety of the target object 281 * @param skipReferenceFields determine whether the referencing fields need to be populated 282 */ 283 public static void setProperty(Object targetObject, Object sourceObject, DynaProperty property, boolean skipReferenceFields) { 284 String propertyName = property.getName(); 285 286 try { 287 if (skipReferenceFields) { 288 @SuppressWarnings("rawtypes") 289 Class propertyType = property.getType(); 290 if (propertyType == null || PersistableBusinessObjectBase.class.isAssignableFrom(propertyType) || List.class.isAssignableFrom(propertyType)) { 291 return; 292 } 293 } 294 295 if (PropertyUtils.isReadable(sourceObject, propertyName) && PropertyUtils.isWriteable(targetObject, propertyName)) { 296 Object propertyValue = PropertyUtils.getProperty(sourceObject, propertyName); 297 PropertyUtils.setProperty(targetObject, propertyName, propertyValue); 298 } 299 } 300 catch (IllegalAccessException e) { 301 LOG.debug(e.getMessage() + ":" + propertyName); 302 } 303 catch (InvocationTargetException e) { 304 LOG.debug(e.getMessage() + ":" + propertyName); 305 } 306 catch (NoSuchMethodException e) { 307 LOG.debug(e.getMessage() + ":" + propertyName); 308 } 309 catch (IllegalArgumentException e) { 310 LOG.debug(e.getMessage() + ":" + propertyName); 311 } 312 catch (Exception e) { 313 LOG.debug(e.getMessage() + ":" + propertyName); 314 } 315 } 316 317 /** 318 * Determine if they have the same values in the specified fields 319 * 320 * @param targetObject the target object 321 * @param sourceObject the source object 322 * @param keyFields the specified fields 323 * @return true if the two objects have the same values in the specified fields; otherwise, false 324 */ 325 public static boolean equals(Object targetObject, Object sourceObject, List<String> keyFields) { 326 if (targetObject == sourceObject) { 327 return true; 328 } 329 330 if (targetObject == null || sourceObject == null) { 331 return false; 332 } 333 334 for (String propertyName : keyFields) { 335 try { 336 Object propertyValueOfSource = PropertyUtils.getProperty(sourceObject, propertyName); 337 Object propertyValueOfTarget = PropertyUtils.getProperty(targetObject, propertyName); 338 339 if (!ObjectUtils.equals(propertyValueOfSource, propertyValueOfTarget)) { 340 return false; 341 } 342 } 343 catch (Exception e) { 344 LOG.info(e); 345 return false; 346 } 347 } 348 return true; 349 } 350 351 /** 352 * compute the hash code for the given object from the given fields 353 * 354 * @param object the given object 355 * @param keyFields the specified fields 356 * @return the hash code for the given object from the given fields 357 */ 358 public static int generateHashCode(Object object, List<String> keyFields) { 359 if (object == null) { 360 return 0; 361 } 362 363 final int prime = 31; 364 int result = 1; 365 for (String propertyName : keyFields) { 366 try { 367 Object propertyValue = PropertyUtils.getProperty(object, propertyName); 368 result = prime * result + ((propertyValue == null) ? 0 : propertyValue.hashCode()); 369 } 370 catch (Exception e) { 371 LOG.info(e); 372 } 373 } 374 return result; 375 } 376 377 /** 378 * build a map of business object with its specified property names and corresponding values 379 * 380 * @param businessObject the given business object 381 * @param the specified fields that need to be included in the return map 382 * @return the map of business object with its property names and values 383 */ 384 public static Map<String, Object> buildPropertyMap(Object object, List<String> keyFields) { 385 DynaClass dynaClass = WrapDynaClass.createDynaClass(object.getClass()); 386 DynaProperty[] properties = dynaClass.getDynaProperties(); 387 Map<String, Object> propertyMap = new LinkedHashMap<String, Object>(); 388 389 for (DynaProperty property : properties) { 390 String propertyName = property.getName(); 391 392 if (PropertyUtils.isReadable(object, propertyName) && keyFields.contains(propertyName)) { 393 try { 394 Object propertyValue = PropertyUtils.getProperty(object, propertyName); 395 396 if (propertyValue != null && !StringUtils.isEmpty(propertyValue.toString())) { 397 propertyMap.put(propertyName, propertyValue); 398 } 399 } 400 catch (Exception e) { 401 LOG.info(e); 402 } 403 } 404 } 405 return propertyMap; 406 } 407 408 /** 409 * concat the specified properties of the given object as a string 410 * 411 * @param object the given object 412 * @param the specified fields that need to be included in the return string 413 * @return the specified properties of the given object as a string 414 */ 415 public static String concatPropertyAsString(Object object, List<String> keyFields) { 416 StringBuilder propertyAsString = new StringBuilder(); 417 for (String field : keyFields) { 418 if (PropertyUtils.isReadable(object, field)) { 419 try { 420 propertyAsString.append(PropertyUtils.getProperty(object, field)); 421 } 422 catch (Exception e) { 423 LOG.error(e); 424 } 425 } 426 } 427 428 return propertyAsString.toString(); 429 } 430 431 /** 432 * Tokenize the input line with the given deliminator and populate the given object with values of the tokens 433 * 434 * @param targetObject the target object 435 * @param line the input line 436 * @param delim the deminator that separates the fields in the given line 437 * @param keyFields the specified fields 438 */ 439 public static void convertLineToBusinessObject(Object targetObject, String line, String delim, List<String> keyFields) { 440 String[] tokens = StringUtils.split(line, delim); 441 ObjectUtil.buildObject(targetObject, tokens, keyFields); 442 } 443 444 /** 445 * Tokenize the input line with the given deliminator and populate the given object with values of the tokens 446 * 447 * @param targetObject the target object 448 * @param line the input line 449 * @param delim the deminator that separates the fields in the given line 450 * @param keyFields the specified fields 451 */ 452 public static void convertLineToBusinessObject(Object targetObject, String line, String delim, String fieldNames) { 453 List<String> tokens = split(line, delim); 454 List<String> keyFields = Arrays.asList(StringUtils.split(fieldNames, delim)); 455 ObjectUtil.buildObject(targetObject, tokens.toArray(), keyFields); 456 } 457 458 /** 459 * Tokenize the input line with the given deliminator and store the tokens in a list 460 * 461 * @param line the input line 462 * @param delim the deminator that separates the fields in the given line 463 * @return a list of tokens 464 */ 465 public static List<String> split(String line, String delim) { 466 List<String> tokens = new ArrayList<String>(); 467 468 int currentPosition = 0; 469 for (int step = 0; step < line.length(); step++) { 470 int previousPosition = currentPosition; 471 currentPosition = StringUtils.indexOf(line, delim, currentPosition); 472 currentPosition = currentPosition == -1 ? line.length() - 1 : currentPosition; 473 474 String sub = line.substring(previousPosition, currentPosition); 475 tokens.add(sub); // don't trim the string 476 477 currentPosition += delim.length(); 478 if (currentPosition >= line.length()) { 479 break; 480 } 481 } 482 return tokens; 483 } 484 485 /** 486 * Tokenize the input line with the given deliminator and populate the given object with values of the tokens 487 * 488 * @param targetObject the target object 489 * @param line the input line 490 * @param delim the deminator that separates the fields in the given line 491 * @param keyFields the specified fields 492 */ 493 public static void convertLineToBusinessObject(Object targetObject, String line, int[] fieldLength, List<String> keyFields) { 494 String[] tokens = new String[fieldLength.length]; 495 496 int currentPosition = 0; 497 for (int i = 0; i < fieldLength.length; i++) { 498 currentPosition = i <= 0 ? 0 : fieldLength[i - 1] + currentPosition; 499 tokens[i] = StringUtils.mid(line, currentPosition, fieldLength[i]).trim(); 500 } 501 ObjectUtil.buildObject(targetObject, tokens, keyFields); 502 } 503 504 /** 505 * Populate a business object with the given properities and information 506 * 507 * @param businessOjbject the business object to be populated 508 * @param properties the given properties 509 * @param propertyKey the property keys in the properties 510 * @param fieldNames the names of the fields to be populated 511 * @param deliminator the deliminator that separates the values to be used in a string 512 */ 513 public static void populateBusinessObject(Object businessOjbject, Properties properties, String propertyKey, String fieldNames, String deliminator) { 514 String data = properties.getProperty(propertyKey); 515 ObjectUtil.convertLineToBusinessObject(businessOjbject, data, deliminator, fieldNames); 516 } 517 518 /** 519 * Populate a business object with the given properities and information 520 * 521 * @param businessOjbject the business object to be populated 522 * @param properties the given properties 523 * @param propertyKey the property keys in the properties 524 * @param fieldNames the names of the fields to be populated 525 * @param deliminator the deliminator that separates the values to be used in a string 526 */ 527 public static void populateBusinessObject(Object businessOjbject, Properties properties, String propertyKey, int[] fieldLength, List<String> keyFields) { 528 String data = properties.getProperty(propertyKey); 529 ObjectUtil.convertLineToBusinessObject(businessOjbject, data, fieldLength, keyFields); 530 } 531 532 /** 533 * determine if the source object has a field with null as its value 534 * 535 * @param sourceObject the source object 536 */ 537 public static boolean hasNullValueField(Object sourceObject) { 538 DynaClass dynaClass = WrapDynaClass.createDynaClass(sourceObject.getClass()); 539 DynaProperty[] properties = dynaClass.getDynaProperties(); 540 541 for (DynaProperty property : properties) { 542 String propertyName = property.getName(); 543 544 if (PropertyUtils.isReadable(sourceObject, propertyName)) { 545 try { 546 Object propertyValue = PropertyUtils.getProperty(sourceObject, propertyName); 547 if (propertyValue == null) { 548 return true; 549 } 550 } 551 catch (Exception e) { 552 LOG.info(e); 553 return false; 554 } 555 } 556 } 557 return false; 558 } 559 560 /** 561 * get the types of the nested attributes starting at the given class 562 * 563 * @param clazz the given class 564 * @param nestedAttribute the nested attributes of the given class 565 * @return a map that contains the types of the nested attributes and the attribute names 566 */ 567 public static Map<Class<?>, String> getNestedAttributeTypes(Class<?> clazz, String nestedAttribute) { 568 List<String> attributes = Arrays.asList(StringUtils.split(nestedAttribute, PropertyUtils.NESTED_DELIM)); 569 Map<Class<?>, String> nestedAttributes = new HashMap<Class<?>, String>(); 570 571 Class<?> currentClass = clazz; 572 for (String propertyName : attributes) { 573 String methodName = "get" + StringUtils.capitalize(propertyName); 574 try { 575 Method method = currentClass.getMethod(methodName); 576 currentClass = method.getReturnType(); 577 nestedAttributes.put(currentClass, propertyName); 578 } 579 catch (Exception e) { 580 LOG.info(e); 581 break; 582 } 583 } 584 return nestedAttributes; 585 } 586 }