/* * Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com) * Parts of this class are Copyright Thomas McGlynn 1997-1998 as part of his * Java FITS reader. * Licensed under the Apache License, Version 2.0 (the "License") * $Id: ObjectUtils.java 3918 2008-04-14 17:35:35Z gbevin $ */ package com.uwyn.rife.tools; /** * General purpose class containing common <code>Object</code> manipulation * methods. * * @author Geert Bevin (gbevin[remove] at uwyn dot com) * @author Thomas McGlynn * @version $Revision: 3918 $ * @since 1.0 */ import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.logging.Logger; public class ObjectUtils { /** * Clone an Object if possible. * <p/> * This method returns an Object which is a clone of the * input object. It checks if the method implements the * Cloneable interface and then uses reflection to invoke * the clone method. * * @param object The object to be cloned. * @return <code>null</code> if the cloning failed; or * <p/> * the cloned <code>Object</code> instance. */ public static <T> T genericClone(T object) { if (object == null) { return null; } // if this is a pure object and not an extending class, just don't clone // it since it's most probably a thread monitor lock if (Object.class == object.getClass()) { return object; } // strings can't be cloned, but they are immutable, so skip over it if (String.class == object.getClass()) { return object; } // exceptions can't be cloned, but they are simply indicative, so skip over it if (object instanceof Throwable) { return object; } // stringbuffers can't be cloned, so just create a new one if (StringBuffer.class == object.getClass()) { return (T)new StringBuffer(object.toString()); } // stringbuilders can't be cloned, so just create a new one if (StringBuilder.class == object.getClass()) { return (T)new StringBuilder(object.toString()); } // handle the reference counterparts of the primitives that are // not cloneable in the jdk, they are immutable if (object instanceof Number || object instanceof Boolean || object instanceof Character) { return object; } if (!(object instanceof Cloneable)) { return null; } try { Method method = object.getClass().getMethod("clone", (Class[])null); method.setAccessible(true); return (T)method.invoke(object, (Object[])null); } catch (Exception e) { return null; } } /** * Try to create a deep clone of the provides object. This handles arrays, * collections and maps. If the class in not a supported standard JDK * collection type the <code>genericClone</code> will be used instead. * * @param object The object to be copied. */ public static <T> T deepClone(T object) throws CloneNotSupportedException { if (null == object) { return null; } String classname = object.getClass().getName(); // check if it's an array if ('[' == classname.charAt(0)) { // handle 1 dimensional primitive arrays if (classname.charAt(1) != '[' && classname.charAt(1) != 'L') { switch (classname.charAt(1)) { case 'B': return (T)((byte[])object).clone(); case 'Z': return (T)((boolean[])object).clone(); case 'C': return (T)((char[])object).clone(); case 'S': return (T)((short[])object).clone(); case 'I': return (T)((int[])object).clone(); case 'J': return (T)((long[])object).clone(); case 'F': return (T)((float[])object).clone(); case 'D': return (T)((double[])object).clone(); ///CLOVER:OFF default: Logger.getLogger("com.uwyn.rife.tools").severe("Unknown primitive array class: " + classname); return null; ///CLOVER:ON } } // get the base type and the dimension count of the array int dimension_count = 1; while (classname.charAt(dimension_count) == '[') { dimension_count += 1; } Class baseClass; if (classname.charAt(dimension_count) != 'L') { baseClass = getBaseClass(object); } else { try { baseClass = Class.forName(classname.substring(dimension_count + 1, classname.length() - 1)); } ///CLOVER:OFF catch (ClassNotFoundException e) { Logger.getLogger("com.uwyn.rife.tools").severe("Internal error: class definition inconsistency: " + classname); return null; } ///CLOVER:ON } // instantiate the array but make all but the first dimension 0. int[] dimensions = new int[dimension_count]; dimensions[0] = Array.getLength(object); for (int i = 1; i < dimension_count; i += 1) { dimensions[i] = 0; } T copy = (T)Array.newInstance(baseClass, dimensions); // now fill in the next level down by recursion. for (int i = 0; i < dimensions[0]; i += 1) { Array.set(copy, i, deepClone(Array.get(object, i))); } return copy; } // handle cloneable collections else if (object instanceof Collection && object instanceof Cloneable) { Collection collection = (Collection)object; // instantiate the new collection and clear it Collection copy = (Collection)ObjectUtils.genericClone(object); copy.clear(); // clone all the values in the collection individually for (Object element : collection) { copy.add(deepClone(element)); } return (T)copy; } // handle cloneable maps else if (object instanceof Map && object instanceof Cloneable) { Map map = (Map)object; // instantiate the new map and clear it Map copy = (Map)ObjectUtils.genericClone(object); copy.clear(); // now clone all the keys and values of the entries Iterator collection_it = map.entrySet().iterator(); Map.Entry entry; while (collection_it.hasNext()) { entry = (Map.Entry)collection_it.next(); copy.put(deepClone(entry.getKey()), deepClone(entry.getValue())); } return (T)copy; } // use the generic clone method else { T copy = ObjectUtils.genericClone(object); if (null == copy) { throw new CloneNotSupportedException(object.getClass().getName()); } return copy; } } /** * This routine returns the base class of an object. This is just * the class of the object for non-arrays. * * @param object The object whose base class you want to retrieve. */ public static Class getBaseClass(Object object) { if (object == null) { return Void.TYPE; } String className = object.getClass().getName(); // skip forward over the array dimensions int dims = 0; while (className.charAt(dims) == '[') { dims += 1; } // if there were no array dimensions, just return the class of the // provided object if (dims == 0) { return object.getClass(); } switch (className.charAt(dims)) { // handle the boxed primitives case 'Z': return Boolean.TYPE; case 'B': return Byte.TYPE; case 'S': return Short.TYPE; case 'C': return Character.TYPE; case 'I': return Integer.TYPE; case 'J': return Long.TYPE; case 'F': return Float.TYPE; case 'D': return Double.TYPE; // look up the class of another reference type case 'L': try { return Class.forName(className.substring(dims + 1, className.length() - 1)); } catch (ClassNotFoundException e) { return null; } default: return null; } } }