001package org.javasimon.utils.bean;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006import java.lang.reflect.Field;
007import java.lang.reflect.InvocationTargetException;
008import java.lang.reflect.Method;
009import java.util.Map;
010import java.util.Set;
011import java.util.concurrent.ConcurrentHashMap;
012
013/**
014 * Utils for setting properties values in Java bean object. It can convert values that should be set from String
015 * to a type of specified properties.
016 *
017 * @author <a href="mailto:ivan.mushketyk@gmail.com">Ivan Mushketyk</a>
018 */
019public class SimonBeanUtils {
020        private static final Logger logger = LoggerFactory.getLogger(SimonBeanUtils.class);
021
022        private static final SimonBeanUtils INSTANCE = new SimonBeanUtils();
023
024        private final Map<Class<?>, Converter> converters = new ConcurrentHashMap<>();
025
026        public SimonBeanUtils() {
027                converters.put(String.class, new ToStringConverter());
028
029                converters.put(boolean.class, new ToBooleanConverter());
030                converters.put(Boolean.class, new ToBooleanConverter());
031
032                converters.put(byte.class, new ToByteConverter());
033                converters.put(Byte.class, new ToByteConverter());
034
035                converters.put(short.class, new ToShortConverter());
036                converters.put(Short.class, new ToShortConverter());
037
038                converters.put(int.class, new ToIntegerConverter());
039                converters.put(Integer.class, new ToIntegerConverter());
040
041                converters.put(long.class, new ToLongConverter());
042                converters.put(Long.class, new ToLongConverter());
043
044                converters.put(float.class, new ToFloatConverter());
045                converters.put(Float.class, new ToFloatConverter());
046
047                converters.put(double.class, new ToDoubleConverter());
048                converters.put(Double.class, new ToDoubleConverter());
049
050                converters.put(char.class, new ToCharConverter());
051                converters.put(Character.class, new ToCharConverter());
052
053        }
054
055        public static SimonBeanUtils getInstance() {
056                return INSTANCE;
057        }
058
059        /**
060         * Set property in object target. If values has type other than String setter method or field
061         * with specified type will be used to set value. If value has String type value will be converted
062         * using available converters. If conversion to all of the types accepted by setters fails, field
063         * with corresponding name will be used
064         *
065         * @param target Java bean where a property will be set
066         * @param property property to be set
067         * @param value value to be set
068         */
069        public void setProperty(Object target, String property, Object value) {
070                NestedResolver resolver = new NestedResolver(target, property);
071
072                if (value instanceof String) {
073                        convertStringValue(resolver.getNestedTarget(), resolver.getProperty(), (String) value);
074                } else {
075                        setObjectValue(resolver.getNestedTarget(), resolver.getProperty(), value);
076                }
077        }
078
079        private void convertStringValue(Object target, String property, String strVal) {
080                Set<Method> potentialSetters = ClassUtils.getSetters(target.getClass(), property);
081                boolean converted = false;
082
083                for (Method setter : potentialSetters) {
084                        try {
085                                Class<?> setterType = ClassUtils.getSetterType(setter);
086                                Converter converter = getConverterTo(setterType);
087                                if (converter != null) {
088                                        Object value = converter.convert(setterType, strVal);
089                                        invokeSetter(target, setter, value);
090                                        converted = true;
091                                        break;
092                                } else {
093                                        logger.debug("Failed to find converter for method '{}'", setter);
094                                }
095                        } catch (ConvertException ex) {
096                                logger.debug("Failed to convert value '{}' for method '{}'", strVal, setter);
097                        }
098                }
099
100                if (!converted) {
101                        Field field = ClassUtils.getField(target.getClass(), property);
102                        if (field != null) {
103                                Class<?> fieldType = field.getType();
104                                Converter converter = getConverterTo(fieldType);
105                                if (converter != null) {
106                                        Object value = converter.convert(fieldType, strVal);
107                                        setValueUsingField(field, target, value);
108                                        return;
109                                }
110                        }
111                        throw new BeanUtilsException(
112                                String.format("Failed to find find setter/field for property '%s' and value '%s'", property, strVal));
113                }
114        }
115
116        private Converter getConverterTo(Class<?> setterType) {
117                return converters.get(setterType);
118        }
119
120        private void setObjectValue(Object target, String property, Object value) {
121                Method setter = ClassUtils.getSetter(target.getClass(), property, value.getClass());
122                if (setter != null) {
123                        invokeSetter(target, setter, value);
124                } else {
125                        Field field = ClassUtils.getField(target.getClass(), property);
126                        if (field != null) {
127                                setValueUsingField(field, target, value);
128                        } else {
129                                throw new BeanUtilsException("Failed to find field/setter for property " + property);
130                        }
131                }
132        }
133
134        private void invokeSetter(Object target, Method setter, Object value) {
135                try {
136                        setter.setAccessible(true);
137                        setter.invoke(target, value);
138                } catch (IllegalAccessException | InvocationTargetException e) {
139                        throw new BeanUtilsException(e);
140                }
141        }
142
143        private void setValueUsingField(Field field, Object target, Object value) {
144                try {
145                        field.setAccessible(true);
146                        field.set(target, value);
147                } catch (SecurityException | IllegalAccessException ex) {
148                        throw new BeanUtilsException(ex);
149                }
150        }
151
152        /**
153         * Register converter for a specified class.
154         *
155         * @param targetClass property type for which a converter will be used
156         * @param converter converter that will be used to convert String values for a specified target class
157         */
158        public void registerConverter(Class<?> targetClass, Converter converter) {
159                converters.put(targetClass, converter);
160        }
161}