001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Parts of this class are Copyright Thomas McGlynn 1997-1998 as part of his
004: * Java FITS reader.
005: * Distributed under the terms of either:
006: * - the common development and distribution license (CDDL), v1.0; or
007: * - the GNU Lesser General Public License, v2.1 or later
008: * $Id: ObjectUtils.java 3669 2007-02-26 13:51:23Z gbevin $
009: */
010: package com.uwyn.rife.tools;
011:
012: /**
013: * General purpose class containing common <code>Object</code> manipulation
014: * methods.
015: *
016: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
017: * @author Thomas McGlynn
018: * @version $Revision: 3669 $
019: * @since 1.0
020: */
021: import java.lang.reflect.Array;
022: import java.lang.reflect.Method;
023: import java.util.Collection;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.logging.Logger;
027:
028: public class ObjectUtils {
029: /**
030: * Clone an Object if possible.
031: *
032: * This method returns an Object which is a clone of the
033: * input object. It checks if the method implements the
034: * Cloneable interface and then uses reflection to invoke
035: * the clone method.
036: *
037: * @param object The object to be cloned.
038: *
039: * @return <code>null</code> if the cloning failed; or
040: * <p>
041: * the cloned <code>Object</code> instance.
042: */
043: public static <T> T genericClone(T object) {
044: if (object == null) {
045: return null;
046: }
047:
048: // if this is a pure object and not an extending class, just don't clone
049: // it since it's most probably a thread monitor lock
050: if (Object.class == object.getClass()) {
051: return object;
052: }
053: // strings can't be cloned, but they are immutable, so skip over it
054: if (String.class == object.getClass()) {
055: return object;
056: }
057: // exceptions can't be cloned, but they are simply indicative, so skip over it
058: if (object instanceof Throwable) {
059: return object;
060: }
061: // stringbuffers can't be cloned, so just create a new one
062: if (StringBuffer.class == object.getClass()) {
063: return (T) new StringBuffer(object.toString());
064: }
065: // stringbuilders can't be cloned, so just create a new one
066: if (StringBuilder.class == object.getClass()) {
067: return (T) new StringBuilder(object.toString());
068: }
069: // handle the reference counterparts of the primitives that are
070: // not cloneable in the jdk, they are immutable
071: if (object instanceof Number || object instanceof Boolean
072: || object instanceof Character) {
073: return object;
074: }
075:
076: if (!(object instanceof Cloneable)) {
077: return null;
078: }
079:
080: try {
081: Method method = object.getClass().getMethod("clone",
082: (Class[]) null);
083: method.setAccessible(true);
084: return (T) method.invoke(object, (Object[]) null);
085: } catch (Exception e) {
086: return null;
087: }
088: }
089:
090: /**
091: * Try to create a deep clone of the provides object. This handles arrays,
092: * collections and maps. If the class in not a supported standard JDK
093: * collection type the <code>genericClone</code> will be used instead.
094: *
095: * @param object The object to be copied.
096: */
097: public static <T> T deepClone(T object)
098: throws CloneNotSupportedException {
099: if (null == object) {
100: return null;
101: }
102:
103: String classname = object.getClass().getName();
104:
105: // check if it's an array
106: if ('[' == classname.charAt(0)) {
107: // handle 1 dimensional primitive arrays
108: if (classname.charAt(1) != '['
109: && classname.charAt(1) != 'L') {
110: switch (classname.charAt(1)) {
111: case 'B':
112: return (T) ((byte[]) object).clone();
113: case 'Z':
114: return (T) ((boolean[]) object).clone();
115: case 'C':
116: return (T) ((char[]) object).clone();
117: case 'S':
118: return (T) ((short[]) object).clone();
119: case 'I':
120: return (T) ((int[]) object).clone();
121: case 'J':
122: return (T) ((long[]) object).clone();
123: case 'F':
124: return (T) ((float[]) object).clone();
125: case 'D':
126: return (T) ((double[]) object).clone();
127: ///CLOVER:OFF
128: default:
129: Logger.getLogger("com.uwyn.rife.tools").severe(
130: "Unknown primitive array class: "
131: + classname);
132: return null;
133: ///CLOVER:ON
134: }
135: }
136:
137: // get the base type and the dimension count of the array
138: int dimension_count = 1;
139: while (classname.charAt(dimension_count) == '[') {
140: dimension_count += 1;
141: }
142: Class baseClass = null;
143: if (classname.charAt(dimension_count) != 'L') {
144: baseClass = getBaseClass(object);
145: } else {
146: try {
147: baseClass = Class.forName(classname
148: .substring(dimension_count + 1, classname
149: .length() - 1));
150: }
151: ///CLOVER:OFF
152: catch (ClassNotFoundException e) {
153: Logger.getLogger("com.uwyn.rife.tools").severe(
154: "Internal error: class definition inconsistency: "
155: + classname);
156: return null;
157: }
158: ///CLOVER:ON
159: }
160:
161: // instantiate the array but make all but the first dimension 0.
162: int[] dimensions = new int[dimension_count];
163: dimensions[0] = Array.getLength(object);
164: for (int i = 1; i < dimension_count; i += 1) {
165: dimensions[i] = 0;
166: }
167: T copy = (T) Array.newInstance(baseClass, dimensions);
168:
169: // now fill in the next level down by recursion.
170: for (int i = 0; i < dimensions[0]; i += 1) {
171: Array.set(copy, i, deepClone(Array.get(object, i)));
172: }
173:
174: return copy;
175: }
176: // handle cloneable collections
177: else if (object instanceof Collection
178: && object instanceof Cloneable) {
179: Collection collection = (Collection) object;
180:
181: // instantiate the new collection and clear it
182: Collection copy = (Collection) ObjectUtils
183: .genericClone(object);
184: copy.clear();
185:
186: // clone all the values in the collection individually
187: for (Object element : collection) {
188: copy.add(deepClone(element));
189: }
190:
191: return (T) copy;
192: }
193: // handle cloneable maps
194: else if (object instanceof Map && object instanceof Cloneable) {
195: Map map = (Map) object;
196:
197: // instantiate the new map and clear it
198: Map copy = (Map) ObjectUtils.genericClone(object);
199: copy.clear();
200:
201: // now clone all the keys and values of the entries
202: Iterator collection_it = map.entrySet().iterator();
203: Map.Entry entry = null;
204: while (collection_it.hasNext()) {
205: entry = (Map.Entry) collection_it.next();
206: copy.put(deepClone(entry.getKey()), deepClone(entry
207: .getValue()));
208: }
209:
210: return (T) copy;
211: }
212: // use the generic clone method
213: else {
214: T copy = ObjectUtils.genericClone(object);
215: if (null == copy) {
216: throw new CloneNotSupportedException(object.getClass()
217: .getName());
218: }
219: return copy;
220: }
221: }
222:
223: /**
224: * This routine returns the base class of an object. This is just
225: * the class of the object for non-arrays.
226: *
227: * @param object The object whose base class you want to retrieve.
228: */
229: public static Class getBaseClass(Object object) {
230: if (object == null) {
231: return Void.TYPE;
232: }
233:
234: String className = object.getClass().getName();
235:
236: // skip forward over the array dimensions
237: int dims = 0;
238: while (className.charAt(dims) == '[') {
239: dims += 1;
240: }
241:
242: // if there were no array dimensions, just return the class of the
243: // provided object
244: if (dims == 0) {
245: return object.getClass();
246: }
247:
248: switch (className.charAt(dims)) {
249: // handle the boxed primitives
250: case 'Z':
251: return Boolean.TYPE;
252: case 'B':
253: return Byte.TYPE;
254: case 'S':
255: return Short.TYPE;
256: case 'C':
257: return Character.TYPE;
258: case 'I':
259: return Integer.TYPE;
260: case 'J':
261: return Long.TYPE;
262: case 'F':
263: return Float.TYPE;
264: case 'D':
265: return Double.TYPE;
266: // look up the class of another reference type
267: case 'L':
268: try {
269: return Class.forName(className.substring(dims + 1,
270: className.length() - 1));
271: } catch (ClassNotFoundException e) {
272: return null;
273: }
274: default:
275: return null;
276: }
277: }
278: }
|