1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.sun.syndication.common;
18
19 import java.beans.BeanInfo;
20 import java.beans.Introspector;
21 import java.beans.PropertyDescriptor;
22 import java.lang.reflect.Array;
23 import java.lang.reflect.Constructor;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Modifier;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.Set;
32 import java.io.Serializable;
33
34 /***
35 * Provides deep <b>Bean</b> clonning support.
36 * <p>
37 * It works on all read/write properties, recursively. It support all primitive types, Strings, Collections,
38 * Cloneable objects and multi-dimensional arrays of any of them.
39 * <p>
40 * @author Alejandro Abdelnur
41 *
42 */
43 public class CloneableBean implements Serializable, Cloneable {
44
45 private static final Class[] NO_PARAMS_DEF = new Class[0];
46 private static final Object[] NO_PARAMS = new Object[0];
47
48 private Object _obj;
49
50 /***
51 * Default constructor.
52 * <p>
53 * To be used by classes extending CloneableBean only.
54 * <p>
55 *
56 */
57 protected CloneableBean() {
58 _obj = this;
59 }
60
61 /***
62 * Creates a CloneableBean to be used in a delegation pattern.
63 * <p>
64 * For example:
65 * <p>
66 * <code>
67 * public class Foo implements Cloneable {
68 * private CloneableBean _cloneableBean;
69 *
70 * public Foo() {
71 * _cloneableBean = new CloneableBean();
72 * }
73 *
74 * public Object clone() throws CloneNotSupportedException {
75 * return _cloneableBean.beanClone();
76 * }
77 *
78 * }
79 * </code>
80 * <p>
81 * @param obj object bean to clone.
82 *
83 */
84 public CloneableBean(Object obj) {
85 _obj = obj;
86 }
87
88 /***
89 * Makes a deep bean clone of the object.
90 * <p>
91 * To be used by classes extending CloneableBean. Although it works also for classes using
92 * CloneableBean in a delegation pattern, for correctness those classes should use the
93 * @see #beanClone() beanClone method.
94 * <p>
95 * @return a clone of the object bean.
96 * @throws CloneNotSupportedException thrown if the object bean could not be cloned.
97 *
98 */
99 public Object clone() throws CloneNotSupportedException {
100 return beanClone();
101 }
102
103 /***
104 * Makes a deep bean clone of the object passed in the constructor.
105 * <p>
106 * To be used by classes using CloneableBean in a delegation pattern,
107 * @see #CloneableBean(Object) constructor.
108 *
109 * @return a clone of the object bean.
110 * @throws CloneNotSupportedException thrown if the object bean could not be cloned.
111 *
112 */
113 public Object beanClone() throws CloneNotSupportedException {
114 Object clonedBean;
115 try {
116 clonedBean = _obj.getClass().newInstance();
117 BeanInfo bi = Introspector.getBeanInfo(_obj.getClass());
118 PropertyDescriptor[] pds = bi.getPropertyDescriptors();
119 if (pds!=null) {
120 for (int i=0;i<pds.length;i++) {
121 Method pReadMethod = pds[i].getReadMethod();
122 Method pWriteMethod = pds[i].getWriteMethod();
123 if (pReadMethod!=null && pWriteMethod!=null &&
124 pReadMethod.getDeclaringClass()!=Object.class &&
125 pReadMethod.getParameterTypes().length==0) {
126 Object value = pReadMethod.invoke(_obj,NO_PARAMS);
127 if (value!=null) {
128 value = doClone(value);
129 pWriteMethod.invoke(clonedBean,new Object[]{value});
130 }
131 }
132 }
133 }
134 }
135 catch (CloneNotSupportedException cnsEx) {
136 throw cnsEx;
137 }
138 catch (Exception ex) {
139 System.out.println(ex);
140 ex.printStackTrace(System.out);
141 throw new CloneNotSupportedException("Cannot clone a "+_obj.getClass()+" object");
142 }
143 return clonedBean;
144 }
145
146 private Object doClone(Object value) throws Exception {
147 if (value!=null) {
148 Class vClass = value.getClass();
149 if (vClass.isArray()) {
150 value = cloneArray(value);
151 }
152 else
153 if (value instanceof Collection) {
154 value = cloneCollection((Collection)value);
155 }
156 else
157 if (value instanceof Map) {
158 value = cloneMap((Map)value);
159 }
160 else
161 if (isBasicType(vClass)) {
162 value = cloneBasicType(value);
163 }
164 else
165 if (value instanceof Cloneable) {
166 Method cloneMethod = vClass.getMethod("clone",NO_PARAMS_DEF);
167 if (Modifier.isPublic(cloneMethod.getModifiers())) {
168 value = cloneMethod.invoke(value,NO_PARAMS);
169 }
170 else {
171 throw new CloneNotSupportedException("Cannot clone a "+value.getClass()+" object, clone() is not public");
172 }
173 }
174 else {
175 throw new CloneNotSupportedException("Cannot clone a "+vClass.getName()+" object");
176 }
177 }
178 return value;
179 }
180
181 private Object cloneArray(Object array) throws Exception {
182 Class elementClass = array.getClass().getComponentType();
183 int length = Array.getLength(array);
184 Object newArray = Array.newInstance(elementClass,length);
185 for (int i=0;i<length;i++) {
186 Array.set(newArray,i,Array.get(array,i));
187 }
188 return newArray;
189 }
190
191 private Object cloneCollection(Collection collection) throws Exception {
192 Class mClass = collection.getClass();
193 Collection newColl = (Collection) mClass.newInstance();
194 Iterator i = collection.iterator();
195 while (i.hasNext()) {
196 Object element = doClone(i.next());
197 newColl.add(element);
198 }
199 return newColl;
200 }
201
202 private Object cloneMap(Map map) throws Exception {
203 Class mClass = map.getClass();
204 Map newMap = (Map) mClass.newInstance();
205 Iterator entries = map.entrySet().iterator();
206 while (entries.hasNext()) {
207 Map.Entry entry = (Map.Entry) entries.next();
208 Object key = doClone(entry.getKey());
209 Object value = doClone(entry.getValue());
210 newMap.put(key,value);
211 }
212 return newMap;
213 }
214
215 private static final Set BASIC_TYPES = new HashSet();
216
217 static {
218 BASIC_TYPES.add(Boolean.class);
219 BASIC_TYPES.add(Byte.class);
220 BASIC_TYPES.add(Character.class);
221 BASIC_TYPES.add(Double.class);
222 BASIC_TYPES.add(Float.class);
223 BASIC_TYPES.add(Integer.class);
224 BASIC_TYPES.add(Long.class);
225 BASIC_TYPES.add(Short.class);
226 BASIC_TYPES.add(String.class);
227 }
228
229 private static final Map CONSTRUCTOR_BASIC_TYPES = new HashMap();
230
231 static {
232 CONSTRUCTOR_BASIC_TYPES.put(Boolean.class,new Class[]{Boolean.TYPE});
233 CONSTRUCTOR_BASIC_TYPES.put(Byte.class,new Class[]{Byte.TYPE});
234 CONSTRUCTOR_BASIC_TYPES.put(Character.class,new Class[]{Character.TYPE});
235 CONSTRUCTOR_BASIC_TYPES.put(Double.class,new Class[]{Double.TYPE});
236 CONSTRUCTOR_BASIC_TYPES.put(Float.class,new Class[]{Float.TYPE});
237 CONSTRUCTOR_BASIC_TYPES.put(Integer.class,new Class[]{Integer.TYPE});
238 CONSTRUCTOR_BASIC_TYPES.put(Long.class,new Class[]{Long.TYPE});
239 CONSTRUCTOR_BASIC_TYPES.put(Short.class,new Class[]{Short.TYPE});
240 CONSTRUCTOR_BASIC_TYPES.put(String.class,new Class[]{String.class});
241 }
242
243 private boolean isBasicType(Class vClass) {
244 return BASIC_TYPES.contains(vClass);
245 }
246
247 private Object cloneBasicType(Object value) throws Exception {
248 Class pClass = value.getClass();
249 Class[] defType = (Class[]) CONSTRUCTOR_BASIC_TYPES.get(pClass);
250 Constructor cons = pClass.getDeclaredConstructor(defType);
251 value = cons.newInstance(new Object[]{value});
252 return value;
253 }
254
255 }
256