/**
* This file Copyright (c) 2012 Magnolia International
* Ltd. (http://www.magnolia-cms.com). All rights reserved.
*
*
* This file is dual-licensed under both the Magnolia
* Network Agreement and the GNU General Public License.
* You may elect to use one or the other of these licenses.
*
* This file is distributed in the hope that it will be
* useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
* Redistribution, except as permitted by whichever of the GPL
* or MNA you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or
* modify this file under the terms of the GNU General
* Public License, Version 3, as published by the Free Software
* Foundation. You should have received a copy of the GNU
* General Public License, Version 3 along with this program;
* if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 2. For the Magnolia Network Agreement (MNA), this file
* and the accompanying materials are made available under the
* terms of the MNA which accompanies this distribution, and
* is available at http://www.magnolia-cms.com/mna.html
*
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.jcr.node2bean.impl;
import info.magnolia.jcr.node2bean.Node2BeanException;
import info.magnolia.jcr.node2bean.Node2BeanTransformer;
import info.magnolia.jcr.node2bean.PropertyTypeDescriptor;
import info.magnolia.jcr.node2bean.TransformedBy;
import info.magnolia.jcr.node2bean.TypeDescriptor;
import info.magnolia.jcr.node2bean.TypeMapping;
import info.magnolia.objectfactory.Components;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Basic type mapping implementation.
*/
public class TypeMappingImpl implements TypeMapping {
private static Logger log = LoggerFactory.getLogger(TypeMappingImpl.class);
private final Map<String, PropertyTypeDescriptor> propertyTypes = new HashMap<String, PropertyTypeDescriptor>();
private final Map<Class<?>, TypeDescriptor> types = new HashMap<Class<?>, TypeDescriptor>();
@Override
public PropertyTypeDescriptor getPropertyTypeDescriptor(Class<?> beanClass, String propName) {
PropertyTypeDescriptor dscr = null;
String key = beanClass.getName() + "." + propName;
dscr = propertyTypes.get(key);
if (dscr != null) {
return dscr;
}
dscr = new PropertyTypeDescriptor();
dscr.setName(propName);
PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(beanClass);
Method writeMethod = null;
for (PropertyDescriptor descriptor : descriptors) {
if (descriptor.getName().equals(propName)) {
// may be null for indexed properties
Class<?> propertyType = descriptor.getPropertyType();
writeMethod = descriptor.getWriteMethod();
if (propertyType != null) {
dscr.setType(getTypeDescriptor(propertyType, writeMethod));
}
// set write method
dscr.setWriteMethod(writeMethod);
// set add method
int numberOfParameters = dscr.isMap() ? 2 : 1;
dscr.setAddMethod(getAddMethod(beanClass, propName, numberOfParameters));
break;
}
}
if (dscr.getType() != null) {
// we have discovered type for property
if (dscr.isMap() || dscr.isCollection()) {
List<Class<?>> parameterTypes = new ArrayList<Class<?>>(); // this will contain collection types (for map key/value type, for collection value type)
if (dscr.getWriteMethod() != null) {
parameterTypes = inferGenericTypes(dscr.getWriteMethod());
}
if (dscr.getAddMethod() != null && parameterTypes.size() == 0) {
// here we know we don't have setter or setter doesn't have parameterized type
// but we have add method so we take parameters from it
parameterTypes = Arrays.asList(dscr.getAddMethod().getParameterTypes());
// rather set it to null because when we are here we will use add method
dscr.setWriteMethod(null);
}
if (parameterTypes.size() > 0) {
// we resolved types
if (dscr.isMap()) {
dscr.setCollectionKeyType(getTypeDescriptor(parameterTypes.get(0)));
dscr.setCollectionEntryType(getTypeDescriptor(parameterTypes.get(1)));
} else {
// collection
dscr.setCollectionEntryType(getTypeDescriptor(parameterTypes.get(0)));
}
}
} else if (dscr.isArray()) {
// for arrays we don't need to discover its parameter from set/add method
// we just take it via Class#getComponentType() method
dscr.setCollectionEntryType(getTypeDescriptor(dscr.getType().getType().getComponentType()));
}
}
propertyTypes.put(key, dscr);
return dscr;
}
private List<Class<?>> inferGenericTypes(Method method) {
List<Class<?>> inferredTypes = new ArrayList<Class<?>>();
Type[] parameterTypes = method.getGenericParameterTypes();
for (Type parameterType : parameterTypes) {
if (parameterType instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) parameterType;
for (Type t : type.getActualTypeArguments()) {
if (t instanceof ParameterizedType) {
// this the case when parameterized type looks like this: Collection<List<String>>
// we care only for raw type List
inferredTypes.add((Class<?>) ((ParameterizedType) t).getRawType());
} else {
inferredTypes.add((Class<?>) t);
}
}
}
}
return inferredTypes;
}
/**
* Resolves transformer from bean class or setter.
*/
private Node2BeanTransformer resolveTransformer(Class<?> beanClass, Method writeMethod) throws Node2BeanException {
if (!beanClass.isArray() && !beanClass.isPrimitive()) { // don't bother looking for a transformer if the property is an array or a primitive type
Class<Node2BeanTransformer> transformerClass = null;
Node2BeanTransformer transformer = null;
if (writeMethod != null) {
TransformedBy transformerAnnotation = writeMethod.getAnnotation(TransformedBy.class);
transformerClass = transformerAnnotation == null ? null : (Class<Node2BeanTransformer>) transformerAnnotation.value();
transformer = transformerClass == null ? null : Components.getComponentProvider().newInstance(transformerClass);
}
if (transformer == null) {
try {
transformerClass = (Class<Node2BeanTransformer>) Class.forName(beanClass.getName() + "Transformer");
transformer = Components.getComponent(transformerClass);
} catch (ClassNotFoundException e) {
log.debug("No transformer found for bean [{}]", beanClass);
}
}
return transformer;
}
return null;
}
/**
* Gets type descriptor from bean class.
*/
private TypeDescriptor getTypeDescriptor(Class<?> beanClass, Method method) {
TypeDescriptor dscr = types.get(beanClass);
// eh, we know about this type, don't bother resolving any further.
if(dscr != null){
return dscr;
}
dscr = new TypeDescriptor();
dscr.setType(beanClass);
dscr.setMap(Map.class.isAssignableFrom(beanClass));
dscr.setCollection(Collection.class.isAssignableFrom(beanClass));
dscr.setArray(beanClass.isArray());
try {
dscr.setTransformer(resolveTransformer(beanClass, method));
} catch (Node2BeanException e) {
log.error("Can't create transformer for bean [" + beanClass + "]", e);
}
types.put(beanClass, dscr);
return dscr;
}
@Override
public TypeDescriptor getTypeDescriptor(Class<?> beanClass) {
return getTypeDescriptor(beanClass, null);
}
/**
* Get a adder method. Transforms name to singular.
* @deprecated since 5.0 - use setters
*/
public Method getAddMethod(Class<?> type, String name, int numberOfParameters) {
name = StringUtils.capitalize(name);
Method method = getExactMethod(type, "add" + name, numberOfParameters);
if (method == null) {
method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "s"), numberOfParameters);
}
if (method == null) {
method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "es"), numberOfParameters);
}
if (method == null) {
method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "ren"), numberOfParameters);
}
if (method == null) {
method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "ies") + "y", numberOfParameters);
}
return method;
}
/**
* Find a method.
*
* @param numberOfParameters
* @deprecated since 5.0 - use setters
*/
protected Method getExactMethod(Class<?> type, String name, int numberOfParameters) {
Method[] methods = type.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (method.getName().equals(name)) {
// TODO - CAUTION: in case there's several methods with the same
// name and the same numberOfParameters
// this method might pick the "wrong" one. We should think about
// adding a check and throw an exceptions
// if there's more than one match!
if (method.getParameterTypes().length == numberOfParameters) {
return method;
}
}
}
return null;
}
}