net.zcarioca.zcommons.config.data.BeanPropertySetterFactory.java Source code

Java tutorial

Introduction

Here is the source code for net.zcarioca.zcommons.config.data.BeanPropertySetterFactory.java

Source

/*
 * Project: zlib-config
 * 
 * Copyright (C) 2013 zcarioca.net
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zcarioca.zcommons.config.data;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import net.zcarioca.zcommons.config.ConfigurableAttribute;
import net.zcarioca.zcommons.config.exceptions.ConfigurationException;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A class for creating instances of {@link BeanPropertySetter}.
 *
 * @author zcarioca
 */
public class BeanPropertySetterFactory {
    private static final Logger logger = LoggerFactory.getLogger(BeanPropertySetterFactory.class);

    /**
     * Gets a collection of {@link BeanPropertySetter} to configure the bean.
     *
     * @param bean The bean to configure.
     * @return Returns a collection of {@link BeanPropertySetter}.
     * @throws ConfigurationException
     */
    public Collection<BeanPropertySetter> getPropertySettersForBean(Object bean) throws ConfigurationException {
        List<BeanPropertySetter> setters = new ArrayList<BeanPropertySetter>();
        Map<String, PropertyDescriptor> descriptors = new HashMap<String, PropertyDescriptor>();

        Class<?> beanClass = bean.getClass();

        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
            for (PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) {
                Method reader = desc.getReadMethod();
                Method writer = desc.getWriteMethod();
                Field field = getField(beanClass, desc);

                if (reader != null) {
                    descriptors.put(desc.getDisplayName(), desc);
                }

                if (writer != null) {
                    if (writer.isAnnotationPresent(ConfigurableAttribute.class)) {
                        setters.add(new WriterBeanPropertySetter(bean, desc, field,
                                writer.getAnnotation(ConfigurableAttribute.class)));
                        descriptors.remove(desc.getDisplayName());
                    }
                    if (reader != null && reader.isAnnotationPresent(ConfigurableAttribute.class)) {
                        setters.add(new WriterBeanPropertySetter(bean, desc, field,
                                reader.getAnnotation(ConfigurableAttribute.class)));
                        descriptors.remove(desc.getDisplayName());
                    }
                }
            }
        } catch (Throwable t) {
            throw new ConfigurationException("Could not introspect bean class", t);
        }
        do {
            Field[] fields = beanClass.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(ConfigurableAttribute.class)) {
                    if (descriptors.containsKey(field.getName())
                            && descriptors.get(field.getName()).getWriteMethod() != null) {
                        PropertyDescriptor desc = descriptors.get(field.getName());
                        setters.add(new WriterBeanPropertySetter(bean, desc, field,
                                field.getAnnotation(ConfigurableAttribute.class)));
                    } else {
                        setters.add(new FieldBeanPropertySetter(bean, null, field,
                                field.getAnnotation(ConfigurableAttribute.class)));
                    }
                } else if (descriptors.containsKey(field.getName())) {
                    // the annotation may have been set on the getter, not the field
                    PropertyDescriptor desc = descriptors.get(field.getName());
                    if (desc.getReadMethod().isAnnotationPresent(ConfigurableAttribute.class)) {
                        setters.add(new FieldBeanPropertySetter(bean, desc, field,
                                desc.getReadMethod().getAnnotation(ConfigurableAttribute.class)));
                    }
                }
            }
        } while ((beanClass = beanClass.getSuperclass()) != null);
        return setters;
    }

    static Object getDefaultValue(Class<?> primitiveType) {
        if (boolean.class == primitiveType)
            return Boolean.FALSE;
        if (char.class == primitiveType)
            return '\u0000';
        if (float.class == primitiveType)
            return 0f;
        if (double.class == primitiveType)
            return 0d;
        if (byte.class == primitiveType)
            return (byte) 0;
        if (short.class == primitiveType)
            return (short) 0;
        if (int.class == primitiveType)
            return 0;
        return 0l;
    }

    private Field getField(Class<?> beanClass, PropertyDescriptor descriptor) {
        do {
            try {
                return beanClass.getDeclaredField(descriptor.getName());
            } catch (NoSuchFieldException exc) {
                // ignore, move on
            }
        } while ((beanClass = beanClass.getSuperclass()) != null);

        return null;
    }

    private static Collection<Annotation> getPropertyAnnotations(Field field, PropertyDescriptor descriptor) {
        Map<Class<? extends Annotation>, Annotation> annotations = new HashMap<Class<? extends Annotation>, Annotation>();

        if (descriptor != null) {
            if (descriptor.getWriteMethod() != null) {
                addAnnotationsToMap(annotations, descriptor.getWriteMethod().getAnnotations());
            }
            if (descriptor.getReadMethod() != null) {
                addAnnotationsToMap(annotations, descriptor.getReadMethod().getAnnotations());
            }
        }
        if (field != null) {
            addAnnotationsToMap(annotations, field.getAnnotations());
        }
        return annotations.values();
    }

    private static Collection<Annotation> getBeanAnnotations(Class<?> beanClass) {
        return Arrays.asList(beanClass.getAnnotations());
    }

    private static void addAnnotationsToMap(Map<Class<? extends Annotation>, Annotation> annotationMap,
            Annotation[] annotations) {
        if (ArrayUtils.isNotEmpty(annotations)) {
            for (Annotation annotation : annotations) {
                if (!annotationMap.containsKey(annotation.annotationType())) {
                    annotationMap.put(annotation.annotationType(), annotation);
                }
            }
        }
    }

    private static abstract class AbstractBeanPropertySetter implements BeanPropertySetter {
        final Object bean;
        final PropertyDescriptor descriptor;
        final Field field;
        final BeanPropertyInfo beanPropertyInfo;
        final ConfigurableAttribute attr;

        public AbstractBeanPropertySetter(Object bean, PropertyDescriptor descriptor, Field field,
                ConfigurableAttribute attr) {
            this.bean = bean;
            this.descriptor = descriptor;
            this.field = field;
            this.attr = attr;

            Class<?> propertyType = field != null ? field.getType() : descriptor.getPropertyType();
            String propertyName = field != null ? field.getName() : descriptor.getName();

            this.beanPropertyInfo = new BeanPropertyInfoImpl(bean.getClass(), propertyType, propertyName,
                    getBeanAnnotations(bean.getClass()), getPropertyAnnotations(field, descriptor));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public BeanPropertyInfo getBeanPropertyInfo() {
            return this.beanPropertyInfo;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getPropertyKey() {
            String propName = attr.propertyName();
            if (StringUtils.isEmpty(propName)) {
                propName = beanPropertyInfo.getPropertyName();
            }
            return propName;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        @SuppressWarnings("rawtypes")
        public void setProperty(Properties properties) throws ConfigurationException {
            String propName = getPropertyKey();
            String defaultVal = attr.defaultValue();

            if (logger.isTraceEnabled())
                logger.trace(String.format("Setting property '%s' with value '%s' for bean '%s'", propName,
                        defaultVal, bean.toString()));

            try {
                BeanPropertyConverter converter = BeanPropertyConverterRegistry.getRegistry()
                        .getPropertyConverter(getRawType());
                Object beanVal = converter.convertPropertyValue(properties.getProperty(propName, defaultVal),
                        beanPropertyInfo);

                if (beanVal == null) {
                    if (!beanPropertyInfo.isArray() && beanPropertyInfo.isPrimitive()) {
                        beanVal = getDefaultValue(beanPropertyInfo.getPropertyType());
                    }
                }

                writeValue(beanVal);
            } catch (Exception exc) {
                throw new ConfigurationException("Could not write property to bean", exc);
            }
        }

        public abstract Class<?> getRawType();

        public abstract void writeValue(Object beanVal) throws IllegalAccessException, InvocationTargetException;
    }

    private static final class FieldBeanPropertySetter extends AbstractBeanPropertySetter
            implements BeanPropertySetter {
        public FieldBeanPropertySetter(Object bean, PropertyDescriptor descriptor, Field field,
                ConfigurableAttribute attr) {
            super(bean, descriptor, field, attr);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void writeValue(Object beanVal) throws IllegalAccessException {
            field.setAccessible(true);
            field.set(bean, beanVal);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Class<?> getRawType() {
            return field.getType();
        }
    }

    private static final class WriterBeanPropertySetter extends AbstractBeanPropertySetter
            implements BeanPropertySetter {
        public WriterBeanPropertySetter(Object bean, PropertyDescriptor descriptor, Field field,
                ConfigurableAttribute attr) {
            super(bean, descriptor, field, attr);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void writeValue(Object beanVal) throws IllegalAccessException, InvocationTargetException {
            descriptor.getWriteMethod().setAccessible(true);
            descriptor.getWriteMethod().invoke(bean, beanVal);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Class<?> getRawType() {
            return descriptor.getPropertyType();
        }
    }

    private static final class BeanPropertyInfoImpl implements BeanPropertyInfo {
        private final Class<?> beanType;
        private final Class<?> propertyType;
        private final String propertyName;
        private final Collection<Annotation> beanAnnotations;
        private final Collection<Annotation> propertyAnnotations;

        public BeanPropertyInfoImpl(Class<?> beanType, Class<?> propertyType, String propertyName,
                Collection<Annotation> beanAnnotations, Collection<Annotation> propertyAnnotations) {
            this.beanType = beanType;
            this.propertyType = propertyType;
            this.propertyName = propertyName;

            this.propertyAnnotations = Collections.unmodifiableCollection(propertyAnnotations);
            this.beanAnnotations = Collections.unmodifiableCollection(beanAnnotations);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Collection<Annotation> getBeanAnnotations() {
            return beanAnnotations;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Class<?> getBeanType() {
            return beanType;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Collection<Annotation> getPropertyAnnotations() {
            return propertyAnnotations;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getPropertyName() {
            return propertyName;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Class<?> getPropertyType() {
            return isArray() ? propertyType.getComponentType() : propertyType;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isArray() {
            return propertyType.isArray();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isPrimitive() {
            return getPropertyType().isPrimitive();
        }
    }
}