View Javadoc

1   /*
2    * Copyright 2004 Sun Microsystems, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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 &&       // ensure it has getter and setter methods
124                         pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
125                         pReadMethod.getParameterTypes().length==0) {     // filter getter methods that take parameters
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