001: /*
002: * Copyright 2004 Sun Microsystems, Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: *
016: */
017: package com.sun.syndication.feed.impl;
018:
019: import java.beans.PropertyDescriptor;
020: import java.io.Serializable;
021: import java.lang.reflect.Array;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.util.*;
025:
026: /**
027: * Provides deep <b>Bean</b> clonning support.
028: * <p>
029: * It works on all read/write properties, recursively. It support all primitive types, Strings, Collections,
030: * Cloneable objects and multi-dimensional arrays of any of them.
031: * <p>
032: * @author Alejandro Abdelnur
033: *
034: */
035: public class CloneableBean implements Serializable, Cloneable {
036:
037: private static final Class[] NO_PARAMS_DEF = new Class[0];
038: private static final Object[] NO_PARAMS = new Object[0];
039:
040: private Object _obj;
041: private Set _ignoreProperties;
042:
043: /**
044: * Default constructor.
045: * <p>
046: * To be used by classes extending CloneableBean only.
047: * <p>
048: *
049: */
050: protected CloneableBean() {
051: _obj = this ;
052: }
053:
054: /**
055: * Creates a CloneableBean to be used in a delegation pattern.
056: * <p>
057: * For example:
058: * <p>
059: * <code>
060: * public class Foo implements Cloneable {
061: * private CloneableBean _cloneableBean;
062: *
063: * public Foo() {
064: * _cloneableBean = new CloneableBean(this);
065: * }
066: *
067: * public Object clone() throws CloneNotSupportedException {
068: * return _cloneableBean.beanClone();
069: * }
070: *
071: * }
072: * </code>
073: * <p>
074: * @param obj object bean to clone.
075: *
076: */
077: public CloneableBean(Object obj) {
078: this (obj, null);
079: }
080:
081: /**
082: * Creates a CloneableBean to be used in a delegation pattern.
083: * <p>
084: * The property names in the ignoreProperties Set will not be copied into
085: * the cloned instance. This is useful for cases where the Bean has convenience
086: * properties (properties that are actually references to other properties or
087: * properties of properties). For example SyndFeed and SyndEntry beans have
088: * convenience properties, publishedDate, author, copyright and categories all
089: * of them mapped to properties in the DC Module.
090: * <p>
091: * @param obj object bean to clone.
092: * @param ignoreProperties properties to ignore when cloning.
093: *
094: */
095: public CloneableBean(Object obj, Set ignoreProperties) {
096: _obj = obj;
097: _ignoreProperties = (ignoreProperties != null) ? ignoreProperties
098: : Collections.EMPTY_SET;
099: }
100:
101: /**
102: * Makes a deep bean clone of the object.
103: * <p>
104: * To be used by classes extending CloneableBean. Although it works also for classes using
105: * CloneableBean in a delegation pattern, for correctness those classes should use the
106: * @see #beanClone() beanClone method.
107: * <p>
108: * @return a clone of the object bean.
109: * @throws CloneNotSupportedException thrown if the object bean could not be cloned.
110: *
111: */
112: public Object clone() throws CloneNotSupportedException {
113: return beanClone();
114: }
115:
116: /**
117: * Makes a deep bean clone of the object passed in the constructor.
118: * <p>
119: * To be used by classes using CloneableBean in a delegation pattern,
120: * @see #CloneableBean(Object) constructor.
121: *
122: * @return a clone of the object bean.
123: * @throws CloneNotSupportedException thrown if the object bean could not be cloned.
124: *
125: */
126: public Object beanClone() throws CloneNotSupportedException {
127: Object clonedBean;
128: try {
129: clonedBean = _obj.getClass().newInstance();
130: PropertyDescriptor[] pds = BeanIntrospector
131: .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
137: && pWriteMethod != null
138: && // ensure it has getter and setter methods
139: !_ignoreProperties.contains(pds[i]
140: .getName())
141: && // is not in the list of properties to ignore
142: pReadMethod.getDeclaringClass() != Object.class
143: && // filter Object.class getter methods
144: pReadMethod.getParameterTypes().length == 0) { // filter getter methods that take parameters
145: Object value = pReadMethod.invoke(_obj,
146: NO_PARAMS);
147: if (value != null) {
148: value = doClone(value);
149: pWriteMethod.invoke(clonedBean,
150: new Object[] { value });
151: }
152: }
153: }
154: }
155: } catch (CloneNotSupportedException cnsEx) {
156: throw cnsEx;
157: } catch (Exception ex) {
158: System.out.println(ex);
159: ex.printStackTrace(System.out);
160: throw new CloneNotSupportedException("Cannot clone a "
161: + _obj.getClass() + " object");
162: }
163: return clonedBean;
164: }
165:
166: private Object doClone(Object value) throws Exception {
167: if (value != null) {
168: Class vClass = value.getClass();
169: if (vClass.isArray()) {
170: value = cloneArray(value);
171: } else if (value instanceof Collection) {
172: value = cloneCollection((Collection) value);
173: } else if (value instanceof Map) {
174: value = cloneMap((Map) value);
175: } else if (isBasicType(vClass)) {
176: // NOTHING SPECIAL TO DO HERE, THEY ARE INMUTABLE
177: } else if (value instanceof Cloneable) {
178: Method cloneMethod = vClass.getMethod("clone",
179: NO_PARAMS_DEF);
180: if (Modifier.isPublic(cloneMethod.getModifiers())) {
181: value = cloneMethod.invoke(value, NO_PARAMS);
182: } else {
183: throw new CloneNotSupportedException(
184: "Cannot clone a " + value.getClass()
185: + " object, clone() is not public");
186: }
187: } else {
188: throw new CloneNotSupportedException("Cannot clone a "
189: + 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)
207: throws Exception {
208: Class mClass = collection.getClass();
209: Collection newColl = (Collection) mClass.newInstance();
210: Iterator i = collection.iterator();
211: while (i.hasNext()) {
212: Object element = doClone(i.next());
213: newColl.add(element);
214: }
215: return newColl;
216: }
217:
218: private Object cloneMap(Map map) throws Exception {
219: Class mClass = map.getClass();
220: Map newMap = (Map) mClass.newInstance();
221: Iterator entries = map.entrySet().iterator();
222: while (entries.hasNext()) {
223: Map.Entry entry = (Map.Entry) entries.next();
224: Object key = doClone(entry.getKey());
225: Object value = doClone(entry.getValue());
226: newMap.put(key, value);
227: }
228: return newMap;
229: }
230:
231: private static final Set BASIC_TYPES = new HashSet();
232:
233: static {
234: BASIC_TYPES.add(Boolean.class);
235: BASIC_TYPES.add(Byte.class);
236: BASIC_TYPES.add(Character.class);
237: BASIC_TYPES.add(Double.class);
238: BASIC_TYPES.add(Float.class);
239: BASIC_TYPES.add(Integer.class);
240: BASIC_TYPES.add(Long.class);
241: BASIC_TYPES.add(Short.class);
242: BASIC_TYPES.add(String.class);
243: }
244:
245: private static final Map CONSTRUCTOR_BASIC_TYPES = new HashMap();
246:
247: static {
248: CONSTRUCTOR_BASIC_TYPES.put(Boolean.class,
249: new Class[] { Boolean.TYPE });
250: CONSTRUCTOR_BASIC_TYPES.put(Byte.class,
251: new Class[] { Byte.TYPE });
252: CONSTRUCTOR_BASIC_TYPES.put(Character.class,
253: new Class[] { Character.TYPE });
254: CONSTRUCTOR_BASIC_TYPES.put(Double.class,
255: new Class[] { Double.TYPE });
256: CONSTRUCTOR_BASIC_TYPES.put(Float.class,
257: new Class[] { Float.TYPE });
258: CONSTRUCTOR_BASIC_TYPES.put(Integer.class,
259: new Class[] { Integer.TYPE });
260: CONSTRUCTOR_BASIC_TYPES.put(Long.class,
261: new Class[] { Long.TYPE });
262: CONSTRUCTOR_BASIC_TYPES.put(Short.class,
263: new Class[] { Short.TYPE });
264: CONSTRUCTOR_BASIC_TYPES.put(String.class,
265: new Class[] { String.class });
266: }
267:
268: private boolean isBasicType(Class vClass) {
269: return BASIC_TYPES.contains(vClass);
270: }
271:
272: // THIS IS NOT NEEDED, BASIC TYPES ARE INMUTABLE, TUCU 20040710
273: /**
274: private Object cloneBasicType(Object value) throws Exception {
275: Class pClass = value.getClass();
276: Class[] defType = (Class[]) CONSTRUCTOR_BASIC_TYPES.get(pClass);
277: Constructor cons = pClass.getDeclaredConstructor(defType);
278: value = cons.newInstance(new Object[]{value});
279: return value;
280: }
281: **/
282:
283: }
|