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 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 &&       // ensure it has getter and setter methods
137                         !_ignoreProperties.contains(pds[i].getName()) && // is not in the list of properties to ignore
138                         pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
139                         pReadMethod.getParameterTypes().length==0) {     // filter getter methods that take parameters
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                 // NOTHING SPECIAL TO DO HERE, THEY ARE INMUTABLE
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     // THIS IS NOT NEEDED, BASIC TYPES  ARE INMUTABLE, TUCU 20040710
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