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