1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.proxy2.stub;
18
19 import java.lang.reflect.Array;
20 import java.lang.reflect.Method;
21 import java.util.Arrays;
22 import java.util.Deque;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.UUID;
26
27 import org.apache.commons.lang3.ArrayUtils;
28 import org.apache.commons.proxy2.Interceptor;
29 import org.apache.commons.proxy2.Invocation;
30 import org.apache.commons.proxy2.Invoker;
31 import org.apache.commons.proxy2.ProxyFactory;
32 import org.apache.commons.proxy2.ProxyUtils;
33 import org.apache.commons.proxy2.interceptor.SwitchInterceptor;
34 import org.apache.commons.proxy2.interceptor.matcher.ArgumentMatcher;
35 import org.apache.commons.proxy2.interceptor.matcher.InvocationMatcher;
36 import org.apache.commons.proxy2.invoker.NullInvoker;
37 import org.apache.commons.proxy2.invoker.RecordedInvocation;
38
39 class TrainingContext
40 {
41
42
43
44
45 private static final ThreadLocal<TrainingContext> TRAINING_CONTEXT = new ThreadLocal<TrainingContext>();
46
47 private final ProxyFactory proxyFactory;
48
49 private final Deque<TrainingContextFrame<?>> frameDeque = new LinkedList<TrainingContextFrame<?>>();
50
51 private final TrainingContext resume;
52
53
54
55
56
57 static TrainingContext current()
58 {
59 return TRAINING_CONTEXT.get();
60 }
61
62 static synchronized TrainingContext join(ProxyFactory proxyFactory)
63 {
64 final TrainingContext context = new TrainingContext(proxyFactory);
65 TRAINING_CONTEXT.set(context);
66 return context;
67 }
68
69
70
71
72
73 private TrainingContext(ProxyFactory proxyFactory)
74 {
75 this.proxyFactory = proxyFactory;
76 this.resume = current();
77 }
78
79
80
81
82
83 void part()
84 {
85 synchronized (TRAINING_CONTEXT)
86 {
87 if (resume == null)
88 {
89 TRAINING_CONTEXT.remove();
90 }
91 else
92 {
93 TRAINING_CONTEXT.set(resume);
94 }
95 }
96 }
97
98 private TrainingContextFrame<?> peek()
99 {
100 return frameDeque.peek();
101 }
102
103 <T> T pop()
104 {
105 return pop(NullInvoker.INSTANCE);
106 }
107
108 <T> T pop(Invoker invoker)
109 {
110 final TrainingContextFrame<?> frame = frameDeque.pop();
111 return proxyFactory.createInterceptorProxy(proxyFactory.createInvokerProxy(invoker, frame.type),
112 frame.stubInterceptor, frame.type);
113 }
114
115 <T> T push(Class<T> type)
116 {
117 return push(type, new SwitchInterceptor());
118 }
119
120 <T> T push(Class<T> type, SwitchInterceptor switchInterceptor)
121 {
122 TrainingContextFrame<T> frame = new TrainingContextFrame<T>(type, switchInterceptor);
123 Invoker invoker = new TrainingInvoker(frame);
124 frameDeque.push(frame);
125 return proxyFactory.createInvokerProxy(invoker, type);
126 }
127
128 void record(ArgumentMatcher<?> argumentMatcher)
129 {
130 peek().argumentMatchers.add(argumentMatcher);
131 }
132
133 void then(Interceptor interceptor)
134 {
135 peek().then(interceptor);
136 }
137
138
139
140
141
142 private static final class ExactArgumentsMatcher implements InvocationMatcher
143 {
144 private final RecordedInvocation recordedInvocation;
145
146 private ExactArgumentsMatcher(RecordedInvocation recordedInvocation)
147 {
148 this.recordedInvocation = recordedInvocation;
149 }
150
151 @Override
152 public boolean matches(Invocation invocation)
153 {
154 return invocation.getMethod().equals(recordedInvocation.getInvokedMethod())
155 && Arrays.deepEquals(invocation.getArguments(), recordedInvocation.getArguments());
156 }
157 }
158
159 private static final class MatchingArgumentsMatcher implements InvocationMatcher
160 {
161 private final RecordedInvocation recordedInvocation;
162 private final ArgumentMatcher<?>[] matchers;
163
164 private MatchingArgumentsMatcher(RecordedInvocation recordedInvocation, ArgumentMatcher<?>[] matchers)
165 {
166 this.recordedInvocation = recordedInvocation;
167 this.matchers = ArrayUtils.clone(matchers);
168 }
169
170 @Override
171 public boolean matches(Invocation invocation)
172 {
173 return invocation.getMethod().equals(recordedInvocation.getInvokedMethod())
174 && allArgumentsMatch(invocation.getArguments());
175 }
176
177 private boolean allArgumentsMatch(Object[] arguments)
178 {
179 for (int i = 0; i < arguments.length; i++)
180 {
181 Object argument = arguments[i];
182 @SuppressWarnings({ "rawtypes", "unchecked"})
183 final boolean matches = ((ArgumentMatcher) matchers[i]).matches(argument);
184 if (!matches)
185 {
186 return false;
187 }
188 }
189 return true;
190 }
191 }
192
193 private static final class TrainingContextFrame<T>
194 {
195 private final String id = UUID.randomUUID().toString();
196
197 private final SwitchInterceptor stubInterceptor;
198
199 private final List<ArgumentMatcher<?>> argumentMatchers = new LinkedList<ArgumentMatcher<?>>();
200
201 private InvocationMatcher matcher = null;
202
203 private final Class<T> type;
204
205 private TrainingContextFrame(Class<T> type, SwitchInterceptor stubInterceptor)
206 {
207 this.type = type;
208 this.stubInterceptor = stubInterceptor;
209 }
210
211 private String getId()
212 {
213 return id;
214 }
215
216 void then(Interceptor thenInterceptor)
217 {
218 if (matcher == null)
219 {
220 throw new IllegalStateException("No when!");
221 }
222 stubInterceptor.when(matcher).then(thenInterceptor);
223 matcher = null;
224 }
225
226 void methodInvoked(Method method, Object[] arguments)
227 {
228 final ArgumentMatcher<?>[] matchersArray = argumentMatchers.toArray(new ArgumentMatcher[argumentMatchers
229 .size()]);
230 argumentMatchers.clear();
231 final RecordedInvocation invocation = new RecordedInvocation(method, arguments);
232 if (ArrayUtils.isEmpty(matchersArray))
233 {
234 this.matcher = new ExactArgumentsMatcher(invocation);
235 }
236 else if (matchersArray.length == arguments.length)
237 {
238 this.matcher = new MatchingArgumentsMatcher(invocation, matchersArray);
239 }
240 else
241 {
242 throw new IllegalStateException("Either use exact arguments or argument matchers, but not both.");
243 }
244 }
245 }
246
247 private static final class TrainingInvoker implements Invoker
248 {
249 private static final long serialVersionUID = 1L;
250
251 private final String id;
252
253 private TrainingInvoker(TrainingContextFrame<?> frame)
254 {
255 this.id = frame.getId();
256 }
257
258 @Override
259 public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable
260 {
261 final TrainingContextFrame<?> frame = current().peek();
262 if (!frame.getId().equals(id))
263 {
264 throw new IllegalStateException("Wrong stub!");
265 }
266 else
267 {
268 frame.methodInvoked(method, arguments);
269 }
270
271 final Class<?> type = method.getReturnType();
272
273 if (Object[].class.isAssignableFrom(type))
274 {
275 return Array.newInstance(type.getComponentType(), 0);
276 }
277 return ProxyUtils.nullValue(type);
278 }
279 }
280 }