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