001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.proxy2.stub;
019
020import java.lang.reflect.Array;
021import java.util.ArrayList;
022import java.util.List;
023
024import org.apache.commons.lang3.ArrayUtils;
025import org.apache.commons.lang3.Validate;
026import org.apache.commons.lang3.reflect.TypeUtils;
027import org.apache.commons.proxy2.Interceptor;
028import org.apache.commons.proxy2.ObjectProvider;
029import org.apache.commons.proxy2.interceptor.InterceptorUtils;
030import org.apache.commons.proxy2.interceptor.matcher.ArgumentMatcher;
031import org.apache.commons.proxy2.interceptor.matcher.argument.ArgumentMatcherUtils;
032
033public abstract class BaseTrainer<S extends BaseTrainer<S, T>, T>
034{
035    //******************************************************************************************************************
036    // Fields
037    //******************************************************************************************************************
038    public final Class<T> traineeType;
039
040    //******************************************************************************************************************
041    // Constructors
042    //******************************************************************************************************************
043
044    /**
045     * Create a new {@link BaseTrainer} instance. This constructor should only be called by classes that explicitly
046     * assign the T parameter in the class definition. This should include basically any runtime-usable class.
047     */
048    protected BaseTrainer()
049    {
050        this(null);
051    }
052
053    protected BaseTrainer(Class<T> traineeType)
054    {
055        super();
056        if (traineeType != null)
057        {
058            this.traineeType = traineeType;
059            return;
060        }
061        @SuppressWarnings("unchecked") // T is this class's second type parameter; thus the raw type is Class<T>
062        final Class<T> resolvedVariable = (Class<T>) TypeUtils.getRawType(BaseTrainer.class.getTypeParameters()[1],
063                getClass());
064        Validate.isTrue(resolvedVariable != null, "Trainee type was not specified and could not be calculated for %s",
065                getClass());
066        this.traineeType = resolvedVariable;
067    }
068
069    //******************************************************************************************************************
070    // Abstract Methods
071    //******************************************************************************************************************
072
073    protected abstract void train(T trainee);
074
075    //******************************************************************************************************************
076    // Other Methods
077    //******************************************************************************************************************
078
079    protected <R> R any(Class<R> type)
080    {
081        return argThat(ArgumentMatcherUtils.<R> any());
082    }
083
084    protected <R> R eq(R value)
085    {
086        return argThat(ArgumentMatcherUtils.eq(value));
087    }
088
089    protected <R> R isInstance(Class<R> type)
090    {
091        return argThat(ArgumentMatcherUtils.<R> isA(type));
092    }
093
094    protected <R> R argThat(ArgumentMatcher<R> matcher)
095    {
096        trainingContext().record(matcher);
097        return null;
098    }
099
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}