/*
* Copyright 2004 Sun Microsystems, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.sun.syndication.feed.impl;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* Provides deep <b>Bean</b> clonning support.
* <p>
* It works on all read/write properties, recursively. It support all primitive types, Strings, Collections,
* Cloneable objects and multi-dimensional arrays of any of them.
* <p>
* @author Alejandro Abdelnur
*
*/
public class CloneableBean implements Serializable, Cloneable {
private static final Class[] NO_PARAMS_DEF = new Class[0];
private static final Object[] NO_PARAMS = new Object[0];
private Object _obj;
private Set _ignoreProperties;
/**
* Default constructor.
* <p>
* To be used by classes extending CloneableBean only.
* <p>
*
*/
protected CloneableBean() {
_obj = this;
}
/**
* Creates a CloneableBean to be used in a delegation pattern.
* <p>
* For example:
* <p>
* <code>
* public class Foo implements Cloneable {
* private CloneableBean _cloneableBean;
*
* public Foo() {
* _cloneableBean = new CloneableBean(this);
* }
*
* public Object clone() throws CloneNotSupportedException {
* return _cloneableBean.beanClone();
* }
*
* }
* </code>
* <p>
* @param obj object bean to clone.
*
*/
public CloneableBean(Object obj) {
this(obj,null);
}
/**
* Creates a CloneableBean to be used in a delegation pattern.
* <p>
* The property names in the ignoreProperties Set will not be copied into
* the cloned instance. This is useful for cases where the Bean has convenience
* properties (properties that are actually references to other properties or
* properties of properties). For example SyndFeed and SyndEntry beans have
* convenience properties, publishedDate, author, copyright and categories all
* of them mapped to properties in the DC Module.
* <p>
* @param obj object bean to clone.
* @param ignoreProperties properties to ignore when cloning.
*
*/
public CloneableBean(Object obj,Set ignoreProperties) {
_obj = obj;
_ignoreProperties = (ignoreProperties!=null) ? ignoreProperties : Collections.EMPTY_SET;
}
/**
* Makes a deep bean clone of the object.
* <p>
* To be used by classes extending CloneableBean. Although it works also for classes using
* CloneableBean in a delegation pattern, for correctness those classes should use the
* @see #beanClone() beanClone method.
* <p>
* @return a clone of the object bean.
* @throws CloneNotSupportedException thrown if the object bean could not be cloned.
*
*/
public Object clone() throws CloneNotSupportedException {
return beanClone();
}
/**
* Makes a deep bean clone of the object passed in the constructor.
* <p>
* To be used by classes using CloneableBean in a delegation pattern,
* @see #CloneableBean(Object) constructor.
*
* @return a clone of the object bean.
* @throws CloneNotSupportedException thrown if the object bean could not be cloned.
*
*/
public Object beanClone() throws CloneNotSupportedException {
Object clonedBean;
try {
clonedBean = _obj.getClass().newInstance();
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(_obj.getClass());
if (pds!=null) {
for (int i=0;i<pds.length;i++) {
Method pReadMethod = pds[i].getReadMethod();
Method pWriteMethod = pds[i].getWriteMethod();
if (pReadMethod!=null && pWriteMethod!=null && // ensure it has getter and setter methods
!_ignoreProperties.contains(pds[i].getName()) && // is not in the list of properties to ignore
pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
pReadMethod.getParameterTypes().length==0) { // filter getter methods that take parameters
Object value = pReadMethod.invoke(_obj,NO_PARAMS);
if (value!=null) {
value = doClone(value);
pWriteMethod.invoke(clonedBean,new Object[]{value});
}
}
}
}
}
catch (CloneNotSupportedException cnsEx) {
throw cnsEx;
}
catch (Exception ex) {
System.out.println(ex);
ex.printStackTrace(System.out);
throw new CloneNotSupportedException("Cannot clone a "+_obj.getClass()+" object");
}
return clonedBean;
}
private Object doClone(Object value) throws Exception {
if (value!=null) {
Class vClass = value.getClass();
if (vClass.isArray()) {
value = cloneArray(value);
}
else
if (value instanceof Collection) {
value = cloneCollection((Collection)value);
}
else
if (value instanceof Map) {
value = cloneMap((Map)value);
}
else
if (isBasicType(vClass)) {
// NOTHING SPECIAL TO DO HERE, THEY ARE INMUTABLE
}
else
if (value instanceof Cloneable) {
Method cloneMethod = vClass.getMethod("clone",NO_PARAMS_DEF);
if (Modifier.isPublic(cloneMethod.getModifiers())) {
value = cloneMethod.invoke(value,NO_PARAMS);
}
else {
throw new CloneNotSupportedException("Cannot clone a "+value.getClass()+" object, clone() is not public");
}
}
else {
throw new CloneNotSupportedException("Cannot clone a "+vClass.getName()+" object");
}
}
return value;
}
private Object cloneArray(Object array) throws Exception {
Class elementClass = array.getClass().getComponentType();
int length = Array.getLength(array);
Object newArray = Array.newInstance(elementClass,length);
for (int i=0;i<length;i++) {
Object element = doClone(Array.get(array,i));
Array.set(newArray,i,element);
}
return newArray;
}
private Object cloneCollection(Collection collection) throws Exception {
Class mClass = collection.getClass();
Collection newColl = (Collection) mClass.newInstance();
Iterator i = collection.iterator();
while (i.hasNext()) {
Object element = doClone(i.next());
newColl.add(element);
}
return newColl;
}
private Object cloneMap(Map map) throws Exception {
Class mClass = map.getClass();
Map newMap = (Map) mClass.newInstance();
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
Object key = doClone(entry.getKey());
Object value = doClone(entry.getValue());
newMap.put(key,value);
}
return newMap;
}
private static final Set BASIC_TYPES = new HashSet();
static {
BASIC_TYPES.add(Boolean.class);
BASIC_TYPES.add(Byte.class);
BASIC_TYPES.add(Character.class);
BASIC_TYPES.add(Double.class);
BASIC_TYPES.add(Float.class);
BASIC_TYPES.add(Integer.class);
BASIC_TYPES.add(Long.class);
BASIC_TYPES.add(Short.class);
BASIC_TYPES.add(String.class);
}
private static final Map CONSTRUCTOR_BASIC_TYPES = new HashMap();
static {
CONSTRUCTOR_BASIC_TYPES.put(Boolean.class,new Class[]{Boolean.TYPE});
CONSTRUCTOR_BASIC_TYPES.put(Byte.class,new Class[]{Byte.TYPE});
CONSTRUCTOR_BASIC_TYPES.put(Character.class,new Class[]{Character.TYPE});
CONSTRUCTOR_BASIC_TYPES.put(Double.class,new Class[]{Double.TYPE});
CONSTRUCTOR_BASIC_TYPES.put(Float.class,new Class[]{Float.TYPE});
CONSTRUCTOR_BASIC_TYPES.put(Integer.class,new Class[]{Integer.TYPE});
CONSTRUCTOR_BASIC_TYPES.put(Long.class,new Class[]{Long.TYPE});
CONSTRUCTOR_BASIC_TYPES.put(Short.class,new Class[]{Short.TYPE});
CONSTRUCTOR_BASIC_TYPES.put(String.class,new Class[]{String.class});
}
private boolean isBasicType(Class vClass) {
return BASIC_TYPES.contains(vClass);
}
// THIS IS NOT NEEDED, BASIC TYPES ARE INMUTABLE, TUCU 20040710
/**
private Object cloneBasicType(Object value) throws Exception {
Class pClass = value.getClass();
Class[] defType = (Class[]) CONSTRUCTOR_BASIC_TYPES.get(pClass);
Constructor cons = pClass.getDeclaredConstructor(defType);
value = cons.newInstance(new Object[]{value});
return value;
}
**/
}