    001package com.hammurapi.convert;
    002
    003import java.lang.reflect.InvocationHandler;
    004import java.lang.reflect.InvocationTargetException;
    005import java.lang.reflect.Method;
    006import java.lang.reflect.Proxy;
    007import java.util.ArrayList;
    008import java.util.Arrays;
    009import java.util.Collection;
    010import java.util.HashMap;
    011import java.util.Iterator;
    012import java.util.List;
    013import java.util.Map;
    014
    015/**
    016 * Mixes interfaces from multiple objects into one proxy.
    017 * @author Pavel
    018 *
    019 */
    020public class Mixer {
    021
    022        /**
    023         * Mixes-in implementation of interfaces into class. I.e. it creates a proxy
    024         * which implements all object's interfaces plus interfaces from parameters.
    025         * E.g. class has methods compatible with interfaces, but doesn't implement
    026         * the interface itself. This is very similar to duck typing.
    027         * This method doesn't check for method compatibility, it simply routes
    028         * interface methods to the object. It is useful when only a sub-set of interface
    029         * methods is used by the calling code, so the code would work even if strict
    030         * conversion failed.
    031         * @param object
    032         * @param interfaces
    033         * @return
    034         */
    035        public static Object mixIn(final Object object, Class<?>... interfaces) {
    036                List<Class<?>> additional = new ArrayList<Class<?>>();
    037                for (int i = 0; interfaces!=null && i<interfaces.length; ++i) {
    038                        if (!interfaces[i].isInstance(object)) {
    039                                additional.add(interfaces[i]);
    040                        }
    041                }
    042
    043                if (additional.isEmpty()) {
    044                        return object;
    045                }
    046
    047                Class<?> objectClass = object.getClass();
    048                Class<?>[] objectInterfaces = DuckConverterFactory.getClassInterfaces(objectClass);
    049                Class<?>[] proxyInterfaces = new Class[objectInterfaces.length+additional.size()];
    050                ClassLoader classLoader = null;
    051                for (int i=0; i<objectInterfaces.length; ++i) {
    052                        proxyInterfaces[i] = objectInterfaces[i];
    053                        classLoader = DuckConverterFactory.getChildClassLoader(classLoader, objectInterfaces[i].getClassLoader());
    054                }
    055
    056                final Map<Method, Method> methodMap = new HashMap<Method, Method>();
    057                Iterator<Class<?>> it = additional.iterator();
    058                for (int i=objectInterfaces.length; it.hasNext(); ++i) {
    059                        proxyInterfaces[i] = it.next();
    060                        classLoader = DuckConverterFactory.getChildClassLoader(classLoader, proxyInterfaces[i].getClassLoader());
    061                        DuckConverterFactory.duckMap(proxyInterfaces[i], objectClass, methodMap);
    062                }
    063
    064                return Proxy.newProxyInstance(
    065                                classLoader == null ? objectClass.getClassLoader() : classLoader,
    066                                proxyInterfaces,
    067                                new FilterInvocationHandler() {
    068
    069                                        public Object invoke(
    070                                                        Object proxy,
    071                                                        Method method,
    072                                                        Object[] args) throws Throwable {
    073
    074                                                Method mappedMethod = methodMap.get(method);
    075                                                if (mappedMethod ==null) {
    076                                                        return method.invoke(object, args);
    077                                                }
    078
    079//                                              CompositeConverter converter = CompositeConverter.getDefaultConverter();
    080                                                Object[] convertedArgs;
    081                                                if (args==null) {
    082                                                        convertedArgs = null;
    083                                                } else {
    084                                                        Class<?>[] parameterTypes = mappedMethod.getParameterTypes();
    085                                                        convertedArgs = new Object[args.length];
    086                                                        for (int i=0; i<convertedArgs.length; ++i) {
    087                                                                convertedArgs[i] = ConvertingService.convert(args[i], parameterTypes[i]);
    088                                                        }
    089                                                }
    090                                                return mappedMethod.invoke(object, args);
    091                                        }
    092
    093                                        public Object getMaster() {
    094                                                return object;
    095                                        }
    096
    097                                });
    098        }
    099
    100        /**
    101         * Creates a proxy which routes invocations to master unless one of interceptors
    102         * has a method with matching signature. Only interface methods are routed
    103         * to interceptors.
    104         *
    105         * Traditional approach to intercepting in Java is to create filters/wrappers.
    106         * This approach might be problematic if the filter master instances might be of
    107         * different subclasses of the base filtered type and if the subclassed functionality
    108         * must be propagated through the filter.
    109         *
    110         * Returned proxy invokes only interceptor's method. It is responsiblity of the
    111         * interceptor to call master's method if needed.
    112         *
    113         * Interceptor methods are matched to master methods based on name and parameter
    114         * types equality (as in overriding). Return type and exception types are not
    115         * taken in consideration.
    116         *
    117         * @param master
    118         * @param interceptors
    119         * @return
    120         */
    121        @SuppressWarnings("unchecked")
    122        public static Object addInterceptors(final Object master, Object... interceptors) {
    123                if (master == null) {
    124                        return null;
    125                }
    126
    127                // Array of collections.
    128                Collection<Method>[] interceptorMethods = new Collection[interceptors.length];
    129                for (int i=0; i<interceptors.length; ++i) {
    130                        Class<? extends Object> interceptorClass = interceptors[i].getClass();
    131                        Method[] iMethods = interceptorClass.getMethods();
    132                        interceptorMethods[i] = new ArrayList<Method>();
    133                        for (int m=0; m<iMethods.length; ++m) {
    134                                Method interceptorMethod = iMethods[m];
    135
    136                                if (!interceptorMethod.getDeclaringClass().equals(Object.class)) {
    137                                        interceptorMethods[i].add(interceptorMethod);
    138                                }
    139                        }
    140                }
    141
    142                class InterceptorEntry {
    143                        Object object;
    144                        Method method;
    145
    146                        InterceptorEntry(Object object, Method method) {
    147                                super();
    148                                this.object = object;
    149                                this.method = method;
    150                        }
    151
    152                        Object invoke(Object[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    153                                return method.invoke(object, args);
    154                        }
    155                }
    156
    157                Class<?>[] interfaces = DuckConverterFactory.getClassInterfaces(master.getClass());
    158                final Map<Method, InterceptorEntry> interceptionMap = new HashMap<Method, InterceptorEntry>();
    159                for (int interfaceIdx=0; interfaceIdx<interfaces.length; ++interfaceIdx) {
    160                        Method[] methods = interfaces[interfaceIdx].getMethods();
    161                        Z: for (int methodIdx = 0; methodIdx < methods.length; ++methodIdx) {
    162                                Method masterMethod = methods[methodIdx];
    163                                if (!masterMethod.getDeclaringClass().equals(Object.class)) {
    164                                        for (int interceptorIdx =0; interceptorIdx<interceptors.length; ++interceptorIdx) {
    165                                                Iterator<?> interceptorMethodIterator = interceptorMethods[interceptorIdx].iterator();
    166                                                Y: while (interceptorMethodIterator.hasNext()) {
    167                                                        Method interceptorMethod = (Method) interceptorMethodIterator.next();
    168                                                        if (masterMethod.getName().equals(interceptorMethod.getName())) {
    169                                                                Class<?>[] masterParameterTypes = masterMethod.getParameterTypes();
    170                                                                Class<?>[] interceptorParameterTypes = interceptorMethod.getParameterTypes();
    171                                                                if (masterParameterTypes.length!=interceptorParameterTypes.length) {
    172                                                                        continue;
    173                                                                }
    174                                                                for (int prmIdx = 0; prmIdx<masterParameterTypes.length; ++prmIdx) {
    175                                                                        if (!masterParameterTypes[prmIdx].equals(interceptorParameterTypes[prmIdx])) {
    176                                                                                continue Y;
    177                                                                        }
    178                                                                }
    179
    180                                                                System.out.println("~~~~~ Mapped method "+masterMethod+" to interceptor "+interceptorIdx);
    181                                                                interceptionMap.put(masterMethod, new InterceptorEntry(interceptors[interceptorIdx], interceptorMethod));
    182                                                                continue Z;
    183                                                        }
    184                                                }
    185                                        }
    186                                }
    187                        }
    188                }
    189
    190                return Proxy.newProxyInstance(
    191                                Object.class.getClassLoader(),
    192                                interfaces,
    193                                new FilterInvocationHandler() {
    194
    195                                        public Object invoke(
    196                                                        Object proxy,
    197                                                        Method method,
    198                                                        Object[] args) throws Throwable {
    199
    200                                                InterceptorEntry ie = interceptionMap.get(method);
    201                                                return ie==null ? method.invoke(master, args) : ie.invoke(args);
    202                                        }
    203
    204                                        public Object getMaster() {
    205                                                return master;
    206                                        }
    207
    208                                });
    209
    210        }
    211
    212        /**
    213         * Creates a proxy which implements given interfaces and routes invocations
    214         * to appropriate object. If object is not found for a particular interface,
    215         * then invocation is routed to the first object.
    216         * @param object
    217         * @param interfaces
    218         * @return
    219         */
    220        @SuppressWarnings("unchecked")
    221        public static Object combine(final Object[] objects, Class<?>[] interfaces) {
    222                if (objects==null || objects.length==0) {
    223                        return null;
    224                }
    225
    226                boolean firstDoesIt = true;
    227                for (int i=0; i<interfaces.length; ++i) {
    228                        if (!interfaces[i].isInstance(objects[0])) {
    229                                firstDoesIt = false;
    230                                break;
    231                        }
    232                }
    233
    234                if (firstDoesIt) {
    235                        return objects[0];
    236                }
    237
    238                if (objects.length==1) {
    239                        return mixIn(objects[0], interfaces);
    240                }
    241
    242                List<Method> allInterfaceMethods = new ArrayList<Method>();
    243                for (int i=0; i<interfaces.length; ++i) {
    244                        allInterfaceMethods.addAll(Arrays.asList(interfaces[i].getMethods()));
    245                }
    246
    247                Method[] aim = allInterfaceMethods.toArray(new Method[allInterfaceMethods.size()]);
    248
    249                final Map<Method, Method>[] methodMaps = new Map[objects.length];
    250                for (int i=0; i<objects.length; ++i) {
    251                        methodMaps[i] = new HashMap<Method, Method>();
    252                        DuckConverterFactory.duckMap(aim, objects[i].getClass().getMethods(), methodMaps[i]);
    253                }
    254
    255                ClassLoader classLoader  = null;
    256                for (int i=0; i<objects.length; ++i) {
    257                        classLoader = DuckConverterFactory.getChildClassLoader(classLoader, objects[i].getClass().getClassLoader());
    258                }
    259                for (int i=0; i<interfaces.length; ++i) {
    260                        classLoader = DuckConverterFactory.getChildClassLoader(classLoader, interfaces[i].getClassLoader());
    261                }
    262
    263                return Proxy.newProxyInstance(
    264                                classLoader == null ? objects[0].getClass().getClassLoader() : classLoader,
    265                                interfaces,
    266                                new InvocationHandler() {
    267
    268                                        public Object invoke(
    269                                                        Object proxy,
    270                                                        Method method,
    271                                                        Object[] args) throws Throwable {
    272
    273                                                Class<?> declaringClass = method.getDeclaringClass();
    274                                                for (int i=0; i<objects.length; ++i) {
    275                                                        if (declaringClass.isInstance(objects[i])) {
    276                                                                return method.invoke(objects[i], args);
    277                                                        }
    278                                                        Method mappedMethod = (Method) methodMaps[i].get(method);
    279                                                        if (mappedMethod!=null) {
    280                                                                return mappedMethod.invoke(objects[i], args);
    281                                                        }
    282                                                }
    283
    284                                                return method.invoke(objects[0], args);
    285                                        }
    286
    287                                });
    288        }
    289
    290        /**
    291         * Mixes interfaces from multiple objects into one proxy.
    292         * It can be used to "mix-in" interface into another interface, e.g.
    293         * mix-in application-specific interfaces into HttpServletRequest.
    294         * Different interfaces can be mixed-in depending on context, which is
    295         * difficult to achieve with wrapping.
    296         * @param objects
    297         * @return
    298         */
    299        public static Object mix(final Object... objects) {
    300                if (objects==null || objects.length==0) {
    301                        return null;
    302                }
    303
    304                if (objects.length==1) {
    305                        return objects[0];
    306                }
    307
    308
    309                // Maps interface to implementation.
    310                final Map<Class<?>, Object> interfaceMap = new HashMap<Class<?>, Object>();
    311                List<Class<?>> interfaces = new ArrayList<Class<?>>();
    312                ClassLoader classLoader = null;
    313                boolean onlyFirstInterfaces = false;
    314
    315                for (int i=0; i<objects.length; ++i) {
    316                        Class<?> sourceClass = objects[i].getClass();
    317                        classLoader=DuckConverterFactory.getChildClassLoader(classLoader, sourceClass.getClassLoader());
    318                        Class<?>[] cia = DuckConverterFactory.getClassInterfaces(sourceClass);
    319                        for (int j=0; j<cia.length; ++j) {
    320                                Class<?> classInterface = cia[j];
    321                                if (!interfaceMap.containsKey(classInterface)) {
    322                                        onlyFirstInterfaces = i==0;
    323                                        interfaceMap.put(classInterface, objects[i]);
    324                                        interfaces.add(classInterface);
    325                                }
    326                        }
    327                }
    328
    329                // All interfaces are implemented by the first object, i.e. other objects add nothing.
    330                if (onlyFirstInterfaces) {
    331                        return objects[0];
    332                }
    333
    334                return Proxy.newProxyInstance(
    335                                classLoader == null ? objects[0].getClass().getClassLoader() : classLoader,
    336                                                interfaces.toArray(new Class[interfaces.size()]),
    337                                                new InvocationHandler() {
    338
    339                                                        public Object invoke(
    340                                                                        Object proxy,
    341                                                                        Method method,
    342                                                                        Object[] args) throws Throwable {
    343
    344                                                                Object target = interfaceMap.get(method.getDeclaringClass());
    345                                                                return method.invoke(target==null ? objects[0] : target, args);
    346                                                        }
    347
    348                                });
    349        }
    350
    351}