001package com.hammurapi.convert;
002
003import java.lang.reflect.Constructor;
004import java.lang.reflect.Method;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.logging.Level;
008import java.util.logging.Logger;
009
010import com.hammurapi.common.Context;
011
012
013/**
014 * Converts one type to another using accessor/constructor combination.
015 * @author Pavel
016 *
017 */
018public class ReflectionConverter<S, T> implements AtomicConverter<S, T> {
019        private static final Logger logger = Logger.getLogger(ReflectionConverter.class.getName());
020        
021        Method accessor;
022        Constructor<? extends T> constructor;
023        private Class<? extends T> targetType;
024        private Class<S> sourceType;
025        private boolean singleArg;
026        
027        /**
028         * @param accessor Source object method to invoke to get target type or
029         * constructor parameter for the target type. If it is null, then source itself
030         * is passed to constructor. One of parameters may be null, but not both.
031         * @param constructor Target class constructor which takes one parameter, either
032         * source type or return type of source type accessor.
033         */
034        @SuppressWarnings("unchecked")
035        public ReflectionConverter(Method accessor, Constructor<? extends T> constructor) {
036                super();
037                this.accessor = accessor;
038                this.constructor = constructor;
039                singleArg = constructor!=null && constructor.getParameterTypes().length==1;
040                if (accessor==null) {
041                        sourceType = (Class<S>) constructor.getParameterTypes()[0];
042                } else {
043                        sourceType = (Class<S>) accessor.getDeclaringClass();
044                }
045                
046                if (constructor==null) {
047                        targetType = (Class<T>) accessor.getReturnType();
048                } else {
049                        targetType = constructor.getDeclaringClass();
050                }               
051        }
052        
053        @SuppressWarnings("unchecked")
054        public T convert(S source, Converter master, Context context, ClassLoader classLoader) {
055                try {
056                        Object param = accessor==null ? source : accessor.invoke(source);
057                        if (constructor==null) {
058                                return (T) param;
059                        }
060                        
061                        if (singleArg) {
062                                return constructor.newInstance(new Object[] {param});
063                        }
064                        
065                        return constructor.newInstance(new Object[] {param, context});
066                } catch (Exception e) {
067                        logger.log(Level.FINE, "Cannot convert "+source.getClass().getName()+" to " + constructor.getDeclaringClass()+": "+e, e);
068                        return null;
069                }
070        }
071
072        public Class<S> getSourceType() {
073                return sourceType;
074        }
075
076        public Class<? extends T> getTargetType() {
077                return targetType;
078        }
079        
080        /**
081         * Discovers constructor conversions.
082         * @param target Target type
083         * @return Collection of discovered converters.
084         */
085        @SuppressWarnings("unchecked")
086        public static <T> Collection<ReflectionConverter<?, T>> discoverConstructorConverters(Class<T> target) {
087                Collection<ReflectionConverter<?, T>> ret=new ArrayList<ReflectionConverter<?, T>>();
088                if (!target.isInterface()) {
089                        for (int i=0, cc=target.getConstructors().length; i<cc; i++) {
090                                Constructor<?> constructor = target.getConstructors()[i];
091                                Class<?>[] parameterTypes = constructor.getParameterTypes();
092                                if (parameterTypes.length==1 && !target.isAssignableFrom(parameterTypes[0])) {
093                                        
094                                        ret.add(new ReflectionConverter(null, constructor));
095                                } else if (parameterTypes.length==2
096                                                && !target.isAssignableFrom(parameterTypes[0])
097                                                && Context.class.equals(parameterTypes[1])) {
098                                                
099                                        ret.add(new ReflectionConverter(null, constructor));
100                                }
101                        }
102                }
103                return ret;
104        }       
105        
106        public String toString() {
107                return this.getClass().getName() + " ("+sourceType+" -> "+targetType+", accessor: "+accessor+", constructor: "+constructor+")";
108        }
109        
110}