/*
* Copyright 2001-2007 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.
* Distributed under the terms of either:
* - the common development and distribution license (CDDL), v1.0; or
* - the GNU Lesser General Public License, v2.1 or later
* $Id: ObjectUtils.java 3669 2007-02-26 13:51:23Z 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: 3669 $
* @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.
*
* 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)) {
throw new RuntimeException("Failed to clone " + object + " (" + object.getClass().getName() + ").");
}
try {
Method method = object.getClass().getMethod("clone", (Class[]) null);
method.setAccessible(true);
T clone = (T) method.invoke(object, (Object[]) null);
return clone;
} catch (Exception e) {
throw new RuntimeException("Failed to clone " + object + ".", e);
}
}
/**
* 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 = null;
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 = null;
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;
}
}
}