/*
* 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.rometools.rome.feed.impl;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.rome.feed.CopyFrom;
/**
* @author Alejandro Abdelnur
*/
public class CopyFromHelper {
private static final Logger LOG = LoggerFactory.getLogger(CopyFromHelper.class);
private static final Set<Class<?>> BASIC_TYPES = new HashSet<Class<?>>();
private static final Object[] NO_PARAMS = new Object[0];
private final Class<? extends CopyFrom> beanInterfaceClass;
private final Map<String, Class<?>> baseInterfaceMap; // ENTRIES(propertyName,interface.class)
private final Map<Class<? extends CopyFrom>, Class<?>> baseImplMap; // ENTRIES(interface.class,implementation.class)
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);
BASIC_TYPES.add(Date.class);
}
public CopyFromHelper(final Class<? extends CopyFrom> beanInterfaceClass, final Map<String, Class<?>> basePropInterfaceMap,
final Map<Class<? extends CopyFrom>, Class<?>> basePropClassImplMap) {
this.beanInterfaceClass = beanInterfaceClass;
baseInterfaceMap = basePropInterfaceMap;
baseImplMap = basePropClassImplMap;
}
public void copy(final Object target, final Object source) {
try {
final List<PropertyDescriptor> propertyDescriptors = BeanIntrospector.getPropertyDescriptorsWithGettersAndSetters(beanInterfaceClass);
for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
final String propertyName = propertyDescriptor.getName();
if (baseInterfaceMap.containsKey(propertyName)) {
final Method getter = propertyDescriptor.getReadMethod();
// only copies properties defined as copyFrom-able
Object value = getter.invoke(source, NO_PARAMS);
if (value != null) {
final Method setter = propertyDescriptor.getWriteMethod();
final Class<?> baseInterface = baseInterfaceMap.get(propertyName);
value = doCopy(value, baseInterface);
setter.invoke(target, new Object[] { value });
}
}
}
} catch (final Exception e) {
LOG.error("Error while copying object", e);
throw new RuntimeException("Could not do a copyFrom " + e, e);
}
}
private CopyFrom createInstance(final Class<? extends CopyFrom> interfaceClass) throws Exception {
if (baseImplMap.get(interfaceClass) == null) {
return null;
} else {
return (CopyFrom) baseImplMap.get(interfaceClass).newInstance();
}
}
@SuppressWarnings("unchecked")
private <T> T doCopy(T value, final Class<?> baseInterface) throws Exception {
if (value != null) {
final Class<?> vClass = value.getClass();
if (vClass.isArray()) {
value = (T) this.<Object> doCopyArray((Object[]) value, baseInterface);
} else if (value instanceof Collection) {
value = (T) this.<Object> doCopyCollection((Collection<Object>) value, baseInterface);
} else if (value instanceof Map) {
value = (T) this.<Object, Object> doCopyMap((Map<Object, Object>) value, baseInterface);
} else if (isBasicType(vClass)) {
// value = value; // nothing to do here
if (value instanceof Date) { // because Date it is not inmutable
value = (T) ((Date) value).clone();
}
} else { // it goes CopyFrom
if (value instanceof CopyFrom) {
final CopyFrom source = (CopyFrom) value;
CopyFrom target = createInstance(source.getInterface());
if (target == null) {
target = (CopyFrom) value.getClass().newInstance();
}
target.copyFrom(source);
value = (T) target;
} else {
throw new Exception("unsupported class for 'copyFrom' " + value.getClass());
}
}
}
return value;
}
private <T> T[] doCopyArray(final T[] array, final Class<?> baseInterface) throws Exception {
final Class<?> elementClass = array.getClass().getComponentType();
final int length = Array.getLength(array);
@SuppressWarnings("unchecked")
final T[] newArray = (T[]) Array.newInstance(elementClass, length);
for (int i = 0; i < length; i++) {
final Object element = doCopy(Array.get(array, i), baseInterface);
Array.set(newArray, i, element);
}
return newArray;
}
private <T> Collection<T> doCopyCollection(final Collection<T> collection, final Class<?> baseInterface) throws Exception {
// expecting a set or a list
final Collection<T> newCollection;
if (collection instanceof Set) {
newCollection = new LinkedHashSet<T>();
} else {
newCollection = new ArrayList<T>();
}
for (final T item : collection) {
final T copied = doCopy(item, baseInterface);
newCollection.add(copied);
}
return newCollection;
}
private <S, T> Map<S, T> doCopyMap(final Map<S, T> map, final Class<?> baseInterface) throws Exception {
final Map<S, T> newMap = new HashMap<S, T>();
for (final Entry<S, T> entry : map.entrySet()) {
// TODO mustn't the key be copied too?
final T copiedValue = doCopy(entry.getValue(), baseInterface);
newMap.put(entry.getKey(), copiedValue);
}
return newMap;
}
private boolean isBasicType(final Class<?> type) {
return BASIC_TYPES.contains(type);
}
}