/*
* Copyright 2005-2010 the original author or authors.
*
* 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 org.dozer.util;
import org.apache.commons.lang.StringUtils;
import org.dozer.MappingException;
import org.dozer.cache.Cache;
import org.dozer.classmap.ClassMap;
import org.dozer.classmap.Configuration;
import org.dozer.classmap.CopyByReferenceContainer;
import org.dozer.classmap.DozerClass;
import org.dozer.config.BeanContainer;
import org.dozer.converters.CustomConverterContainer;
import org.dozer.fieldmap.DozerField;
import org.dozer.fieldmap.FieldMap;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.dozer.util.DozerConstants.BASE_CLASS;
/**
* Internal class that provides various mapping utilities used throughout the code base. Only intended for internal use.
*
* @author garsombke.franz
* @author sullins.ben
* @author tierney.matt
*
*/
public final class MappingUtils {
private MappingUtils() {
}
public static String getClassNameWithoutPackage(Class<?> clazz) { // TODO Replace with Apache implementation
Package pckage = clazz.getPackage();
int pckageIndex = 0;
if (pckage != null) {
pckageIndex = pckage.getName().length() + 1;
}
return clazz.getName().substring(pckageIndex);
}
public static boolean isSupportedCollection(Class<?> aClass) {
return CollectionUtils.isCollection(aClass) || CollectionUtils.isArray(aClass);
}
public static boolean isSupportedMap(Class<?> aClass) {
return Map.class.isAssignableFrom(aClass);
}
public static void throwMappingException(Throwable e) throws MappingException {
if (e instanceof MappingException) {
// in this case we do not want to re-wrap an existing mapping exception
throw (MappingException) e;
} else if (e instanceof RuntimeException) {
// feature request 1561837. Dont wrap any runtime exceptions in a MappingException
throw (RuntimeException) e;
} else {
throw new MappingException(e);
}
}
public static void throwMappingException(String msg) throws MappingException {
throw new MappingException(msg);
}
public static void throwMappingException(String msg, Throwable cause) throws MappingException {
throw new MappingException(msg, cause);
}
public static boolean isBlankOrNull(String value) {
return value == null || value.trim().length() < 1;
}
public static Throwable getRootCause(Throwable ex) {
Throwable rootCause = ex;
while (rootCause.getCause() != null) {
rootCause = rootCause.getCause();
}
return rootCause;
}
public static String getMappedParentFieldKey(Object destObj, FieldMap destFieldMap) {
StringBuilder buf = new StringBuilder(100); // TODO Use IdentityHashMap instead of String concatenation
buf.append(System.identityHashCode(destObj));
buf.append(destFieldMap.getDestFieldName());
if ( destFieldMap.getDestFieldKey() != null ) {
buf.append("[").append( destFieldMap.getDestFieldKey() ).append("]");
}
return buf.toString();
}
public static Class<?> findCustomConverter(Cache converterByDestTypeCache, CustomConverterContainer customConverterContainer,
Class<?> srcClass, Class<?> destClass) {
if (customConverterContainer == null) {
return null;
}
return customConverterContainer.getCustomConverter(srcClass, destClass, converterByDestTypeCache);
}
public static Class<?> determineCustomConverter(FieldMap fieldMap, Cache converterByDestTypeCache,
CustomConverterContainer customConverterContainer, Class<?> srcClass, Class<?> destClass) {
if (customConverterContainer == null) {
return null;
}
// This method is messy. Just trying to isolate the junk into this one method instead of spread across the mapping
// processor until a better solution can be put into place
// For indexed mapping, need to use the actual class at index to determine the custom converter.
if (fieldMap != null && fieldMap.isDestFieldIndexed()) {
if (destClass.isArray()) {
destClass = destClass.getComponentType();
} else if (destClass.isAssignableFrom(Collection.class) && fieldMap.getDestHintContainer() != null
&& !fieldMap.getDestHintContainer().hasMoreThanOneHint()) {
// use hint when trying to find a custom converter
destClass = fieldMap.getDestHintContainer().getHint();
}
}
return findCustomConverter(converterByDestTypeCache, customConverterContainer, srcClass, destClass);
}
public static void reverseFields(FieldMap source, FieldMap reversed) {
DozerField destField = source.getSrcFieldCopy();
DozerField sourceField = source.getDestFieldCopy();
reversed.setDestField(destField);
reversed.setSrcField(sourceField);
reversed.setCustomConverter(source.getCustomConverter());
reversed.setCustomConverterId(source.getCustomConverterId());
reversed.setMapId(source.getMapId());
reversed.setRelationshipType(source.getRelationshipType());
reversed.setRemoveOrphans(source.isRemoveOrphans());
reversed.setSrcHintContainer(source.getDestHintContainer());
reversed.setDestHintContainer(source.getSrcHintContainer());
reversed.setSrcDeepIndexHintContainer(source.getDestDeepIndexHintContainer());
reversed.setDestDeepIndexHintContainer(source.getSrcDeepIndexHintContainer());
}
public static void reverseFields(ClassMap source, ClassMap destination) {
// reverse the fields
destination.setSrcClass(new DozerClass(source.getDestClassName(), source.getDestClassToMap(), source.getDestClassBeanFactory(),
source.getDestClassBeanFactoryId(), source.getDestClassMapGetMethod(), source.getDestClassMapSetMethod(),
source.getDestClass().getCreateMethod(),
source.isDestMapNull(), source.isDestMapEmptyString(), source.getDestClass().isAccesible()));
destination.setDestClass(new DozerClass(source.getSrcClassName(), source.getSrcClassToMap(), source.getSrcClassBeanFactory(),
source.getSrcClassBeanFactoryId(), source.getSrcClassMapGetMethod(), source.getSrcClassMapSetMethod(),
source.getSrcClass().getCreateMethod(),
source.isSrcMapNull(), source.isSrcMapEmptyString(), source.getSrcClass().isAccesible()));
destination.setType(source.getType());
destination.setWildcard(source.isWildcard());
destination.setTrimStrings(source.isTrimStrings());
destination.setDateFormat(source.getDateFormat());
destination.setRelationshipType(source.getRelationshipType());
destination.setStopOnErrors(source.isStopOnErrors());
destination.setAllowedExceptions(source.getAllowedExceptions());
destination.setSrcClassCreateMethod(source.getDestClassCreateMethod());
destination.setDestClassCreateMethod(source.getSrcClassCreateMethod());
if (StringUtils.isNotEmpty(source.getMapId())) {
destination.setMapId(source.getMapId());
}
}
public static Object getIndexedValue(Object collection, int index) {
Object result = null;
if (collection instanceof Object[]) {
Object[] x = (Object[]) collection;
if (index < x.length) {
return x[index];
}
} else if (collection instanceof Collection) {
Collection<?> x = (Collection<?>) collection;
if (index < x.size()) {
Iterator<?> iter = x.iterator();
for (int i = 0; i < index; i++) {
iter.next();
}
result = iter.next();
}
}
return result;
}
public static void applyGlobalCopyByReference(Configuration globalConfig, FieldMap fieldMap, ClassMap classMap) {
CopyByReferenceContainer copyByReferenceContainer = globalConfig.getCopyByReferences();
String destFieldTypeName = null;
Class<?> clazz = fieldMap.getDestFieldType(classMap.getDestClassToMap());
if (clazz != null) {
destFieldTypeName = clazz.getName();
}
if (copyByReferenceContainer.contains(destFieldTypeName) && !fieldMap.isCopyByReferenceOveridden()) {
fieldMap.setCopyByReference(true);
}
}
public static Class<?> loadClass(String name) {
BeanContainer container = BeanContainer.getInstance();
DozerClassLoader loader = container.getClassLoader();
return loader.loadClass(name);
}
public static Class<?> getRealClass(Class<?> clazz) {
BeanContainer container = BeanContainer.getInstance();
DozerProxyResolver proxyResolver = container.getProxyResolver();
return proxyResolver.getRealClass(clazz);
}
public static <T> T deProxy(T object) {
BeanContainer container = BeanContainer.getInstance();
DozerProxyResolver proxyResolver = container.getProxyResolver();
return proxyResolver.unenhanceObject(object);
}
public static boolean isProxy(Class<?> clazz) {
if (clazz.isInterface()) {
return false;
}
String className = clazz.getName();
return className.contains(DozerConstants.CGLIB_ID)
|| className.startsWith(DozerConstants.JAVASSIST_PACKAGE)
|| className.contains(DozerConstants.JAVASSIST_NAME);
}
public static Object prepareIndexedCollection(Class<?> collectionType, Object existingCollection, Object collectionEntry,
int index) {
Object result = null;
if (collectionType.isArray()) {
result = prepareIndexedArray(collectionType, existingCollection, collectionEntry, index);
} else if (Collection.class.isAssignableFrom(collectionType)) {
result = prepareIndexedCollectionType(collectionType, existingCollection, collectionEntry, index);
} else {
throwMappingException("Only types java.lang.Object[] and java.util.Collection are supported for indexed properties.");
}
return result;
}
public static boolean isDeepMapping(String mapping) {
return mapping != null && mapping.contains(DozerConstants.DEEP_FIELD_DELIMITER);
}
@SuppressWarnings("unchecked")
private static <T> T[] prepareIndexedArray(Class<T> collectionType, Object existingCollection, Object collectionEntry, int index) {
T[] result;
if (existingCollection == null) {
result = (T[]) Array.newInstance(collectionType.getComponentType(), index + 1);
} else {
int originalLenth = ((Object[]) existingCollection).length;
result = (T[]) Array.newInstance(collectionType.getComponentType(), Math.max(index + 1, originalLenth));
System.arraycopy(existingCollection, 0, result, 0, originalLenth);
}
result[index] = (T) collectionEntry;
return result;
}
@SuppressWarnings("unchecked")
private static Collection<?> prepareIndexedCollectionType(Class<?> collectionType, Object existingCollection,
Object collectionEntry, int index) {
Collection result = null;
//Instantiation of the new Collection: can be interface or implementation class
if (collectionType.isInterface()) {
if (collectionType.equals(Set.class)) {
result = new HashSet();
} else if (collectionType.equals(List.class)) {
result = new ArrayList();
} else {
throwMappingException("Only interface types java.util.Set and java.util.List are supported for java.util.Collection type indexed properties.");
}
} else {
//It is an implementation class of Collection
try {
result = (Collection) collectionType.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//Fill in old values in new Collection
if (existingCollection != null) {
result.addAll((Collection) existingCollection);
}
//Add the new value:
//For an ordered Collection
if (result instanceof List) {
while (result.size() < index + 1) {
result.add(null);
}
((List) result).set(index, collectionEntry);
}
//for an unordered Collection (index has no use here)
else {
result.add(collectionEntry);
}
return result;
}
/**
* Used to test if both {@code srcFieldClass} and {@code destFieldType} are enum.
* @param srcFieldClass the source field to be tested.
* @param destFieldType the destination field to be tested.
* @return {@code true} if and only if current running JRE is 1.5 or above, and both
* {@code srcFieldClass} and {@code destFieldType} are enum; otherwise return {@code false}.
*/
public static boolean isEnumType(Class<?> srcFieldClass, Class<?> destFieldType) {
if (srcFieldClass.isAnonymousClass()) {
//If srcFieldClass is anonymous class, replace srcFieldClass with its enclosing class.
//This is used to ensure Dozer can get correct Enum type.
srcFieldClass = srcFieldClass.getEnclosingClass();
}
if (destFieldType.isAnonymousClass()) {
//Just like srcFieldClass, if destFieldType is anonymous class, replace destFieldType with
//its enclosing class. This is used to ensure Dozer can get correct Enum type.
destFieldType = destFieldType.getEnclosingClass();
}
return srcFieldClass.isEnum() && destFieldType.isEnum();
}
public static List<Class<?>> getSuperClassesAndInterfaces(Class<?> srcClass) {
List<Class<?>> superClasses = new ArrayList<Class<?>>();
Class<?> realClass = getRealClass(srcClass);
// Add all super classes first
Class<?> superClass = getRealClass(realClass).getSuperclass();
while (!isBaseClass(superClass)) {
superClasses.add(superClass);
superClass = superClass.getSuperclass();
}
// Now add all interfaces of the passed in class and all it's super classes
// Linked hash set so duplicated are not added but insertion order is kept
LinkedHashSet<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
interfaces.addAll(getInterfaceHierarchy(realClass));
for (Class<?> clazz : superClasses) {
interfaces.addAll(getInterfaceHierarchy(clazz));
}
superClasses.addAll(interfaces);
return superClasses;
}
@SuppressWarnings("unchecked")
public static List<Class<?>> getInterfaceHierarchy(Class<?> srcClass) {
final List<Class<?>> result = new LinkedList<Class<?>>();
Class<?> realClass = getRealClass(srcClass);
final LinkedList<Class> interfacesToProcess = new LinkedList<Class>();
Class[] interfaces = realClass.getInterfaces();
interfacesToProcess.addAll(Arrays.asList(interfaces));
while (!interfacesToProcess.isEmpty()) {
Class<?> iface = interfacesToProcess.remove();
if (!result.contains(iface)) {
result.add(iface);
for (Class subiface : iface.getInterfaces()) {
// if we haven't processed this interface yet then add it to be processed
if (!result.contains(subiface)) {
interfacesToProcess.add(subiface);
}
}
}
}
return result;
}
private static boolean isBaseClass(Class<?> clazz) {
return clazz == null || BASE_CLASS.equals(clazz.getName());
}
}