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.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  
44      /***
45       * Default constructor.
46       * <p>
47       * To be used by classes extending CloneableBean only.
48       * <p>
49       *
50       */
51      protected CloneableBean() {
52          _obj = this;
53      }
54  
55      /***
56       * Creates a CloneableBean to be used in a delegation pattern.
57       * <p>
58       * For example:
59       * <p>
60       * <code>
61       *   public class Foo implements Cloneable {
62       *       private CloneableBean _cloneableBean;
63       *
64       *       public Foo() {
65       *           _cloneableBean = new CloneableBean();
66       *       }
67       *
68       *       public Object clone() throws CloneNotSupportedException {
69       *           return _cloneableBean.beanClone();
70       *       }
71       *
72       *   }
73       * </code>
74       * <p>
75       * @param obj object bean to clone.
76       *
77       */
78      public CloneableBean(Object obj) {
79          _obj = obj;
80      }
81  
82      /***
83       * Makes a deep bean clone of the object.
84       * <p>
85       * To be used by classes extending CloneableBean. Although it works also for classes using
86       * CloneableBean in a delegation pattern, for correctness those classes should use the
87       * @see #beanClone() beanClone method.
88       * <p>
89       * @return a clone of the object  bean.
90       * @throws CloneNotSupportedException thrown if the object bean could not be cloned.
91       *
92       */
93      public Object clone() throws CloneNotSupportedException {
94          return beanClone();
95      }
96  
97      /***
98       * Makes a deep bean clone of the object passed in the constructor.
99       * <p>
100      * To be used by classes using CloneableBean in a delegation pattern,
101      * @see #CloneableBean(Object) constructor.
102      *
103      * @return a clone of the object bean.
104      * @throws CloneNotSupportedException thrown if the object bean could not be cloned.
105      *
106      */
107     public Object beanClone() throws CloneNotSupportedException {
108         Object clonedBean;
109         try {
110             clonedBean = _obj.getClass().newInstance();
111             BeanInfo bi = Introspector.getBeanInfo(_obj.getClass());
112             PropertyDescriptor[] pds = bi.getPropertyDescriptors();
113             if (pds!=null) {
114                 for (int i=0;i<pds.length;i++) {
115                     Method pReadMethod = pds[i].getReadMethod();
116                     Method pWriteMethod = pds[i].getWriteMethod();
117                     if (pReadMethod!=null && pWriteMethod!=null &&       // ensure it has getter and setter methods
118                         pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
119                         pReadMethod.getParameterTypes().length==0) {     // filter getter methods that take parameters
120                         Object value = pReadMethod.invoke(_obj,NO_PARAMS);
121                         if (value!=null) {
122                             value = doClone(value);
123                             pWriteMethod.invoke(clonedBean,new Object[]{value});
124                         }
125                     }
126                 }
127             }
128         }
129         catch (CloneNotSupportedException cnsEx) {
130             throw cnsEx;
131         }
132         catch (Exception ex) {
133             System.out.println(ex);
134             ex.printStackTrace(System.out);
135             throw new CloneNotSupportedException("Cannot clone a "+_obj.getClass()+" object");
136         }
137         return clonedBean;
138     }
139 
140     private Object doClone(Object value) throws Exception {
141         if (value!=null) {
142             Class vClass = value.getClass();
143             if (vClass.isArray()) {
144                 value = cloneArray(value);
145             }
146             else
147             if (value instanceof Collection) {
148                 value = cloneCollection((Collection)value);
149             }
150             else
151             if (value instanceof Map) {
152                 value = cloneMap((Map)value);
153             }
154             else
155             if (isBasicType(vClass)) {
156                 // NOTHING SPECIAL TO DO HERE, THEY ARE INMUTABLE
157             }
158             else
159             if (value instanceof Cloneable) {
160                 Method cloneMethod = vClass.getMethod("clone",NO_PARAMS_DEF);
161                 if (Modifier.isPublic(cloneMethod.getModifiers())) {
162                    value = cloneMethod.invoke(value,NO_PARAMS);
163                 }
164                 else {
165                     throw new CloneNotSupportedException("Cannot clone a "+value.getClass()+" object, clone() is not public");
166                 }
167             }
168             else {
169                 throw new CloneNotSupportedException("Cannot clone a "+vClass.getName()+" object");
170             }
171         }
172         return value;
173     }
174 
175     private Object cloneArray(Object array) throws Exception {
176         Class elementClass = array.getClass().getComponentType();
177         int length = Array.getLength(array);
178         Object newArray = Array.newInstance(elementClass,length);
179         for (int i=0;i<length;i++) {
180             Object element = doClone(Array.get(array,i));
181             Array.set(newArray,i,element);
182         }
183         return newArray;
184     }
185 
186     private Object cloneCollection(Collection collection) throws Exception {
187         Class mClass = collection.getClass();
188         Collection newColl = (Collection) mClass.newInstance();
189         Iterator i = collection.iterator();
190         while (i.hasNext()) {
191             Object element = doClone(i.next());
192             newColl.add(element);
193         }
194         return newColl;
195     }
196 
197     private Object cloneMap(Map map) throws Exception {
198         Class mClass = map.getClass();
199         Map newMap = (Map) mClass.newInstance();
200         Iterator entries = map.entrySet().iterator();
201         while (entries.hasNext()) {
202             Map.Entry entry = (Map.Entry) entries.next();
203             Object key = doClone(entry.getKey());
204             Object value = doClone(entry.getValue());
205             newMap.put(key,value);
206         }
207         return newMap;
208     }
209 
210     private static final Set BASIC_TYPES = new HashSet();
211 
212     static {
213         BASIC_TYPES.add(Boolean.class);
214         BASIC_TYPES.add(Byte.class);
215         BASIC_TYPES.add(Character.class);
216         BASIC_TYPES.add(Double.class);
217         BASIC_TYPES.add(Float.class);
218         BASIC_TYPES.add(Integer.class);
219         BASIC_TYPES.add(Long.class);
220         BASIC_TYPES.add(Short.class);
221         BASIC_TYPES.add(String.class);
222     }
223 
224     private static final Map CONSTRUCTOR_BASIC_TYPES = new HashMap();
225 
226     static {
227         CONSTRUCTOR_BASIC_TYPES.put(Boolean.class,new Class[]{Boolean.TYPE});
228         CONSTRUCTOR_BASIC_TYPES.put(Byte.class,new Class[]{Byte.TYPE});
229         CONSTRUCTOR_BASIC_TYPES.put(Character.class,new Class[]{Character.TYPE});
230         CONSTRUCTOR_BASIC_TYPES.put(Double.class,new Class[]{Double.TYPE});
231         CONSTRUCTOR_BASIC_TYPES.put(Float.class,new Class[]{Float.TYPE});
232         CONSTRUCTOR_BASIC_TYPES.put(Integer.class,new Class[]{Integer.TYPE});
233         CONSTRUCTOR_BASIC_TYPES.put(Long.class,new Class[]{Long.TYPE});
234         CONSTRUCTOR_BASIC_TYPES.put(Short.class,new Class[]{Short.TYPE});
235         CONSTRUCTOR_BASIC_TYPES.put(String.class,new Class[]{String.class});
236     }
237 
238     private boolean isBasicType(Class vClass) {
239         return BASIC_TYPES.contains(vClass);
240     }
241 
242     // THIS IS NOT NEEDED, BASIC TYPES  ARE INMUTABLE, TUCU 20040710
243     /***
244     private Object cloneBasicType(Object value) throws Exception {
245         Class pClass = value.getClass();
246         Class[] defType = (Class[]) CONSTRUCTOR_BASIC_TYPES.get(pClass);
247         Constructor cons = pClass.getDeclaredConstructor(defType);
248         value = cons.newInstance(new Object[]{value});
249         return value;
250     }
251     **/
252 
253 }
254