1 4 package com.tc.util; 5 6 import com.tc.exception.TCRuntimeException; 7 8 import java.lang.reflect.Array ; 9 import java.lang.reflect.InvocationTargetException ; 10 import java.lang.reflect.Method ; 11 import java.util.Collection ; 12 import java.util.Date ; 13 import java.util.IdentityHashMap ; 14 import java.util.Iterator ; 15 import java.util.Map ; 16 17 56 public class DeepCloner { 57 58 68 private static final Class [] IMMUTABLE_VALUE_TYPES = new Class [] { Boolean .class, Character .class, Byte .class, 69 Short .class, Integer .class, Long .class, Float .class, Double .class, String .class, Date .class }; 70 71 private static final Object DUMMY_VALUE = new Object (); 72 73 private final Map clones; 74 private final Map pendingClones; 75 76 private DeepCloner() { 77 this.clones = new IdentityHashMap (); 78 this.pendingClones = new IdentityHashMap (); 79 } 80 81 100 public static Object deepClone(Object source) { 101 if (source == null) return null; 102 return new DeepCloner().subClone(source); 103 } 104 105 122 public Object subClone(Object source) { 123 if (source == null) return null; 124 125 Object out = this.clones.get(source); 126 127 if (out == null) { 128 Assert 129 .eval( 130 "You're trying to clone an object that's currently being cloned -- in other words, someone tried " 131 + "to clone some object A, and, somewhere in the set of calls that generated, some other object went back " 132 + "and tried to clone A again. (Any kind of cycle in an object graph will typically generate this problem.) " 133 + "To fix this, you need to break the chain: call the DeepCloner.setClone(Object, Object) method somewhere " 134 + "*after* the original object A is created, but *before* the call to some other object's deepClone() method " 135 + "that leads back to A. (This is often in the 'cloning constructor' of A.) See the JavaDoc for " 136 + "DeepCloner.subClone() for details.", !this.pendingClones.containsKey(source)); 137 this.pendingClones.put(source, DUMMY_VALUE); 138 139 out = doDeepClone(source); 140 this.clones.put(source, out); 141 142 this.pendingClones.remove(source); 143 } 144 145 return out; 146 } 147 148 197 public void setClone(Object original, Object clone) { 198 Assert.eval("You're trying to set the clone of an object that isn't currently being cloned. Perhaps you passed " + 199 "your arguments backwards?", this.pendingClones.containsKey(original)); 200 this.clones.put(original, clone); 201 } 202 203 private Object doDeepClone(Object source) { 204 if (source instanceof DeepCloneable) { 205 Object out = ((DeepCloneable) source).deepClone(this); 206 Assert 207 .eval("You've returned an object from deepClone() that is of a DIFFERENT class than the source object; " 208 + "this almost certainly means you forgot to override deepClone() on a subclass. You should fix this.", 209 out.getClass().equals(source.getClass())); 210 return out; 211 } else if (isValueType(source.getClass())) { 212 return source; 213 } else if (source instanceof Collection && source instanceof Cloneable ) { 214 return deepClone((Collection ) source); 215 } else if (source instanceof Map && source instanceof Cloneable ) { 216 return deepClone((Map ) source); 217 } else if (source instanceof Object []) { 218 return deepClone((Object []) source); 219 } else if (source instanceof boolean[]) { 220 return deepClone((boolean[]) source); 221 } else if (source instanceof char[]) { 222 return deepClone((char[]) source); 223 } else if (source instanceof byte[]) { 224 return deepClone((byte[]) source); 225 } else if (source instanceof short[]) { 226 return deepClone((short[]) source); 227 } else if (source instanceof int[]) { 228 return deepClone((int[]) source); 229 } else if (source instanceof long[]) { 230 return deepClone((long[]) source); 231 } else if (source instanceof float[]) { 232 return deepClone((float[]) source); 233 } else if (source instanceof double[]) { 234 return deepClone((double[]) source); 235 } else { 236 throw new UnsupportedOperationException ("You can't deep-clone " + source + ", a " + source.getClass().getName() 237 + "; it does not implement DeepCloneable, and is not one of the " 238 + "predefined 'known' classes."); 239 } 240 } 241 242 private boolean isValueType(Class c) { 243 for (int i = 0; i < IMMUTABLE_VALUE_TYPES.length; ++i) { 244 if (IMMUTABLE_VALUE_TYPES[i].isAssignableFrom(c)) return true; 245 } 246 247 return false; 248 } 249 250 private Object deepClone(Collection source) { 251 Collection out = (Collection ) doShallowClone(source); 252 out.clear(); 253 254 Iterator iter = source.iterator(); 255 while (iter.hasNext()) { 256 out.add(subClone(iter.next())); 257 } 258 259 return out; 260 } 261 262 private Object deepClone(Map source) { 263 Map out = (Map ) doShallowClone(source); 264 out.clear(); 265 266 Iterator iter = source.entrySet().iterator(); 267 while (iter.hasNext()) { 268 Map.Entry entry = (Map.Entry ) iter.next(); 269 out.put(subClone(entry.getKey()), subClone(entry.getValue())); 270 } 271 272 return out; 273 } 274 275 private Object deepClone(Object [] source) { 276 Object [] out = (Object []) Array.newInstance(source.getClass().getComponentType(), source.length); 277 for (int i = 0; i < out.length; ++i) { 278 out[i] = subClone(source[i]); 279 } 280 return out; 281 } 282 283 private Object deepClone(boolean[] source) { 284 boolean[] out = new boolean[source.length]; 285 System.arraycopy(source, 0, out, 0, source.length); 286 return out; 287 } 288 289 private Object deepClone(char[] source) { 290 char[] out = new char[source.length]; 291 System.arraycopy(source, 0, out, 0, source.length); 292 return out; 293 } 294 295 private Object deepClone(short[] source) { 296 short[] out = new short[source.length]; 297 System.arraycopy(source, 0, out, 0, source.length); 298 return out; 299 } 300 301 private Object deepClone(int[] source) { 302 int[] out = new int[source.length]; 303 System.arraycopy(source, 0, out, 0, source.length); 304 return out; 305 } 306 307 private Object deepClone(byte[] source) { 308 byte[] out = new byte[source.length]; 309 System.arraycopy(source, 0, out, 0, source.length); 310 return out; 311 } 312 313 private Object deepClone(long[] source) { 314 long[] out = new long[source.length]; 315 System.arraycopy(source, 0, out, 0, source.length); 316 return out; 317 } 318 319 private Object deepClone(float[] source) { 320 float[] out = new float[source.length]; 321 System.arraycopy(source, 0, out, 0, source.length); 322 return out; 323 } 324 325 private Object deepClone(double[] source) { 326 double[] out = new double[source.length]; 327 System.arraycopy(source, 0, out, 0, source.length); 328 return out; 329 } 330 331 private Object doShallowClone(Object source) { 332 Class c = source.getClass(); 333 Method cloneMethod; 334 try { 335 cloneMethod = c.getMethod("clone", null); 336 return cloneMethod.invoke(source, null); 337 } catch (SecurityException se) { 338 throw new TCRuntimeException("Unexpected exception when trying to clone a " + source.getClass(), se); 339 } catch (NoSuchMethodException nsme) { 340 throw new TCRuntimeException("Unexpected exception when trying to clone a " + source.getClass(), nsme); 341 } catch (IllegalArgumentException iae) { 342 throw new TCRuntimeException("Unexpected exception when trying to clone a " + source.getClass(), iae); 343 } catch (IllegalAccessException iae) { 344 throw new TCRuntimeException("Unexpected exception when trying to clone a " + source.getClass(), iae); 345 } catch (InvocationTargetException ite) { 346 throw new TCRuntimeException("Unexpected exception when trying to clone a " + source.getClass(), ite); 347 } 348 } 349 350 } | Popular Tags |