001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.module.purap.util;
017:
018: import java.lang.reflect.Field;
019: import java.lang.reflect.InvocationTargetException;
020: import java.lang.reflect.Modifier;
021: import java.util.ArrayList;
022: import java.util.Collection;
023: import java.util.HashMap;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Set;
029:
030: import org.kuali.core.bo.BusinessObject;
031: import org.kuali.core.util.ObjectUtils;
032: import org.kuali.core.util.TypedArrayList;
033: import org.kuali.core.web.format.FormatException;
034: import org.kuali.module.purap.PurapConstants;
035: import org.kuali.rice.KNSServiceLocator;
036:
037: /**
038: * Purap Object Utils.
039: * Similar to the nervous system ObjectUtils this class contains methods to reflectively set and get values on
040: * BusinessObjects that are passed in.
041: */
042: public class PurApObjectUtils {
043: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
044: .getLogger(PurApObjectUtils.class);
045:
046: /**
047: *
048: * Populates a class using a base class to determine fields
049: *
050: * @param base the class to determine what fields to copy
051: * @param src the source class
052: * @param target the target class
053: * @param supplementalUncopyable a list of fields to never copy
054: */
055: public static void populateFromBaseClass(Class base,
056: BusinessObject src, BusinessObject target,
057: Map supplementalUncopyable) {
058: List<String> fieldNames = new ArrayList<String>();
059: Field[] fields = base.getDeclaredFields();
060:
061: for (Field field : fields) {
062: if (!Modifier.isTransient(field.getModifiers())) {
063: fieldNames.add(field.getName());
064: } else {
065: LOG.warn("field " + field.getName()
066: + " is transient, skipping ");
067: }
068: }
069: int counter = 0;
070: for (String fieldName : fieldNames) {
071: if ((isProcessableField(base, fieldName,
072: PurapConstants.KNOWN_UNCOPYABLE_FIELDS))
073: && (isProcessableField(base, fieldName,
074: supplementalUncopyable))) {
075: attemptCopyOfFieldName(base.getName(), fieldName, src,
076: target, supplementalUncopyable);
077: counter++;
078: }
079: }
080: LOG.debug("Population complete for " + counter
081: + " fields out of a total of " + fieldNames.size()
082: + " potential fields in object with base class '"
083: + base + "'");
084: }
085:
086: /**
087: *
088: * True if a field is processable
089: *
090: * @param baseClass the base class
091: * @param fieldName the field name to detrmine if processable
092: * @param excludedFieldNames field names to exclude
093: * @return true if a field is processable
094: */
095: private static boolean isProcessableField(Class baseClass,
096: String fieldName, Map excludedFieldNames) {
097: if (excludedFieldNames.containsKey(fieldName)) {
098: Class potentialClassName = (Class) excludedFieldNames
099: .get(fieldName);
100: if ((ObjectUtils.isNull(potentialClassName))
101: || (potentialClassName.equals(baseClass))) {
102: return false;
103: }
104: }
105: return true;
106: }
107:
108: /**
109: *
110: * Attempts to copy a field
111: * @param baseClass the base class
112: * @param fieldName the field name to determine if processable
113: * @param sourceObject source object
114: * @param targetObject target object
115: * @param supplementalUncopyable
116: */
117: private static void attemptCopyOfFieldName(String baseClassName,
118: String fieldName, BusinessObject sourceObject,
119: BusinessObject targetObject, Map supplementalUncopyable) {
120: try {
121:
122: Object propertyValue = ObjectUtils.getPropertyValue(
123: sourceObject, fieldName);
124: if ((ObjectUtils.isNotNull(propertyValue))
125: && (Collection.class.isAssignableFrom(propertyValue
126: .getClass()))) {
127: LOG.debug("attempting to copy collection field '"
128: + fieldName + "' using base class '"
129: + baseClassName
130: + "' and property value class '"
131: + propertyValue.getClass() + "'");
132: copyCollection(fieldName, targetObject, propertyValue,
133: supplementalUncopyable);
134: } else {
135: String propertyValueClass = (ObjectUtils
136: .isNotNull(propertyValue)) ? propertyValue
137: .getClass().toString() : "(null)";
138: LOG.debug("attempting to set field '" + fieldName
139: + "' using base class '" + baseClassName
140: + "' and property value class '"
141: + propertyValueClass + "'");
142: ObjectUtils.setObjectProperty(targetObject, fieldName,
143: propertyValue);
144: }
145: } catch (Exception e) {
146: // purposefully skip for now
147: // (I wish objectUtils getPropertyValue threw named errors instead of runtime) so I could
148: // selectively skip
149: LOG.debug("couldn't set field '" + fieldName
150: + "' using base class '" + baseClassName
151: + "' due to exception with class name '"
152: + e.getClass().getName() + "'", e);
153: }
154: }
155:
156: /**
157: *
158: * Copies a collection
159: *
160: * @param fieldName field to copy
161: * @param targetObject the object of the collection
162: * @param propertyValue value to copy
163: * @param supplementalUncopyable uncopyable fields
164: * @throws FormatException
165: * @throws IllegalAccessException
166: * @throws InvocationTargetException
167: * @throws NoSuchMethodException
168: */
169: private static void copyCollection(String fieldName,
170: BusinessObject targetObject, Object propertyValue,
171: Map supplementalUncopyable) throws FormatException,
172: IllegalAccessException, InvocationTargetException,
173: NoSuchMethodException {
174: Collection sourceList = (Collection) propertyValue;
175: Collection listToSet = null;
176:
177: // materialize collections
178: if (ObjectUtils.isNotNull(sourceList)) {
179: ObjectUtils.materializeObjects(sourceList);
180: }
181:
182: // TypedArrayList requires argument so handle differently than below
183: if (sourceList instanceof TypedArrayList) {
184: TypedArrayList typedArray = (TypedArrayList) sourceList;
185: LOG.debug("collection will be typed using class '"
186: + typedArray.getListObjectType() + "'");
187: try {
188: listToSet = new TypedArrayList(typedArray
189: .getListObjectType());
190: } catch (Exception e) {
191: LOG
192: .info(
193: "couldn't set class '"
194: + propertyValue.getClass()
195: + "' on collection... using TypedArrayList using ",
196: e);
197: listToSet = new ArrayList();
198: }
199: } else {
200: try {
201: listToSet = sourceList.getClass().newInstance();
202: } catch (Exception e) {
203: LOG.info("couldn't set class '"
204: + propertyValue.getClass()
205: + "' on collection..." + fieldName + " using "
206: + sourceList.getClass());
207: listToSet = new ArrayList();
208: }
209: }
210:
211: for (Iterator iterator = sourceList.iterator(); iterator
212: .hasNext();) {
213: BusinessObject sourceCollectionObject = (BusinessObject) iterator
214: .next();
215: LOG
216: .debug("attempting to copy collection member with class '"
217: + sourceCollectionObject.getClass() + "'");
218: BusinessObject targetCollectionObject = (BusinessObject) ObjectUtils
219: .createNewObjectFromClass(sourceCollectionObject
220: .getClass());
221: populateFromBaseWithSuper(sourceCollectionObject,
222: targetCollectionObject, supplementalUncopyable,
223: new HashSet<Class>());
224: // BusinessObject targetCollectionObject = (BusinessObject)ObjectUtils.deepCopy((Serializable)sourceCollectionObject);
225: Map pkMap = KNSServiceLocator.getPersistenceService()
226: .getPrimaryKeyFieldValues(targetCollectionObject);
227: Set<String> pkFields = pkMap.keySet();
228: for (String field : pkFields) {
229: ObjectUtils.setObjectProperty(targetCollectionObject,
230: field, null);
231: }
232: listToSet.add(targetCollectionObject);
233: }
234: ObjectUtils.setObjectProperty(targetObject, fieldName,
235: listToSet);
236: }
237:
238: /**
239: * Copies based on a class template it does not copy fields in Known Uncopyable Fields
240: *
241: * @param base the base class
242: * @param src source
243: * @param target target
244: */
245: public static void populateFromBaseClass(Class base,
246: BusinessObject src, BusinessObject target) {
247: populateFromBaseClass(base, src, target, new HashMap());
248: }
249:
250: /**
251: *
252: * Populates from a base class traversing up the object hierarchy.
253: *
254: * @param sourceObject object to copy from
255: * @param targetObject object to copy to
256: * @param supplementalUncopyableFieldNames fields to exclude
257: * @param classesToExclude classes to exclude
258: */
259: public static void populateFromBaseWithSuper(
260: BusinessObject sourceObject, BusinessObject targetObject,
261: Map supplementalUncopyableFieldNames,
262: Set<Class> classesToExclude) {
263: List<Class> classesToCopy = new ArrayList<Class>();
264: Class sourceObjectClass = sourceObject.getClass();
265: classesToCopy.add(sourceObjectClass);
266: while (sourceObjectClass.getSuperclass() != null) {
267: sourceObjectClass = sourceObjectClass.getSuperclass();
268: if (!classesToExclude.contains(sourceObjectClass)) {
269: classesToCopy.add(sourceObjectClass);
270: }
271: }
272: for (int i = (classesToCopy.size() - 1); i >= 0; i--) {
273: Class temp = classesToCopy.get(i);
274: populateFromBaseClass(temp, sourceObject, targetObject,
275: supplementalUncopyableFieldNames);
276: }
277: }
278:
279: // ***** following changes are to work around an ObjectUtils bug and are copied from ObjectUtils.java
280: /**
281: * Compares a business object with a List of BOs to determine if an object with the same key as the BO exists in the list. If it
282: * does, the item is returned.
283: *
284: * @param controlList - The list of items to check
285: * @param bo - The BO whose keys we are looking for in the controlList
286: */
287: public static BusinessObject retrieveObjectWithIdentitcalKey(
288: Collection controlList, BusinessObject bo) {
289: BusinessObject returnBo = null;
290:
291: for (Iterator i = controlList.iterator(); i.hasNext();) {
292: BusinessObject listBo = (BusinessObject) i.next();
293: if (equalByKeys(listBo, bo)) {
294: returnBo = listBo;
295: }
296: }
297:
298: return returnBo;
299: }
300:
301: /**
302: * Compares two business objects for equality of type and key values.
303: *
304: * @param bo1
305: * @param bo2
306: * @return boolean indicating whether the two objects are equal.
307: */
308: public static boolean equalByKeys(BusinessObject bo1,
309: BusinessObject bo2) {
310: boolean equal = true;
311:
312: if (bo1 == null && bo2 == null) {
313: equal = true;
314: } else if (bo1 == null || bo2 == null) {
315: equal = false;
316: } else if (!bo1.getClass().getName().equals(
317: bo2.getClass().getName())) {
318: equal = false;
319: } else {
320: Map bo1Keys = KNSServiceLocator.getPersistenceService()
321: .getPrimaryKeyFieldValues(bo1);
322: Map bo2Keys = KNSServiceLocator.getPersistenceService()
323: .getPrimaryKeyFieldValues(bo2);
324: for (Iterator iter = bo1Keys.keySet().iterator(); iter
325: .hasNext();) {
326: String keyName = (String) iter.next();
327: if (bo1Keys.get(keyName) != null
328: && bo2Keys.get(keyName) != null) {
329: if (!bo1Keys.get(keyName).toString().equals(
330: bo2Keys.get(keyName).toString())) {
331: equal = false;
332: }
333: } else {
334: // CHANGE FOR PurapOjbCollectionHelper change if one is null we are likely looking at a new object (sequence) which is definitely
335: // not equal
336: equal = false;
337: }
338: }
339: }
340:
341: return equal;
342: }
343: // ***** END copied from ObjectUtils.java changes
344: }
|