1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.testing;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static junit.framework.Assert.assertEquals;
22 import static junit.framework.Assert.fail;
23
24 import com.google.common.annotations.Beta;
25 import com.google.common.base.Function;
26 import com.google.common.base.Throwables;
27 import com.google.common.collect.Lists;
28 import com.google.common.reflect.AbstractInvocationHandler;
29 import com.google.common.reflect.Reflection;
30
31 import java.lang.reflect.AccessibleObject;
32 import java.lang.reflect.InvocationTargetException;
33 import java.lang.reflect.Method;
34 import java.lang.reflect.Modifier;
35 import java.util.List;
36 import java.util.concurrent.atomic.AtomicInteger;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 @Beta
53 public final class ForwardingWrapperTester {
54
55 private boolean testsEquals = false;
56
57
58
59
60
61 public ForwardingWrapperTester includingEquals() {
62 this.testsEquals = true;
63 return this;
64 }
65
66
67
68
69
70
71 public <T> void testForwarding(
72 Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) {
73 checkNotNull(wrapperFunction);
74 checkArgument(interfaceType.isInterface(), "%s isn't an interface", interfaceType);
75 Method[] methods = getMostConcreteMethods(interfaceType);
76 AccessibleObject.setAccessible(methods, true);
77 for (Method method : methods) {
78
79
80
81 if (!Modifier.isAbstract(method.getModifiers())) {
82 continue;
83 }
84
85
86 if (method.getName().equals("equals")
87 && method.getParameterTypes().length == 1
88 && method.getParameterTypes()[0] == Object.class) {
89 continue;
90 }
91 if (method.getName().equals("hashCode")
92 && method.getParameterTypes().length == 0) {
93 continue;
94 }
95 if (method.getName().equals("toString")
96 && method.getParameterTypes().length == 0) {
97 continue;
98 }
99 testSuccessfulForwarding(interfaceType, method, wrapperFunction);
100 testExceptionPropagation(interfaceType, method, wrapperFunction);
101 }
102 if (testsEquals) {
103 testEquals(interfaceType, wrapperFunction);
104 }
105 testToString(interfaceType, wrapperFunction);
106 }
107
108
109 private static Method[] getMostConcreteMethods(Class<?> type) {
110 Method[] methods = type.getMethods();
111 for (int i = 0; i < methods.length; i++) {
112 try {
113 methods[i] = type.getMethod(methods[i].getName(), methods[i].getParameterTypes());
114 } catch (Exception e) {
115 throw Throwables.propagate(e);
116 }
117 }
118 return methods;
119 }
120
121 private static <T> void testSuccessfulForwarding(
122 Class<T> interfaceType, Method method, Function<? super T, ? extends T> wrapperFunction) {
123 new InteractionTester<T>(interfaceType, method).testInteraction(wrapperFunction);
124 }
125
126 private static <T> void testExceptionPropagation(
127 Class<T> interfaceType, Method method, Function<? super T, ? extends T> wrapperFunction) {
128 final RuntimeException exception = new RuntimeException();
129 T proxy = Reflection.newProxy(interfaceType, new AbstractInvocationHandler() {
130 @Override protected Object handleInvocation(Object p, Method m, Object[] args)
131 throws Throwable {
132 throw exception;
133 }
134 });
135 T wrapper = wrapperFunction.apply(proxy);
136 try {
137 method.invoke(wrapper, getParameterValues(method));
138 fail(method + " failed to throw exception as is.");
139 } catch (InvocationTargetException e) {
140 if (exception != e.getCause()) {
141 throw new RuntimeException(e);
142 }
143 } catch (IllegalAccessException e) {
144 throw new AssertionError(e);
145 }
146 }
147
148 private static <T> void testEquals(
149 Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) {
150 FreshValueGenerator generator = new FreshValueGenerator();
151 T instance = generator.newProxy(interfaceType);
152 new EqualsTester()
153 .addEqualityGroup(wrapperFunction.apply(instance), wrapperFunction.apply(instance))
154 .addEqualityGroup(wrapperFunction.apply(generator.newProxy(interfaceType)))
155
156 .testEquals();
157 }
158
159 private static <T> void testToString(
160 Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) {
161 T proxy = new FreshValueGenerator().newProxy(interfaceType);
162 assertEquals("toString() isn't properly forwarded",
163 proxy.toString(), wrapperFunction.apply(proxy).toString());
164 }
165
166 private static Object[] getParameterValues(Method method) {
167 FreshValueGenerator paramValues = new FreshValueGenerator();
168 final List<Object> passedArgs = Lists.newArrayList();
169 for (Class<?> paramType : method.getParameterTypes()) {
170 passedArgs.add(paramValues.generate(paramType));
171 }
172 return passedArgs.toArray();
173 }
174
175
176 private static final class InteractionTester<T> extends AbstractInvocationHandler {
177
178 private final Class<T> interfaceType;
179 private final Method method;
180 private final Object[] passedArgs;
181 private final Object returnValue;
182 private final AtomicInteger called = new AtomicInteger();
183
184 InteractionTester(Class<T> interfaceType, Method method) {
185 this.interfaceType = interfaceType;
186 this.method = method;
187 this.passedArgs = getParameterValues(method);
188 this.returnValue = new FreshValueGenerator().generate(method.getReturnType());
189 }
190
191 @Override protected Object handleInvocation(Object p, Method calledMethod, Object[] args)
192 throws Throwable {
193 assertEquals(method, calledMethod);
194 assertEquals(method + " invoked more than once.", 0, called.get());
195 for (int i = 0; i < passedArgs.length; i++) {
196 assertEquals("Parameter #" + i + " of " + method + " not forwarded",
197 passedArgs[i], args[i]);
198 }
199 called.getAndIncrement();
200 return returnValue;
201 }
202
203 void testInteraction(Function<? super T, ? extends T> wrapperFunction) {
204 T proxy = Reflection.newProxy(interfaceType, this);
205 T wrapper = wrapperFunction.apply(proxy);
206 boolean isPossibleChainingCall = interfaceType.isAssignableFrom(method.getReturnType());
207 try {
208 Object actualReturnValue = method.invoke(wrapper, passedArgs);
209
210
211 if (!isPossibleChainingCall || wrapper != actualReturnValue) {
212 assertEquals("Return value of " + method + " not forwarded", returnValue,
213 actualReturnValue);
214 }
215 } catch (IllegalAccessException e) {
216 throw new RuntimeException(e);
217 } catch (InvocationTargetException e) {
218 throw Throwables.propagate(e.getCause());
219 }
220 assertEquals("Failed to forward to " + method, 1, called.get());
221 }
222
223 @Override public String toString() {
224 return "dummy " + interfaceType.getSimpleName();
225 }
226 }
227 }