001/* 002 * Copyright 2007-2008 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.ole.sys; 017 018import java.lang.reflect.InvocationTargetException; 019import java.lang.reflect.Method; 020import java.math.BigDecimal; 021import java.sql.Date; 022import java.sql.Timestamp; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.HashMap; 026import java.util.LinkedHashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Properties; 030 031import org.apache.commons.beanutils.DynaClass; 032import org.apache.commons.beanutils.DynaProperty; 033import org.apache.commons.beanutils.PropertyUtils; 034import org.apache.commons.beanutils.WrapDynaClass; 035import org.apache.commons.lang.ObjectUtils; 036import org.apache.commons.lang.StringUtils; 037import org.kuali.rice.core.api.util.type.KualiDecimal; 038import org.kuali.rice.core.api.util.type.KualiInteger; 039import org.kuali.rice.krad.bo.PersistableBusinessObjectBase; 040 041/** 042 * This class provides a set of facilities that can be used to manipulate objects, for example, object population 043 */ 044public 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 if (LOG.isDebugEnabled()) { 302 LOG.debug(e.getMessage() + ":" + propertyName); 303 } 304 } 305 catch (InvocationTargetException e) { 306 if (LOG.isDebugEnabled()) { 307 LOG.debug(e.getMessage() + ":" + propertyName); 308 } 309 } 310 catch (NoSuchMethodException e) { 311 if (LOG.isDebugEnabled()) { 312 LOG.debug(e.getMessage() + ":" + propertyName); 313 } 314 } 315 catch (IllegalArgumentException e) { 316 if (LOG.isDebugEnabled()) { 317 LOG.debug(e.getMessage() + ":" + propertyName); 318 } 319 } 320 catch (Exception e) { 321 if (LOG.isDebugEnabled()) { 322 LOG.debug(e.getMessage() + ":" + propertyName); 323 } 324 } 325 } 326 327 /** 328 * Determine if they have the same values in the specified fields 329 * 330 * @param targetObject the target object 331 * @param sourceObject the source object 332 * @param keyFields the specified fields 333 * @return true if the two objects have the same values in the specified fields; otherwise, false 334 */ 335 public static boolean equals(Object targetObject, Object sourceObject, List<String> keyFields) { 336 if (targetObject == sourceObject) { 337 return true; 338 } 339 340 if (targetObject == null || sourceObject == null) { 341 return false; 342 } 343 344 for (String propertyName : keyFields) { 345 try { 346 Object propertyValueOfSource = PropertyUtils.getProperty(sourceObject, propertyName); 347 Object propertyValueOfTarget = PropertyUtils.getProperty(targetObject, propertyName); 348 349 if (!ObjectUtils.equals(propertyValueOfSource, propertyValueOfTarget)) { 350 return false; 351 } 352 } 353 catch (Exception e) { 354 LOG.info(e); 355 return false; 356 } 357 } 358 return true; 359 } 360 361 /** 362 * compute the hash code for the given object from the given fields 363 * 364 * @param object the given object 365 * @param keyFields the specified fields 366 * @return the hash code for the given object from the given fields 367 */ 368 public static int generateHashCode(Object object, List<String> keyFields) { 369 if (object == null) { 370 return 0; 371 } 372 373 final int prime = 31; 374 int result = 1; 375 for (String propertyName : keyFields) { 376 try { 377 Object propertyValue = PropertyUtils.getProperty(object, propertyName); 378 result = prime * result + ((propertyValue == null) ? 0 : propertyValue.hashCode()); 379 } 380 catch (Exception e) { 381 LOG.info(e); 382 } 383 } 384 return result; 385 } 386 387 /** 388 * build a map of business object with its specified property names and corresponding values 389 * 390 * @param businessObject the given business object 391 * @param the specified fields that need to be included in the return map 392 * @return the map of business object with its property names and values 393 */ 394 public static Map<String, Object> buildPropertyMap(Object object, List<String> keyFields) { 395 DynaClass dynaClass = WrapDynaClass.createDynaClass(object.getClass()); 396 DynaProperty[] properties = dynaClass.getDynaProperties(); 397 Map<String, Object> propertyMap = new LinkedHashMap<String, Object>(); 398 399 for (DynaProperty property : properties) { 400 String propertyName = property.getName(); 401 402 if (PropertyUtils.isReadable(object, propertyName) && keyFields.contains(propertyName)) { 403 try { 404 Object propertyValue = PropertyUtils.getProperty(object, propertyName); 405 406 if (propertyValue != null && !StringUtils.isEmpty(propertyValue.toString())) { 407 propertyMap.put(propertyName, propertyValue); 408 } 409 } 410 catch (Exception e) { 411 LOG.info(e); 412 } 413 } 414 } 415 return propertyMap; 416 } 417 418 /** 419 * concat the specified properties of the given object as a string 420 * 421 * @param object the given object 422 * @param the specified fields that need to be included in the return string 423 * @return the specified properties of the given object as a string 424 */ 425 public static String concatPropertyAsString(Object object, List<String> keyFields) { 426 StringBuilder propertyAsString = new StringBuilder(); 427 for (String field : keyFields) { 428 if (PropertyUtils.isReadable(object, field)) { 429 try { 430 propertyAsString.append(PropertyUtils.getProperty(object, field)); 431 } 432 catch (Exception e) { 433 LOG.error(e); 434 } 435 } 436 } 437 438 return propertyAsString.toString(); 439 } 440 441 /** 442 * Tokenize the input line with the given deliminator and populate the given object with values of the tokens 443 * 444 * @param targetObject the target object 445 * @param line the input line 446 * @param delim the deminator that separates the fields in the given line 447 * @param keyFields the specified fields 448 */ 449 public static void convertLineToBusinessObject(Object targetObject, String line, String delim, List<String> keyFields) { 450 String[] tokens = StringUtils.split(line, delim); 451 ObjectUtil.buildObject(targetObject, tokens, keyFields); 452 } 453 454 /** 455 * Tokenize the input line with the given deliminator and populate the given object with values of the tokens 456 * 457 * @param targetObject the target object 458 * @param line the input line 459 * @param delim the deminator that separates the fields in the given line 460 * @param keyFields the specified fields 461 */ 462 public static void convertLineToBusinessObject(Object targetObject, String line, String delim, String fieldNames) { 463 List<String> tokens = split(line, delim); 464 List<String> keyFields = Arrays.asList(StringUtils.split(fieldNames, delim)); 465 ObjectUtil.buildObject(targetObject, tokens.toArray(), keyFields); 466 } 467 468 /** 469 * Tokenize the input line with the given deliminator and store the tokens in a list 470 * 471 * @param line the input line 472 * @param delim the deminator that separates the fields in the given line 473 * @return a list of tokens 474 */ 475 public static List<String> split(String line, String delim) { 476 List<String> tokens = new ArrayList<String>(); 477 478 int currentPosition = 0; 479 for (int step = 0; step < line.length(); step++) { 480 int previousPosition = currentPosition; 481 currentPosition = StringUtils.indexOf(line, delim, currentPosition); 482 currentPosition = currentPosition == -1 ? line.length() - 1 : currentPosition; 483 484 String sub = line.substring(previousPosition, currentPosition); 485 tokens.add(sub); // don't trim the string 486 487 currentPosition += delim.length(); 488 if (currentPosition >= line.length()) { 489 break; 490 } 491 } 492 return tokens; 493 } 494 495 /** 496 * Tokenize the input line with the given deliminator and populate the given object with values of the tokens 497 * 498 * @param targetObject the target object 499 * @param line the input line 500 * @param delim the deminator that separates the fields in the given line 501 * @param keyFields the specified fields 502 */ 503 public static void convertLineToBusinessObject(Object targetObject, String line, int[] fieldLength, List<String> keyFields) { 504 String[] tokens = new String[fieldLength.length]; 505 506 int currentPosition = 0; 507 for (int i = 0; i < fieldLength.length; i++) { 508 currentPosition = i <= 0 ? 0 : fieldLength[i - 1] + currentPosition; 509 tokens[i] = StringUtils.mid(line, currentPosition, fieldLength[i]).trim(); 510 } 511 ObjectUtil.buildObject(targetObject, tokens, keyFields); 512 } 513 514 /** 515 * Populate a business object with the given properities and information 516 * 517 * @param businessOjbject the business object to be populated 518 * @param properties the given properties 519 * @param propertyKey the property keys in the properties 520 * @param fieldNames the names of the fields to be populated 521 * @param deliminator the deliminator that separates the values to be used in a string 522 */ 523 public static void populateBusinessObject(Object businessOjbject, Properties properties, String propertyKey, String fieldNames, String deliminator) { 524 String data = properties.getProperty(propertyKey); 525 ObjectUtil.convertLineToBusinessObject(businessOjbject, data, deliminator, fieldNames); 526 } 527 528 /** 529 * Populate a business object with the given properities and information 530 * 531 * @param businessOjbject the business object to be populated 532 * @param properties the given properties 533 * @param propertyKey the property keys in the properties 534 * @param fieldNames the names of the fields to be populated 535 * @param deliminator the deliminator that separates the values to be used in a string 536 */ 537 public static void populateBusinessObject(Object businessOjbject, Properties properties, String propertyKey, int[] fieldLength, List<String> keyFields) { 538 String data = properties.getProperty(propertyKey); 539 ObjectUtil.convertLineToBusinessObject(businessOjbject, data, fieldLength, keyFields); 540 } 541 542 /** 543 * determine if the source object has a field with null as its value 544 * 545 * @param sourceObject the source object 546 */ 547 public static boolean hasNullValueField(Object sourceObject) { 548 DynaClass dynaClass = WrapDynaClass.createDynaClass(sourceObject.getClass()); 549 DynaProperty[] properties = dynaClass.getDynaProperties(); 550 551 for (DynaProperty property : properties) { 552 String propertyName = property.getName(); 553 554 if (PropertyUtils.isReadable(sourceObject, propertyName)) { 555 try { 556 Object propertyValue = PropertyUtils.getProperty(sourceObject, propertyName); 557 if (propertyValue == null) { 558 return true; 559 } 560 } 561 catch (Exception e) { 562 LOG.info(e); 563 return false; 564 } 565 } 566 } 567 return false; 568 } 569 570 /** 571 * get the types of the nested attributes starting at the given class 572 * 573 * @param clazz the given class 574 * @param nestedAttribute the nested attributes of the given class 575 * @return a map that contains the types of the nested attributes and the attribute names 576 */ 577 public static Map<Class<?>, String> getNestedAttributeTypes(Class<?> clazz, String nestedAttribute) { 578 List<String> attributes = Arrays.asList(StringUtils.split(nestedAttribute, PropertyUtils.NESTED_DELIM)); 579 Map<Class<?>, String> nestedAttributes = new HashMap<Class<?>, String>(); 580 581 Class<?> currentClass = clazz; 582 for (String propertyName : attributes) { 583 String methodName = "get" + StringUtils.capitalize(propertyName); 584 try { 585 Method method = currentClass.getMethod(methodName); 586 currentClass = method.getReturnType(); 587 nestedAttributes.put(currentClass, propertyName); 588 } 589 catch (Exception e) { 590 LOG.info(e); 591 break; 592 } 593 } 594 return nestedAttributes; 595 } 596}