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 }