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}