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}