001: /*
002: * Created on Nov 25, 2005
003: */
004: package uk.org.ponder.reflect;
005:
006: import java.util.Collection;
007: import java.util.Enumeration;
008: import java.util.Iterator;
009: import java.util.Map;
010:
011: import uk.org.ponder.arrayutil.ArrayUtil;
012: import uk.org.ponder.saxalizer.AccessMethod;
013: import uk.org.ponder.saxalizer.MethodAnalyser;
014: import uk.org.ponder.saxalizer.SAXAccessMethod;
015: import uk.org.ponder.saxalizer.SAXalizerMappingContext;
016: import uk.org.ponder.util.EnumerationConverter;
017:
018: /**
019: * A moderately capable deep cloner of beans. Will not cope with anything
020: * unusual like multidimensional arrays, and assumes that every non-leaf object
021: * encountered in the tree is default-constructible (including collections).
022: * Also assumes that the object graph supplied is a tree.
023: * <p>
024: * Never uses Object.clone() but rather direct introspection and type inference,
025: * so should run relatively like a rocket compared to most things out there
026: * (once FastClass is integrated) - 60ns*nprops per bean rather than
027: * 900ns+16ns*nprops. (Cost per property - 1xFastClass, probably no constructs
028: * (since immutable), perhaps 3 function calls, a loop it and a hashmap lookup).
029: * <p>
030: * Anyone capable of rewriting this code into the corresponding BCEL visitor is
031: * strongly invited to do so.
032: *
033: * @author Antranig Basman (antranig@caret.cam.ac.uk)
034: *
035: */
036:
037: public class DeepBeanCloner {
038: private SAXalizerMappingContext mappingcontext;
039: private ReflectiveCache reflectivecache;
040:
041: public void setMappingContext(SAXalizerMappingContext mappingcontext) {
042: this .mappingcontext = mappingcontext;
043: }
044:
045: public void setReflectiveCache(ReflectiveCache reflectivecache) {
046: this .reflectivecache = reflectivecache;
047: }
048:
049: public ReflectiveCache getReflectiveCache() {
050: return reflectivecache;
051: }
052:
053: /** Simply produce another object of the same type as the argument * */
054: public Object emptyClone(Object toclone) {
055: return reflectivecache.construct(toclone.getClass());
056: }
057:
058: public Object cloneBean(Object toclone) {
059: return cloneBean(toclone, null);
060: }
061:
062: public boolean areEqual(Object left, Object right) {
063: if (left == null)
064: return right == null;
065: if (right == null)
066: return false;
067: if (left.getClass() != right.getClass())
068: return false;
069: Class objclass = left.getClass();
070: if (mappingcontext.saxleafparser.isLeafType(objclass)
071: || Collection.class.isAssignableFrom(objclass)) {
072: return left.equals(right);
073: } else {
074: MethodAnalyser ma = mappingcontext.getAnalyser(objclass);
075: for (int i = 0; i < ma.allgetters.length; ++i) {
076: AccessMethod sam = ma.allgetters[i];
077: if (!sam.canGet() || !sam.canSet())
078: continue;
079: Object leftchild = sam.getChildObject(left);
080: Object rightchild = sam.getChildObject(right);
081: boolean equals = areEqual(leftchild, rightchild);
082: if (!equals)
083: return false;
084: }
085: }
086: return true;
087: }
088:
089: /**
090: * Copies the source object onto the destination - must not be a leaf or
091: * container object.
092: */
093: public void copyTrunk(Object source, Object target,
094: String[] exceptions) {
095: MethodAnalyser ma = mappingcontext.getAnalyser(source
096: .getClass());
097: for (int i = 0; i < ma.allgetters.length; ++i) {
098: SAXAccessMethod sam = ma.allgetters[i];
099: if (!sam.canGet() || !sam.canSet())
100: continue;
101: if (exceptions != null
102: && ArrayUtil.contains(exceptions, sam.tagname))
103: continue;
104: if (sam.isexactsetter) {
105: Enumeration childenum = EnumerationConverter
106: .getEnumeration(sam.getChildObject(source));
107: while (childenum.hasMoreElements()) {
108: Object child = childenum.nextElement();
109: Object clonechild = cloneBean(child, null);
110: sam.setChildObject(target, clonechild);
111: }
112: } else {
113: Object child = sam.getChildObject(source);
114: if (child != null) {
115: Object clonechild = cloneBean(child, null);
116: sam.setChildObject(target, clonechild);
117: }
118: }
119: }
120: }
121:
122: /**
123: * Produce a deep clone of the supplied object
124: *
125: * @param toclone The object to be cloned
126: * @param A list of property names to be excluded from the top-level bean.
127: */
128: public Object cloneBean(Object toclone, String[] exceptions) {
129: if (toclone == null)
130: return null;
131: Class objclass = toclone.getClass();
132: Object cloned = null;
133: if (mappingcontext.saxleafparser.isLeafType(objclass)) {
134: cloned = mappingcontext.saxleafparser.copy(toclone);
135: } else if (toclone instanceof Collection) {
136: Collection coll = (Collection) toclone;
137: Collection togo = (Collection) reflectivecache
138: .construct(objclass);
139: for (Iterator colit = coll.iterator(); colit.hasNext();) {
140: Object next = colit.next();
141: cloned = cloneBean(next, null);
142: togo.add(cloned);
143: }
144: cloned = togo;
145: } else if (toclone instanceof Map) {
146: Map map = (Map) toclone;
147: Map togo = (Map) reflectivecache.construct(objclass);
148: for (Iterator keyit = map.values().iterator(); keyit
149: .hasNext();) {
150: Object key = keyit.next();
151: Object value = map.get(key);
152: Object clonekey = cloneBean(key, null);
153: Object clonevalue = cloneBean(value, null);
154: togo.put(clonekey, clonevalue);
155: }
156: cloned = togo;
157: } else if (toclone.getClass().isArray()) {
158: Object[] array = (Object[]) toclone;
159: Object[] clonedarr = (Object[]) ReflectUtils
160: .instantiateContainer(objclass, array.length,
161: reflectivecache);
162: for (int i = 0; i < array.length; ++i) {
163: clonedarr[i] = cloneBean(array[i], null);
164: }
165: cloned = clonedarr;
166: } else {
167: // The general case. A trunk object with a good lot of properties.
168: cloned = reflectivecache.construct(objclass);
169: copyTrunk(toclone, cloned, exceptions);
170: }
171: return cloned;
172: }
173: }
|