1 package com.atlassian.core.test.util;
2
3 import java.lang.reflect.*;
4 import java.util.*;
5
6 /**
7 * Utility for getting a proxy that delegates to a list of delegates in order. The delegates are queried whether they
8 * implement a particular method and if so, it is called and the result returned. If they don't, the next handler is
9 * queried and so forth. If none of the objects implement the interface then the UnimplementedMethodhandler is invoked.
10 * None of the delegates need to actually implement the proxied interface, only have a method with the same signature.
11 * <p>
12 * So for instance, given:
13 *
14 * <pre>
15 * interface MyInterface
16 * {
17 * String getString();
18 *
19 * Long getLong();
20 *
21 * Integer getInteger();
22 * }
23 * </pre>
24 *
25 * you can create a proxy for this interface without needing to implement the whole thing:
26 *
27 * <pre>
28 * Object mocker = new Object()
29 * {
30 * public String myMethod()
31 * {
32 * return "proxied";
33 * }
34 * }
35 * MyInterface mock = (MyInterface) DuckTypeProxy.getProxy(MyInterface.class, mocker);
36 * System.out.println(mock.getString());
37 * </pre>
38 *
39 * prints "proxied" to the console.
40 * <p>
41 * There are {@link UnimplementedMethodHandler facilities for handling unimplemented methods} either by
42 * {@link #RETURN_NULL returning null} or {@link #THROW throwing exceptions by default}.
43 */
44 public class DuckTypeProxy
45 {
46 // -------------------------------------------------------------------------------------------------- static members
47
48 /**
49 * Return null if the method cannot be found.
50 */
51 public static UnimplementedMethodHandler RETURN_NULL = new UnimplementedMethodHandler()
52 {
53 public Object methodNotImplemented(Method method, Object[] args)
54 {
55 return null;
56 }
57 };
58
59 /**
60 * Throw an exception if the method cannot be found.
61 */
62 public static UnimplementedMethodHandler THROW = new UnimplementedMethodHandler()
63 {
64 public Object methodNotImplemented(Method method, Object[] args)
65 {
66 throw new UnsupportedOperationException(method.toString());
67 }
68 };
69
70 // ------------------------------------------------------------------------------------------------- factory methods
71
72 /**
73 * Get a Proxy that checks each of the enclosed objects and calls them if they have a Method of the same signature.
74 * By default this will {@link #THROW throw an UnsupportedOperationException} if the method is not implemented
75 */
76 /* <T> */public static/* T */Object getProxy(/* Class<T> */Class implementingClass, List delegates)
77 {
78 return getProxy(new Class[] { implementingClass }, delegates);
79 }
80
81 /**
82 * Get a Proxy that checks each of the enclosed objects and calls them if they have a Method of the same signature.
83 */
84 /* <T> */public static/* T */Object getProxy(/* Class<T> */Class implementingClass, List delegates,
85 UnimplementedMethodHandler unimplementedMethodHandler)
86 {
87 return getProxy(new Class[] { implementingClass }, delegates, unimplementedMethodHandler);
88 }
89
90 /**
91 * Get a Proxy that checks each of the enclosed objects and calls them if they have a Method of the same signature.
92 * Uses the {@link #THROW} {@link UnimplementedMethodHandler}
93 */
94 public static Object getProxy(Class[] implementingClasses, List delegates)
95 {
96 return getProxy(implementingClasses, delegates, THROW);
97 }
98
99 public static Object getProxy(Class[] implementingClasses, List delegates, UnimplementedMethodHandler unimplementedMethodHandler)
100 {
101 return Proxy.newProxyInstance(DuckTypeProxy.class.getClassLoader(), implementingClasses, new DuckTypeInvocationHandler(delegates,
102 unimplementedMethodHandler));
103 }
104
105 /**
106 * Get a Proxy that checks the enclosed object and calls it if it has a Method of the same signature.
107 * By default this will {@link #THROW throw an UnsupportedOperationException} if the method is not implemented
108 */
109 /* <T> */public static/* T */Object getProxy(/* Class<T> */Class implementingClass, Object delegate)
110 {
111 return getProxy(new Class[] { implementingClass }, Arrays.asList(new Object[] {delegate}));
112 }
113
114 // --------------------------------------------------------------------------------------------------- inner classes
115
116 public interface UnimplementedMethodHandler
117 {
118 Object methodNotImplemented(Method method, Object[] args);
119 }
120
121 /**
122 * The invocation handler that keeps the references to the delegate objects.
123 */
124 private static class DuckTypeInvocationHandler implements InvocationHandler
125 {
126 private final List delegates;
127 private final UnimplementedMethodHandler unimplementedMethodHandler;
128
129 DuckTypeInvocationHandler(List handlers, UnimplementedMethodHandler unimplementedMethodHandler)
130 {
131 this.delegates = Collections.unmodifiableList(new ArrayList(handlers));
132 this.unimplementedMethodHandler = unimplementedMethodHandler;
133 }
134
135 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
136 {
137 for (Iterator it = delegates.iterator(); it.hasNext();)
138 {
139 Object handler = it.next();
140 Method duckTypeMethod;
141 try
142 {
143 duckTypeMethod = handler.getClass().getMethod(method.getName(), method.getParameterTypes());
144 }
145 catch (NoSuchMethodException ignoreAndTryNext)
146 {
147 continue;
148 }
149 try
150 {
151 duckTypeMethod.setAccessible(true);
152 return duckTypeMethod.invoke(handler, args);
153 }
154 catch (IllegalArgumentException ignoreAndContinue)
155 {
156 // ignored
157 }
158 catch (IllegalAccessException ignoreAndContinue)
159 {
160 // ignored
161 }
162 catch (InvocationTargetException e)
163 {
164 throw e.getCause();
165 }
166 }
167 return unimplementedMethodHandler.methodNotImplemented(method, args);
168 }
169 }
170 }