/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sys; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.sql.Date; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.beanutils.DynaClass; import org.apache.commons.beanutils.DynaProperty; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.beanutils.WrapDynaClass; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.core.api.util.type.KualiInteger; import org.kuali.rice.krad.bo.PersistableBusinessObjectBase; /** * This class provides a set of facilities that can be used to manipulate objects, for example, object population */ public class ObjectUtil { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ObjectUtil.class); /** * create an object of the specified type * * @param clazz the specified type of the object * @return an object of the specified type */ public static <T> T createObject(Class<T> clazz) { T object = null; try { object = clazz.newInstance(); } catch (InstantiationException ie) { LOG.error(ie); throw new RuntimeException(ie); } catch (IllegalAccessException iae) { LOG.error(iae); throw new RuntimeException(iae); } return object; } /** * Populate the given fields of the target object with the corresponding field values of source object * * @param targetObject the target object * @param sourceObject the source object * @param keyFields the given fields of the target object that need to be popluated */ public static void buildObject(Object targetObject, Object sourceObject, List<String> keyFields) { if (sourceObject.getClass().isArray()) { buildObject(targetObject, sourceObject, keyFields); return; } for (String propertyName : keyFields) { if (PropertyUtils.isReadable(sourceObject, propertyName) && PropertyUtils.isWriteable(targetObject, propertyName)) { try { Object propertyValue = PropertyUtils.getProperty(sourceObject, propertyName); PropertyUtils.setProperty(targetObject, propertyName, propertyValue); } catch (Exception e) { LOG.debug(e); } } } } /** * Populate the given fields of the target object with the values of an array * * @param targetObject the target object * @param sourceObject the given array * @param keyFields the given fields of the target object that need to be popluated */ public static void buildObject(Object targetObject, Object[] sourceObject, List<String> keyFields) { int indexOfArray = 0; for (String propertyName : keyFields) { if (PropertyUtils.isWriteable(targetObject, propertyName) && indexOfArray < sourceObject.length) { try { Object value = sourceObject[indexOfArray]; String propertyValue = value != null ? value.toString() : StringUtils.EMPTY; String type = getSimpleTypeName(targetObject, propertyName); Object realPropertyValue = valueOf(type, propertyValue); if (realPropertyValue != null && !StringUtils.isEmpty(realPropertyValue.toString())) { PropertyUtils.setProperty(targetObject, propertyName, realPropertyValue); } else { PropertyUtils.setProperty(targetObject, propertyName, null); } } catch (Exception e) { LOG.debug(e); } } indexOfArray++; } } public static String getSimpleTypeName(Object targetObject, String propertyName) { String simpleTypeName = StringUtils.EMPTY; try { simpleTypeName = PropertyUtils.getPropertyType(targetObject, propertyName).getSimpleName(); } catch (Exception e) { LOG.debug(e); } return simpleTypeName; } /** * Get an object of the given type holding the property value of the specified String. * * @param type the given type of the returning object * @param propertyValue the property value of the specified string * @return an object of the given type holding the property value of the specified String */ public static Object valueOf(String type, String propertyValue) { Object realPropertyValue = null; if (type.equals("Integer")) { realPropertyValue = isInteger(propertyValue) ? Integer.valueOf(propertyValue) : null; } else if (type.equals("KualiInteger")) { realPropertyValue = isInteger(propertyValue) ? new KualiInteger(propertyValue) : null; } else if (type.equalsIgnoreCase("Boolean")) { realPropertyValue = Boolean.valueOf(propertyValue); } else if (type.equals("KualiDecimal")) { realPropertyValue = isDecimal(propertyValue) ? new KualiDecimal(propertyValue) : null; } else if (type.equals("Date")) { realPropertyValue = formatDate(propertyValue); } else if (type.equals("BigDecimal")) { realPropertyValue = isDecimal(propertyValue) ? new BigDecimal(propertyValue) : null; } else if (type.equals("Timestamp")) { realPropertyValue = formatTimeStamp(propertyValue); } else { realPropertyValue = propertyValue; } return realPropertyValue; } /** * determine if the given string can be converted into an Integer * * @param value the value of the specified string * @return true if the string can be converted into an Integer; otherwise, return false */ public static boolean isInteger(String value) { String pattern = "^(\\+|-)?\\d+$"; return value != null && value.matches(pattern); } /** * determine if the given string can be converted into a decimal * * @param value the value of the specified string * @return true if the string can be converted into a decimal; otherwise, return false */ public static boolean isDecimal(String value) { String pattern = "^(((\\+|-)?\\d+(\\.\\d*)?)|((\\+|-)?(\\d*\\.)?\\d+))$"; return value != null && value.matches(pattern); } /** * convert the given string into a date * * @param value the given string * @return a date converted from the given string */ public static Date formatDate(String value) { Date formattedDate = null; try { formattedDate = Date.valueOf(value); } catch (Exception e) { return formattedDate; } return formattedDate; } /** * convert the given string into a timestamp object if the string is in the valid format of timestamp * * @param value the given string * @return a timestamp converted from the given string */ public static Timestamp formatTimeStamp(String value) { Timestamp formattedTimestamp = null; String pattern = "^(\\d{1,4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d{1,9})?)$"; boolean isTimestamp = value != null && value.matches(pattern); try { if (isTimestamp) { formattedTimestamp = Timestamp.valueOf(value); } else { formattedTimestamp = new Timestamp(formatDate(value).getTime()); } } catch (Exception e) { return formattedTimestamp; } return formattedTimestamp; } /** * Populate the target object with the source object * * @param targetObject the target object * @param sourceObject the source object */ public static void buildObject(Object targetObject, Object sourceObject) { DynaClass dynaClass = WrapDynaClass.createDynaClass(targetObject.getClass()); DynaProperty[] properties = dynaClass.getDynaProperties(); for (DynaProperty property : properties) { ObjectUtil.setProperty(targetObject, sourceObject, property, false); } } /** * Populate the target object with the source object * * @param targetObject the target object * @param sourceObject the source object */ public static void buildObjectWithoutReferenceFields(Object targetObject, Object sourceObject) { DynaClass dynaClass = WrapDynaClass.createDynaClass(targetObject.getClass()); DynaProperty[] properties = dynaClass.getDynaProperties(); for (DynaProperty property : properties) { ObjectUtil.setProperty(targetObject, sourceObject, property, true); } } /** * Populate the property of the target object with the counterpart of the source object * * @param targetObject the target object * @param sourceObject the source object * @param property the specified propety of the target object * @param skipReferenceFields determine whether the referencing fields need to be populated */ public static void setProperty(Object targetObject, Object sourceObject, DynaProperty property, boolean skipReferenceFields) { String propertyName = property.getName(); try { if (skipReferenceFields) { @SuppressWarnings("rawtypes") Class propertyType = property.getType(); if (propertyType == null || PersistableBusinessObjectBase.class.isAssignableFrom(propertyType) || List.class.isAssignableFrom(propertyType)) { return; } } if (PropertyUtils.isReadable(sourceObject, propertyName) && PropertyUtils.isWriteable(targetObject, propertyName)) { Object propertyValue = PropertyUtils.getProperty(sourceObject, propertyName); PropertyUtils.setProperty(targetObject, propertyName, propertyValue); } } catch (IllegalAccessException e) { if (LOG.isDebugEnabled()) { LOG.debug(e.getMessage() + ":" + propertyName); } } catch (InvocationTargetException e) { if (LOG.isDebugEnabled()) { LOG.debug(e.getMessage() + ":" + propertyName); } } catch (NoSuchMethodException e) { if (LOG.isDebugEnabled()) { LOG.debug(e.getMessage() + ":" + propertyName); } } catch (IllegalArgumentException e) { if (LOG.isDebugEnabled()) { LOG.debug(e.getMessage() + ":" + propertyName); } } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug(e.getMessage() + ":" + propertyName); } } } /** * Determine if they have the same values in the specified fields * * @param targetObject the target object * @param sourceObject the source object * @param keyFields the specified fields * @return true if the two objects have the same values in the specified fields; otherwise, false */ public static boolean equals(Object targetObject, Object sourceObject, List<String> keyFields) { if (targetObject == sourceObject) { return true; } if (targetObject == null || sourceObject == null) { return false; } for (String propertyName : keyFields) { try { Object propertyValueOfSource = PropertyUtils.getProperty(sourceObject, propertyName); Object propertyValueOfTarget = PropertyUtils.getProperty(targetObject, propertyName); if (!ObjectUtils.equals(propertyValueOfSource, propertyValueOfTarget)) { return false; } } catch (Exception e) { LOG.info(e); return false; } } return true; } /** * compute the hash code for the given object from the given fields * * @param object the given object * @param keyFields the specified fields * @return the hash code for the given object from the given fields */ public static int generateHashCode(Object object, List<String> keyFields) { if (object == null) { return 0; } final int prime = 31; int result = 1; for (String propertyName : keyFields) { try { Object propertyValue = PropertyUtils.getProperty(object, propertyName); result = prime * result + ((propertyValue == null) ? 0 : propertyValue.hashCode()); } catch (Exception e) { LOG.info(e); } } return result; } /** * build a map of business object with its specified property names and corresponding values * * @param businessObject the given business object * @param the specified fields that need to be included in the return map * @return the map of business object with its property names and values */ public static Map<String, Object> buildPropertyMap(Object object, List<String> keyFields) { DynaClass dynaClass = WrapDynaClass.createDynaClass(object.getClass()); DynaProperty[] properties = dynaClass.getDynaProperties(); Map<String, Object> propertyMap = new LinkedHashMap<String, Object>(); for (DynaProperty property : properties) { String propertyName = property.getName(); if (PropertyUtils.isReadable(object, propertyName) && keyFields.contains(propertyName)) { try { Object propertyValue = PropertyUtils.getProperty(object, propertyName); if (propertyValue != null && !StringUtils.isEmpty(propertyValue.toString())) { propertyMap.put(propertyName, propertyValue); } } catch (Exception e) { LOG.info(e); } } } return propertyMap; } /** * concat the specified properties of the given object as a string * * @param object the given object * @param the specified fields that need to be included in the return string * @return the specified properties of the given object as a string */ public static String concatPropertyAsString(Object object, List<String> keyFields) { StringBuilder propertyAsString = new StringBuilder(); for (String field : keyFields) { if (PropertyUtils.isReadable(object, field)) { try { propertyAsString.append(PropertyUtils.getProperty(object, field)); } catch (Exception e) { LOG.error(e); } } } return propertyAsString.toString(); } /** * Tokenize the input line with the given deliminator and populate the given object with values of the tokens * * @param targetObject the target object * @param line the input line * @param delim the deminator that separates the fields in the given line * @param keyFields the specified fields */ public static void convertLineToBusinessObject(Object targetObject, String line, String delim, List<String> keyFields) { String[] tokens = StringUtils.split(line, delim); ObjectUtil.buildObject(targetObject, tokens, keyFields); } /** * Tokenize the input line with the given deliminator and populate the given object with values of the tokens * * @param targetObject the target object * @param line the input line * @param delim the deminator that separates the fields in the given line * @param keyFields the specified fields */ public static void convertLineToBusinessObject(Object targetObject, String line, String delim, String fieldNames) { List<String> tokens = split(line, delim); List<String> keyFields = Arrays.asList(StringUtils.split(fieldNames, delim)); ObjectUtil.buildObject(targetObject, tokens.toArray(), keyFields); } /** * Tokenize the input line with the given deliminator and store the tokens in a list * * @param line the input line * @param delim the deminator that separates the fields in the given line * @return a list of tokens */ public static List<String> split(String line, String delim) { List<String> tokens = new ArrayList<String>(); int currentPosition = 0; for (int step = 0; step < line.length(); step++) { int previousPosition = currentPosition; currentPosition = StringUtils.indexOf(line, delim, currentPosition); currentPosition = currentPosition == -1 ? line.length() - 1 : currentPosition; String sub = line.substring(previousPosition, currentPosition); tokens.add(sub); // don't trim the string currentPosition += delim.length(); if (currentPosition >= line.length()) { break; } } return tokens; } /** * Tokenize the input line with the given deliminator and populate the given object with values of the tokens * * @param targetObject the target object * @param line the input line * @param delim the deminator that separates the fields in the given line * @param keyFields the specified fields */ public static void convertLineToBusinessObject(Object targetObject, String line, int[] fieldLength, List<String> keyFields) { String[] tokens = new String[fieldLength.length]; int currentPosition = 0; for (int i = 0; i < fieldLength.length; i++) { currentPosition = i <= 0 ? 0 : fieldLength[i - 1] + currentPosition; tokens[i] = StringUtils.mid(line, currentPosition, fieldLength[i]).trim(); } ObjectUtil.buildObject(targetObject, tokens, keyFields); } /** * Populate a business object with the given properities and information * * @param businessOjbject the business object to be populated * @param properties the given properties * @param propertyKey the property keys in the properties * @param fieldNames the names of the fields to be populated * @param deliminator the deliminator that separates the values to be used in a string */ public static void populateBusinessObject(Object businessOjbject, Properties properties, String propertyKey, String fieldNames, String deliminator) { String data = properties.getProperty(propertyKey); ObjectUtil.convertLineToBusinessObject(businessOjbject, data, deliminator, fieldNames); } /** * Populate a business object with the given properities and information * * @param businessOjbject the business object to be populated * @param properties the given properties * @param propertyKey the property keys in the properties * @param fieldNames the names of the fields to be populated * @param deliminator the deliminator that separates the values to be used in a string */ public static void populateBusinessObject(Object businessOjbject, Properties properties, String propertyKey, int[] fieldLength, List<String> keyFields) { String data = properties.getProperty(propertyKey); ObjectUtil.convertLineToBusinessObject(businessOjbject, data, fieldLength, keyFields); } /** * determine if the source object has a field with null as its value * * @param sourceObject the source object */ public static boolean hasNullValueField(Object sourceObject) { DynaClass dynaClass = WrapDynaClass.createDynaClass(sourceObject.getClass()); DynaProperty[] properties = dynaClass.getDynaProperties(); for (DynaProperty property : properties) { String propertyName = property.getName(); if (PropertyUtils.isReadable(sourceObject, propertyName)) { try { Object propertyValue = PropertyUtils.getProperty(sourceObject, propertyName); if (propertyValue == null) { return true; } } catch (Exception e) { LOG.info(e); return false; } } } return false; } /** * get the types of the nested attributes starting at the given class * * @param clazz the given class * @param nestedAttribute the nested attributes of the given class * @return a map that contains the types of the nested attributes and the attribute names */ public static Map<Class<?>, String> getNestedAttributeTypes(Class<?> clazz, String nestedAttribute) { List<String> attributes = Arrays.asList(StringUtils.split(nestedAttribute, PropertyUtils.NESTED_DELIM)); Map<Class<?>, String> nestedAttributes = new HashMap<Class<?>, String>(); Class<?> currentClass = clazz; for (String propertyName : attributes) { String methodName = "get" + StringUtils.capitalize(propertyName); try { Method method = currentClass.getMethod(methodName); currentClass = method.getReturnType(); nestedAttributes.put(currentClass, propertyName); } catch (Exception e) { LOG.info(e); break; } } return nestedAttributes; } }