001 package com.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 /**
016 * Mixes interfaces from multiple objects into one proxy.
017 * @author Pavel
018 *
019 */
020 public 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 }