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