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