/*
* 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;
}
}