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