info.magnolia.content2bean.impl.TypeMappingImpl.java Source code

Java tutorial

Introduction

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

Source

/**
 * This file Copyright (c) 2003-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.content2bean.impl;

import info.magnolia.cms.core.SystemProperty;
import info.magnolia.content2bean.Content2BeanTransformer;
import info.magnolia.content2bean.PropertyTypeDescriptor;
import info.magnolia.content2bean.TypeDescriptor;
import info.magnolia.content2bean.TypeMapping;
import info.magnolia.objectfactory.ClassFactory;
import info.magnolia.objectfactory.Classes;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.inject.Singleton;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Basic type mapping implementation.
 * @author philipp
 * @version $Id$
 *
 */
@Singleton
public class TypeMappingImpl implements TypeMapping {

    private static Logger log = LoggerFactory.getLogger(TypeMappingImpl.class);

    /**
     * Property types already resolved.
     */
    private final Map<String, PropertyTypeDescriptor> propertyTypes = new HashMap<String, PropertyTypeDescriptor>();

    /**
     * Descriptors for types.
     **/
    private final Map<Class<?>, TypeDescriptor> types = new HashMap<Class<?>, TypeDescriptor>();

    /**
     * Get a adder method. Transforms name to singular.
     */
    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;
    }

    /**
     * Cache the already resolved types.
     *
     */
    @Override
    public PropertyTypeDescriptor getPropertyTypeDescriptor(Class<?> beanClass, String propName) {
        PropertyTypeDescriptor dscr;
        String key = beanClass.getName() + "." + propName;

        dscr = propertyTypes.get(key);
        if (dscr != null) {
            return dscr;
        }

        //TypeMapping defaultMapping = TypeMapping.Factory.getDefaultMapping();
        // TODO - is this used - or is the comparison correct ?
        //        if (this != defaultMapping) {
        //            dscr = defaultMapping.getPropertyTypeDescriptor(beanClass, propName);
        //            if (dscr.getType()  != null) {
        //                return dscr;
        //            }
        //        }

        dscr = new PropertyTypeDescriptor();
        dscr.setName(propName);

        PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(beanClass);
        for (int i = 0; i < descriptors.length; i++) {
            PropertyDescriptor descriptor = descriptors[i];
            if (descriptor.getName().equals(propName)) {
                Class<?> propertytype = descriptor.getPropertyType(); // may be null for indexed properties
                if (propertytype != null) {
                    dscr.setType(getTypeDescriptor(propertytype));
                }
                break;
            }
        }

        if (dscr.getType() != null) {
            if (dscr.isMap() || dscr.isCollection()) {
                int numberOfParameters = dscr.isMap() ? 2 : 1;
                Method method = getAddMethod(beanClass, propName, numberOfParameters);
                if (method != null) {
                    dscr.setAddMethod(method);
                    if (dscr.isMap()) {
                        dscr.setCollectionKeyType(getTypeDescriptor(method.getParameterTypes()[0]));
                        dscr.setCollectionEntryType(getTypeDescriptor(method.getParameterTypes()[1]));
                    } else {
                        dscr.setCollectionEntryType(getTypeDescriptor(method.getParameterTypes()[0]));
                    }
                }
            }
        }

        // remember me
        propertyTypes.put(key, dscr);

        return dscr;
    }

    @Override
    public void addPropertyTypeDescriptor(Class<?> beanClass, String propName, PropertyTypeDescriptor dscr) {
        propertyTypes.put(beanClass.getName() + "." + propName, dscr);
    }

    @Override
    public void addTypeDescriptor(Class<?> beanClass, TypeDescriptor dscr) {
        types.put(beanClass, dscr);
    }

    @Override
    public TypeDescriptor getTypeDescriptor(Class<?> beanClass) {
        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(beanClass.isArray() || Collection.class.isAssignableFrom(beanClass));
        types.put(beanClass, dscr);

        if (!beanClass.isArray() && !beanClass.isPrimitive()) { // don't bother looking for a transformer if the property is an array or a primitive type
            Content2BeanTransformer transformer = null; // TODO ? transformerProvider.getTransformerFor(beanClass);
            try {
                if (transformer == null) {
                    transformer = findTransformerByNamingConvention(beanClass);
                }
                if (transformer == null) {
                    transformer = findTransformerViaProperty(beanClass);
                }
            } catch (Exception e) {
                // this is fine because having a transformer class is optional
                log.debug("No custom transformer class {}Transformer class found", beanClass.getName());
            }
            dscr.setTransformer(transformer);
        }
        return dscr;
    }

    /**
     * @deprecated since 4.5, transformers should be explicitly registered via the module descriptor.
     */
    protected Content2BeanTransformer findTransformerByNamingConvention(Class<?> beanClass) {
        final String transformerClassName = beanClass.getName() + "Transformer";
        try {
            return instantiateTransformer(beanClass, transformerClassName);
        } catch (ClassNotFoundException e) {
            log.debug("No transformer found by naming convention for {} (attempted to load {})", beanClass,
                    transformerClassName);
            return null;
        }
    }

    /**
     * This was originally implemented by {@link info.magnolia.content2bean.impl.PropertiesBasedTypeMapping}.
     * @deprecated since 4.5, transformers should be explicitly registered via the module descriptor.
     */
    protected Content2BeanTransformer findTransformerViaProperty(Class<?> beanClass) throws ClassNotFoundException {
        final String property = SystemProperty.getProperty(beanClass.getName() + ".transformer");
        if (property != null) {
            return instantiateTransformer(beanClass, property);
        }
        return null;
    }

    protected Content2BeanTransformer instantiateTransformer(Class<?> beanClass, String transformerClassName)
            throws ClassNotFoundException {
        final ClassFactory classFactory = Classes.getClassFactory();
        final Class<Content2BeanTransformer> transformerClass = classFactory.forName(transformerClassName);

        if (Content2BeanTransformer.class.isAssignableFrom(transformerClass)) {
            try {
                log.debug("Found a custom transformer [{" + transformerClass + "}] for [{" + beanClass + "}]");
                // TODO use components ?
                return classFactory.newInstance(transformerClass);
            } catch (Exception e) {
                log.error("Can't instantiate custom transformer [{" + transformerClass + "}] for [{" + beanClass
                        + "}]", e);
            }
        }
        return null;
    }

    /**
     * Find a method.
     *
     * @param numberOfParameters
     */
    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;
    }

}