info.magnolia.jcr.node2bean.impl.TypeMappingImpl.java Source code

Java tutorial

Introduction

Here is the source code for info.magnolia.jcr.node2bean.impl.TypeMappingImpl.java

Source

/**
 * 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;
    }

}