View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.proxy2.stub;
19  
20  import java.lang.reflect.Array;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.apache.commons.lang3.ArrayUtils;
25  import org.apache.commons.lang3.Validate;
26  import org.apache.commons.lang3.reflect.TypeUtils;
27  import org.apache.commons.proxy2.Interceptor;
28  import org.apache.commons.proxy2.ObjectProvider;
29  import org.apache.commons.proxy2.interceptor.InterceptorUtils;
30  import org.apache.commons.proxy2.interceptor.matcher.ArgumentMatcher;
31  import org.apache.commons.proxy2.interceptor.matcher.argument.ArgumentMatcherUtils;
32  
33  public abstract class BaseTrainer<S extends BaseTrainer<S, T>, T>
34  {
35      //******************************************************************************************************************
36      // Fields
37      //******************************************************************************************************************
38      public final Class<T> traineeType;
39  
40      //******************************************************************************************************************
41      // Constructors
42      //******************************************************************************************************************
43  
44      /**
45       * Create a new {@link BaseTrainer} instance. This constructor should only be called by classes that explicitly
46       * assign the T parameter in the class definition. This should include basically any runtime-usable class.
47       */
48      protected BaseTrainer()
49      {
50          this(null);
51      }
52  
53      protected BaseTrainer(Class<T> traineeType)
54      {
55          super();
56          if (traineeType != null)
57          {
58              this.traineeType = traineeType;
59              return;
60          }
61          @SuppressWarnings("unchecked") // T is this class's second type parameter; thus the raw type is Class<T>
62          final Class<T> resolvedVariable = (Class<T>) TypeUtils.getRawType(BaseTrainer.class.getTypeParameters()[1],
63                  getClass());
64          Validate.isTrue(resolvedVariable != null, "Trainee type was not specified and could not be calculated for %s",
65                  getClass());
66          this.traineeType = resolvedVariable;
67      }
68  
69      //******************************************************************************************************************
70      // Abstract Methods
71      //******************************************************************************************************************
72  
73      protected abstract void train(T trainee);
74  
75      //******************************************************************************************************************
76      // Other Methods
77      //******************************************************************************************************************
78  
79      protected <R> R any(Class<R> type)
80      {
81          return argThat(ArgumentMatcherUtils.<R> any());
82      }
83  
84      protected <R> R eq(R value)
85      {
86          return argThat(ArgumentMatcherUtils.eq(value));
87      }
88  
89      protected <R> R isInstance(Class<R> type)
90      {
91          return argThat(ArgumentMatcherUtils.<R> isA(type));
92      }
93  
94      protected <R> R argThat(ArgumentMatcher<R> matcher)
95      {
96          trainingContext().record(matcher);
97          return null;
98      }
99  
100     protected void thenThrow(Exception e)
101     {
102         trainingContext().then(InterceptorUtils.throwing(e));
103     }
104 
105     protected void thenThrow(ObjectProvider<? extends Exception> provider)
106     {
107         trainingContext().then(InterceptorUtils.throwing(provider));
108     }
109 
110     protected TrainingContext trainingContext()
111     {
112         return TrainingContext.current();
113     }
114 
115     public <R> WhenObject<R> when(R expression)
116     {
117         return new WhenObject<R>();
118     }
119 
120     public WhenClass when(Class<?> expression)
121     {
122         return new WhenClass();
123     }
124 
125     public WhenByteArray when(byte[] expression)
126     {
127         return new WhenByteArray();
128     }
129 
130     public WhenBooleanArray when(boolean[] expression)
131     {
132         return new WhenBooleanArray();
133     }
134 
135     public WhenIntArray when(int[] expression)
136     {
137         return new WhenIntArray();
138     }
139 
140     public WhenShortArray when(short[] expresssion)
141     {
142         return new WhenShortArray();
143     }
144 
145     public WhenLongArray when(long[] expression)
146     {
147         return new WhenLongArray();
148     }
149 
150     public WhenFloatArray when(float[] expression)
151     {
152         return new WhenFloatArray();
153     }
154 
155     public WhenDoubleArray when(double[] expression)
156     {
157         return new WhenDoubleArray();
158     }
159 
160     public <R> WhenObjectArray<R> when(R[] expression)
161     {
162         @SuppressWarnings("unchecked") // we can reasonably say that the component type of an R[] is Class<? extends R>:
163         final Class<? extends R> componentType = (Class<? extends R>) expression.getClass().getComponentType();
164         return new WhenObjectArray<R>(componentType);
165     }
166 
167     public WhenCharArray when(char[] expression)
168     {
169         return new WhenCharArray();
170     }
171 
172     protected S self()
173     {
174         @SuppressWarnings("unchecked") // S is our "self" type parameter
175         final S self = (S) this;
176         return self;
177     }
178 
179     //******************************************************************************************************************
180     // Inner Classes
181     //******************************************************************************************************************
182 
183     protected abstract class BaseWhen<R>
184     {
185         public S thenThrow(Exception e)
186         {
187             return then(InterceptorUtils.throwing(e));
188         }
189 
190         public S thenThrow(ObjectProvider<? extends Exception> provider)
191         {
192             return then(InterceptorUtils.throwing(provider));
193         }
194 
195         public S thenAnswer(ObjectProvider<? extends R> provider)
196         {
197             return then(InterceptorUtils.provider(provider));
198         }
199 
200         public S then(Interceptor interceptor)
201         {
202             trainingContext().then(interceptor);
203             return self();
204         }
205     }
206 
207     protected class WhenBooleanArray extends BaseWhen<boolean[]>
208     {
209         public S thenReturn(boolean... values)
210         {
211             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
212             return self();
213         }
214     }
215 
216     protected class WhenByteArray extends BaseWhen<byte[]>
217     {
218         public S thenReturn(byte... values)
219         {
220             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
221             return self();
222         }
223     }
224 
225     protected class WhenCharArray extends BaseWhen<char[]>
226     {
227         public S thenReturn(char... values)
228         {
229             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
230             return self();
231         }
232     }
233 
234     protected class WhenDoubleArray extends BaseWhen<double[]>
235     {
236         public S thenReturn(double... values)
237         {
238             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
239             return self();
240         }
241     }
242 
243     protected class WhenFloatArray extends BaseWhen<float[]>
244     {
245         public S thenReturn(float... values)
246         {
247             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
248             return self();
249         }
250     }
251 
252     protected class WhenIntArray extends BaseWhen<int[]>
253     {
254         public S thenReturn(int... values)
255         {
256             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
257             return self();
258         }
259     }
260 
261     protected class WhenLongArray extends BaseWhen<long[]>
262     {
263         public S thenReturn(long... values)
264         {
265             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
266             return self();
267         }
268     }
269 
270     protected class WhenObject<R> extends BaseWhen<R>
271     {
272         public S thenReturn(R value)
273         {
274             trainingContext().then(InterceptorUtils.constant(value));
275             return self();
276         }
277 
278         public S thenStub(BaseTrainer<?, R> trainer)
279         {
280             final R trainee = trainingContext().push(trainer.traineeType);
281             trainer.train(trainee);
282             trainingContext().then(InterceptorUtils.constant(trainingContext().pop()));
283             return self();
284         }
285     }
286 
287     /**
288      * Intermediate result of a when(Class) call. Provided because it is such a common case to have a mismatch between a
289      * declared Class<?> return type and the bound parameter of a class literal.
290      */
291     protected class WhenClass extends BaseWhen<Class<?>>
292     {
293         public S thenReturn(Class<?> value)
294         {
295             trainingContext().then(InterceptorUtils.constant(value));
296             return self();
297         }
298     }
299 
300     protected class WhenObjectArray<R> extends BaseWhen<R[]>
301     {
302         protected final Class<? extends R> componentType;
303 
304         protected WhenObjectArray(Class<? extends R> componentType)
305         {
306             this.componentType = componentType;
307         }
308 
309         public S thenReturn(R... values)
310         {
311             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
312             return self();
313         }
314 
315         public StubArrayBuilder<R> thenBuildArray()
316         {
317             return new StubArrayBuilder<R>(componentType);
318         }
319     }
320 
321     protected class StubArrayBuilder<R>
322     {
323         protected final List<R> elements = new ArrayList<R>();
324         protected final Class<? extends R> componentType;
325 
326         protected StubArrayBuilder(Class<? extends R> componentType)
327         {
328             this.componentType = componentType;
329         }
330 
331         public StubArrayBuilder<R> addElement(BaseTrainer<?, R> trainer)
332         {
333             final R trainee = trainingContext().push(trainer.traineeType);
334             trainer.train(trainee);
335             elements.add(trainingContext().<R> pop());
336             return this;
337         }
338 
339         public S build()
340         {
341             @SuppressWarnings("unchecked") // an array of component type ? extends R is assignable to R[]:
342             final R[] array = elements.toArray((R[]) Array.newInstance(componentType, elements.size()));
343             trainingContext().then(InterceptorUtils.constant(array));
344             return self();
345         }
346     }
347 
348     protected class WhenShortArray extends BaseWhen<short[]>
349     {
350         public S thenReturn(short... values)
351         {
352             trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values)));
353             return self();
354         }
355     }
356 }