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    }