/*
* Copyright 2007 The Apache Software Foundation.
*
* 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 net.sf.beanlib.provider.replicator;
import static net.sf.beanlib.utils.ClassUtils.immutable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.Blob;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.Map;
import net.sf.beanlib.BeanlibException;
import net.sf.beanlib.PropertyInfo;
import net.sf.beanlib.spi.BeanTransformerSpi;
import net.sf.beanlib.spi.CustomBeanTransformerSpi;
import org.apache.log4j.Logger;
/**
* A useful base class for the replicator implementations.
*
* @author Joe D. Velopar
*/
public abstract class ReplicatorTemplate {
protected final Logger log = Logger.getLogger(getClass());
private final BeanTransformerSpi beanTransformer;
protected ReplicatorTemplate(BeanTransformerSpi beanTransformer) {
this.beanTransformer = beanTransformer;
}
protected ReplicatorTemplate() {
this.beanTransformer = (BeanTransformerSpi) this;
}
protected final CustomBeanTransformerSpi getCustomerBeanTransformer() {
return beanTransformer.getCustomBeanTransformer();
}
/**
* Replicate the given from object, recursively if necessary. Currently a property is replicated if it is an
* instance of Collection, Map, Timestamp, Date, Blob, Hibernate entity, JavaBean, or an array.
*/
protected <T> T replicate(T from) {
if (from == null) {
return null;
}
try {
@SuppressWarnings("unchecked")
T ret = (T) replicate(from, from.getClass());
return ret;
} catch (SecurityException e) {
throw new BeanlibException(e);
}
}
/**
* Replicate the given from object, recursively if necessary, to an instance of the toClass. If, however, the from
* object is an enhanced object and the toClass is the same as the from object's class, the class of the un-enhanced
* object will be used as the toClass instead of the original toClass. Currently a property is replicated if it is
* an instance of Collection, Map, Timestamp, Date, Blob, Hibernate entity, JavaBean, or an array.
*/
protected <T> T replicate(final Object from, Class<T> toClass) throws SecurityException {
if (from == null) {
return toClass.isPrimitive() ? ImmutableReplicator.getDefaultPrimitiveValue(toClass) : null;
}
final Object unenhanced = unenhanceObject(from);
if (unenhanced != from && from.getClass() == toClass) { // The original to-class is an enhanced class:
// Use the un-enhanced class instead
@SuppressWarnings("unchecked")
Class<T> unenhancedClass = (Class<T>) unenhanced.getClass();
toClass = unenhancedClass;
}
if (containsTargetCloned(from)) { // already transformed
@SuppressWarnings("unchecked")
T to = (T) getTargetCloned(from);
return to;
}
// https://sourceforge.net/tracker/?func=detail&atid=745596&aid=3496862&group_id=140152
// Immutable e.g. String, Enum, primitvies, BigDecimal, etc.
if (immutable(toClass) || immutable(unenhanced.getClass())) {
return beanTransformer.getImmutableReplicatable().replicateImmutable(unenhanced, toClass);
}
// Collection
if (unenhanced instanceof Collection<?>) {
return beanTransformer.getCollectionReplicatable().replicateCollection((Collection<?>) unenhanced, toClass);
}
// Array
if (unenhanced.getClass().isArray()) {
return beanTransformer.getArrayReplicatable().replicateArray(unenhanced, toClass);
}
// Map
if (unenhanced instanceof Map<?, ?>) {
return beanTransformer.getMapReplicatable().replicateMap((Map<?, ?>) unenhanced, toClass);
}
// Date or Timestamp
if (unenhanced instanceof Date) {
return beanTransformer.getDateReplicatable().replicateDate((Date) unenhanced, toClass);
}
// Calendar
if (unenhanced instanceof Calendar) {
return beanTransformer.getCalendarReplicatable().replicateCalendar((Calendar) unenhanced, toClass);
}
// Blob
if (unenhanced instanceof Blob) {
return beanTransformer.getBlobReplicatable().replicateBlob((Blob) unenhanced, toClass);
}
// Other objects
// Note the original from object is expected to be passed along
return replicateByBeanReplicatable(from, toClass);
}
protected <T> T replicateByBeanReplicatable(Object from, Class<T> toClass) {
return beanTransformer.getBeanReplicatable().replicateBean(from, toClass);
}
/**
* Creates a target instance using the class of the given object.
*/
@SuppressWarnings("unchecked")
protected final <T> T createToInstance(T from) throws InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException {
return (T) createToInstance(from, from.getClass());
}
/**
* Creates a target instance from either the class of the given "from" object or the given toClass, giving priority
* to the one which is more specific whenever possible.
*/
protected <T> T createToInstance(Object from, Class<T> toClass) throws InstantiationException, IllegalAccessException, SecurityException,
NoSuchMethodException {
Class<T> targetClass = chooseClass(from.getClass(), toClass);
return newInstanceAsPrivileged(targetClass);
}
/**
* Chooses a target class from the given "from" and "to" classes, giving priority to the one which is more specific
* whenever possible.
*
* @param <T> target type
* @param fromClass from class
* @param toClass target class
* @return the target class if either the "from" class is not assignable to the "to" class, or if the from class is
* abstract; otherwise, returns the from class.
*/
protected final <T> Class<T> chooseClass(Class<?> fromClass, Class<T> toClass) {
if (!toClass.isAssignableFrom(fromClass) || Modifier.isAbstract(fromClass.getModifiers())) {
return toClass;
}
@SuppressWarnings("unchecked")
Class<T> ret = (Class<T>) fromClass;
return ret;
}
protected <T> T transform(Object in, Class<T> toClass, PropertyInfo propertyInfo) {
return beanTransformer.transform(in, toClass, propertyInfo);
}
protected void populateBean(Object fromMember, Object toMember) {
beanTransformer.getBeanPopulatorSpiFactory().newBeanPopulator(fromMember, toMember)
.initBeanPopulatorBaseConfig(beanTransformer.getBeanPopulatorBaseConfig()).initTransformer(beanTransformer).populate();
}
@SuppressWarnings("unchecked")
protected <T> T createToInstanceWithComparator(T from, Comparator<?> comparator) throws SecurityException, NoSuchMethodException {
return (T) createToInstanceWithComparator(from.getClass(), comparator);
}
private <T> T createToInstanceWithComparator(Class<T> toClass, Comparator<?> comparator) throws SecurityException, NoSuchMethodException {
return newInstanceWithComparatorAsPrivileged(toClass, comparator);
}
/**
* Creates a new instance of the given class via the no-arg constructor, invoking the constructor as a privileged
* action if it is protected or private.
*
* @param c given class
* @return a new instance of the given class via the no-arg constructor
*/
protected final <T> T newInstanceAsPrivileged(Class<T> c) throws SecurityException, NoSuchMethodException, InstantiationException,
IllegalAccessException {
final Constructor<?> constructor = c.getDeclaredConstructor();
if (Modifier.isPublic(constructor.getModifiers())) {
return c.newInstance();
}
return c.cast(AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
constructor.setAccessible(true);
try {
return constructor.newInstance();
} catch (IllegalAccessException e) {
throw new BeanlibException(e);
} catch (InvocationTargetException e) {
throw new BeanlibException(e.getTargetException());
} catch (InstantiationException e) {
throw new BeanlibException(e);
}
}
}));
}
private <T> T newInstanceWithComparatorAsPrivileged(Class<T> c, final Comparator<?> comparator) throws SecurityException, NoSuchMethodException {
final Constructor<?> constructor = c.getDeclaredConstructor(Comparator.class);
if (Modifier.isPublic(constructor.getModifiers())) {
try {
return c.cast(constructor.newInstance(comparator));
} catch (InstantiationException e) {
throw new BeanlibException(e);
} catch (IllegalAccessException e) {
throw new BeanlibException(e);
} catch (InvocationTargetException e) {
throw new BeanlibException(e.getTargetException());
}
}
return c.cast(AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
constructor.setAccessible(true);
try {
return constructor.newInstance(comparator);
} catch (IllegalAccessException e) {
throw new BeanlibException(e);
} catch (InvocationTargetException e) {
throw new BeanlibException(e.getTargetException());
} catch (InstantiationException e) {
throw new BeanlibException(e);
}
}
}));
}
protected final boolean containsTargetCloned(Object from) {
return beanTransformer.getClonedMap().containsKey(from);
}
protected final Object getTargetCloned(Object from) {
return beanTransformer.getClonedMap().get(from);
}
protected final Object putTargetCloned(Object from, Object to) {
return beanTransformer.getClonedMap().put(from, to);
}
/**
* Returns an equivalent object un-enhanced from the given object. By default, the input object is returned.
* Subclass must override this method to perform any such un-enhancement, if necessary.
*/
protected <T> T unenhanceObject(T object) {
return object;
}
}