1 /*
2  * Copyright 2001-2009 OFFIS, Tammo Freese
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.easymock.internal;
17 
18 import java.io.Serializable;
19 import java.lang.reflect.Method;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 
24 import org.easymock.IAnswer;
25 import org.easymock.IArgumentMatcher;
26 
27 public class RecordState implements IMocksControlState, Serializable {
28 
29     private static final long serialVersionUID = -5418279681566430252L;
30 
31     private ExpectedInvocation lastInvocation = null;
32 
33     private boolean lastInvocationUsed = true;
34 
35     private Result lastResult;
36 
37     private final IMocksBehavior behavior;
38 
39     private static Map<Class<?>, Object> emptyReturnValues = new HashMap<Class<?>, Object>();
40 
41     static {
emptyReturnValues.put(Void.TYPE, null)42         emptyReturnValues.put(Void.TYPE, null);
emptyReturnValues.put(Boolean.TYPE, Boolean.FALSE)43         emptyReturnValues.put(Boolean.TYPE, Boolean.FALSE);
emptyReturnValues.put(Byte.TYPE, Byte.valueOf((byte) 0))44         emptyReturnValues.put(Byte.TYPE, Byte.valueOf((byte) 0));
emptyReturnValues.put(Short.TYPE, Short.valueOf((short) 0))45         emptyReturnValues.put(Short.TYPE, Short.valueOf((short) 0));
emptyReturnValues.put(Character.TYPE, Character.valueOf((char)0))46         emptyReturnValues.put(Character.TYPE, Character.valueOf((char)0));
emptyReturnValues.put(Integer.TYPE, Integer.valueOf(0))47         emptyReturnValues.put(Integer.TYPE, Integer.valueOf(0));
emptyReturnValues.put(Long.TYPE, Long.valueOf(0))48         emptyReturnValues.put(Long.TYPE, Long.valueOf(0));
emptyReturnValues.put(Float.TYPE, Float.valueOf(0))49         emptyReturnValues.put(Float.TYPE, Float.valueOf(0));
emptyReturnValues.put(Double.TYPE, Double.valueOf(0))50         emptyReturnValues.put(Double.TYPE, Double.valueOf(0));
51     }
52 
53     private static Map<Class<?>, Class<?>> primitiveToWrapperType = new HashMap<Class<?>, Class<?>>();
54 
55     static {
primitiveToWrapperType.put(Boolean.TYPE, Boolean.class)56         primitiveToWrapperType.put(Boolean.TYPE, Boolean.class);
primitiveToWrapperType.put(Byte.TYPE, Byte.class)57         primitiveToWrapperType.put(Byte.TYPE, Byte.class);
primitiveToWrapperType.put(Short.TYPE, Short.class)58         primitiveToWrapperType.put(Short.TYPE, Short.class);
primitiveToWrapperType.put(Character.TYPE, Character.class)59         primitiveToWrapperType.put(Character.TYPE, Character.class);
primitiveToWrapperType.put(Integer.TYPE, Integer.class)60         primitiveToWrapperType.put(Integer.TYPE, Integer.class);
primitiveToWrapperType.put(Long.TYPE, Long.class)61         primitiveToWrapperType.put(Long.TYPE, Long.class);
primitiveToWrapperType.put(Float.TYPE, Float.class)62         primitiveToWrapperType.put(Float.TYPE, Float.class);
primitiveToWrapperType.put(Double.TYPE, Double.class)63         primitiveToWrapperType.put(Double.TYPE, Double.class);
64     }
65 
RecordState(IMocksBehavior behavior)66     public RecordState(IMocksBehavior behavior) {
67         this.behavior = behavior;
68     }
69 
assertRecordState()70     public void assertRecordState() {
71     }
72 
invoke(Invocation invocation)73     public java.lang.Object invoke(Invocation invocation) {
74         closeMethod();
75         List<IArgumentMatcher> lastMatchers = LastControl.pullMatchers();
76         lastInvocation = new ExpectedInvocation(invocation, lastMatchers);
77         lastInvocationUsed = false;
78         return emptyReturnValueFor(invocation.getMethod().getReturnType());
79     }
80 
replay()81     public void replay() {
82         closeMethod();
83         if (LastControl.pullMatchers() != null) {
84             throw new IllegalStateException("matcher calls were used outside expectations");
85         }
86     }
87 
verify()88     public void verify() {
89         throw new RuntimeExceptionWrapper(new IllegalStateException(
90                 "calling verify is not allowed in record state"));
91     }
92 
andReturn(Object value)93     public void andReturn(Object value) {
94         requireMethodCall("return value");
95         value = convertNumberClassIfNeccessary(value);
96         requireAssignable(value);
97         if (lastResult != null) {
98             times(MocksControl.ONCE);
99         }
100         lastResult = Result.createReturnResult(value);
101     }
102 
andThrow(Throwable throwable)103     public void andThrow(Throwable throwable) {
104         requireMethodCall("Throwable");
105         requireValidThrowable(throwable);
106         if (lastResult != null) {
107             times(MocksControl.ONCE);
108         }
109         lastResult = Result.createThrowResult(throwable);
110     }
111 
andAnswer(IAnswer<?> answer)112     public void andAnswer(IAnswer<?> answer) {
113         requireMethodCall("answer");
114         requireValidAnswer(answer);
115         if (lastResult != null) {
116             times(MocksControl.ONCE);
117         }
118         lastResult = Result.createAnswerResult(answer);
119     }
120 
andDelegateTo(Object delegateTo)121     public void andDelegateTo(Object delegateTo) {
122         requireMethodCall("delegate");
123         requireValidDelegation(delegateTo);
124         if (lastResult != null) {
125             times(MocksControl.ONCE);
126         }
127         lastResult = Result.createDelegatingResult(delegateTo);
128     }
129 
andStubReturn(Object value)130     public void andStubReturn(Object value) {
131         requireMethodCall("stub return value");
132         value = convertNumberClassIfNeccessary(value);
133         requireAssignable(value);
134         if (lastResult != null) {
135             times(MocksControl.ONCE);
136         }
137         behavior.addStub(lastInvocation, Result.createReturnResult(value));
138         lastInvocationUsed = true;
139     }
140 
141     @SuppressWarnings("deprecation")
setDefaultReturnValue(Object value)142     public void setDefaultReturnValue(Object value) {
143         requireMethodCall("default return value");
144         value = convertNumberClassIfNeccessary(value);
145         requireAssignable(value);
146         if (lastResult != null) {
147             times(MocksControl.ONCE);
148         }
149         behavior.addStub(
150                 lastInvocation.withMatcher(org.easymock.MockControl.ALWAYS_MATCHER), Result
151                         .createReturnResult(value));
152         lastInvocationUsed = true;
153     }
154 
asStub()155     public void asStub() {
156         requireMethodCall("stub behavior");
157         requireVoidMethod();
158         behavior.addStub(lastInvocation, Result.createReturnResult(null));
159         lastInvocationUsed = true;
160     }
161 
162     @SuppressWarnings("deprecation")
setDefaultVoidCallable()163     public void setDefaultVoidCallable() {
164         requireMethodCall("default void callable");
165         requireVoidMethod();
166         behavior.addStub(
167                 lastInvocation.withMatcher(org.easymock.MockControl.ALWAYS_MATCHER), Result
168                         .createReturnResult(null));
169         lastInvocationUsed = true;
170     }
171 
andStubThrow(Throwable throwable)172     public void andStubThrow(Throwable throwable) {
173         requireMethodCall("stub Throwable");
174         requireValidThrowable(throwable);
175         if (lastResult != null) {
176             times(MocksControl.ONCE);
177         }
178         behavior.addStub(lastInvocation, Result.createThrowResult(throwable));
179         lastInvocationUsed = true;
180     }
181 
182     @SuppressWarnings("deprecation")
setDefaultThrowable(Throwable throwable)183     public void setDefaultThrowable(Throwable throwable) {
184         requireMethodCall("default Throwable");
185         requireValidThrowable(throwable);
186         if (lastResult != null) {
187             times(MocksControl.ONCE);
188         }
189         behavior.addStub(
190                 lastInvocation.withMatcher(org.easymock.MockControl.ALWAYS_MATCHER), Result
191                         .createThrowResult(throwable));
192         lastInvocationUsed = true;
193     }
194 
andStubAnswer(IAnswer<?> answer)195     public void andStubAnswer(IAnswer<?> answer) {
196         requireMethodCall("stub answer");
197         requireValidAnswer(answer);
198         if (lastResult != null) {
199             times(MocksControl.ONCE);
200         }
201         behavior.addStub(lastInvocation, Result.createAnswerResult(answer));
202         lastInvocationUsed = true;
203     }
204 
andStubDelegateTo(Object delegateTo)205     public void andStubDelegateTo(Object delegateTo) {
206         requireMethodCall("stub delegate");
207         requireValidDelegation(delegateTo);
208         if (lastResult != null) {
209             times(MocksControl.ONCE);
210         }
211         behavior.addStub(lastInvocation, Result
212                 .createDelegatingResult(delegateTo));
213         lastInvocationUsed = true;
214     }
215 
times(Range range)216     public void times(Range range) {
217         requireMethodCall("times");
218         requireLastResultOrVoidMethod();
219 
220         behavior.addExpected(lastInvocation, lastResult != null ? lastResult
221                 : Result.createReturnResult(null), range);
222         lastInvocationUsed = true;
223         lastResult = null;
224     }
225 
createNumberObject(Object value, Class<?> returnType)226     private Object createNumberObject(Object value, Class<?> returnType) {
227         if (!(value instanceof Number)) {
228             return value;
229         }
230         Number number = (Number) value;
231         if (returnType.equals(Byte.TYPE)) {
232             return number.byteValue();
233         } else if (returnType.equals(Short.TYPE)) {
234             return number.shortValue();
235         } else if (returnType.equals(Character.TYPE)) {
236             return (char) number.intValue();
237         } else if (returnType.equals(Integer.TYPE)) {
238             return number.intValue();
239         } else if (returnType.equals(Long.TYPE)) {
240             return number.longValue();
241         } else if (returnType.equals(Float.TYPE)) {
242             return number.floatValue();
243         } else if (returnType.equals(Double.TYPE)) {
244             return number.doubleValue();
245         } else {
246             return number;
247         }
248     }
249 
convertNumberClassIfNeccessary(Object o)250     private Object convertNumberClassIfNeccessary(Object o) {
251         Class<?> returnType = lastInvocation.getMethod().getReturnType();
252         return createNumberObject(o, returnType);
253     }
254 
255     @SuppressWarnings("deprecation")
closeMethod()256     private void closeMethod() {
257         if (lastInvocationUsed && lastResult == null) {
258             return;
259         }
260         if (!isLastResultOrVoidMethod()) {
261             throw new RuntimeExceptionWrapper(new IllegalStateException(
262                     "missing behavior definition for the preceding method call "
263                             + lastInvocation.toString()));
264         }
265         this.times(org.easymock.MockControl.ONE);
266     }
267 
emptyReturnValueFor(Class<?> type)268     public static Object emptyReturnValueFor(Class<?> type) {
269         return type.isPrimitive() ? emptyReturnValues.get(type) : null;
270     }
271 
requireMethodCall(String failMessage)272     private void requireMethodCall(String failMessage) {
273         if (lastInvocation == null) {
274             throw new RuntimeExceptionWrapper(new IllegalStateException(
275                     "method call on the mock needed before setting "
276                             + failMessage));
277         }
278     }
279 
requireAssignable(Object returnValue)280     private void requireAssignable(Object returnValue) {
281         if (lastMethodIsVoidMethod()) {
282             throw new RuntimeExceptionWrapper(new IllegalStateException(
283                     "void method cannot return a value"));
284         }
285         if (returnValue == null) {
286             return;
287         }
288         Class<?> returnedType = lastInvocation.getMethod().getReturnType();
289         if (returnedType.isPrimitive()) {
290             returnedType = primitiveToWrapperType.get(returnedType);
291 
292         }
293         if (!returnedType.isAssignableFrom(returnValue.getClass())) {
294             throw new RuntimeExceptionWrapper(new IllegalStateException(
295                     "incompatible return value type"));
296         }
297     }
298 
requireValidThrowable(Throwable throwable)299     private void requireValidThrowable(Throwable throwable) {
300         if (throwable == null)
301             throw new RuntimeExceptionWrapper(new NullPointerException(
302                     "null cannot be thrown"));
303         if (isValidThrowable(throwable))
304             return;
305 
306         throw new RuntimeExceptionWrapper(new IllegalArgumentException(
307                 "last method called on mock cannot throw "
308                         + throwable.getClass().getName()));
309     }
310 
requireValidAnswer(IAnswer<?> answer)311     private void requireValidAnswer(IAnswer<?> answer) {
312         if (answer == null)
313             throw new RuntimeExceptionWrapper(new NullPointerException(
314                     "answer object must not be null"));
315     }
316 
requireValidDelegation(Object delegateTo)317     private void requireValidDelegation(Object delegateTo) {
318         if (delegateTo == null)
319             throw new RuntimeExceptionWrapper(new NullPointerException(
320                     "delegated to object must not be null"));
321         // Would be nice to validate delegateTo is implementing the mock
322         // interface (not possible right now)
323     }
324 
requireLastResultOrVoidMethod()325     private void requireLastResultOrVoidMethod() {
326         if (isLastResultOrVoidMethod()) {
327             return;
328         }
329         throw new RuntimeExceptionWrapper(new IllegalStateException(
330                 "last method called on mock is not a void method"));
331     }
332 
requireVoidMethod()333     private void requireVoidMethod() {
334         if (lastMethodIsVoidMethod()) {
335             return;
336         }
337         throw new RuntimeExceptionWrapper(new IllegalStateException(
338                 "last method called on mock is not a void method"));
339     }
340 
isLastResultOrVoidMethod()341     private boolean isLastResultOrVoidMethod() {
342         return lastResult != null || lastMethodIsVoidMethod();
343     }
344 
lastMethodIsVoidMethod()345     private boolean lastMethodIsVoidMethod() {
346         Class<?> returnType = lastInvocation.getMethod().getReturnType();
347         return returnType.equals(Void.TYPE);
348     }
349 
isValidThrowable(Throwable throwable)350     private boolean isValidThrowable(Throwable throwable) {
351         if (throwable instanceof RuntimeException) {
352             return true;
353         }
354         if (throwable instanceof Error) {
355             return true;
356         }
357         Class<?>[] exceptions = lastInvocation.getMethod().getExceptionTypes();
358         Class<?> throwableClass = throwable.getClass();
359         for (Class<?> exception : exceptions) {
360             if (exception.isAssignableFrom(throwableClass))
361                 return true;
362         }
363         return false;
364     }
365 
checkOrder(boolean value)366     public void checkOrder(boolean value) {
367         closeMethod();
368         behavior.checkOrder(value);
369     }
370 
makeThreadSafe(boolean threadSafe)371     public void makeThreadSafe(boolean threadSafe) {
372         behavior.makeThreadSafe(threadSafe);
373     }
374 
checkIsUsedInOneThread(boolean shouldBeUsedInOneThread)375     public void checkIsUsedInOneThread(boolean shouldBeUsedInOneThread) {
376         behavior.shouldBeUsedInOneThread(shouldBeUsedInOneThread);
377     }
378 
379     @SuppressWarnings("deprecation")
setDefaultMatcher(org.easymock.ArgumentsMatcher matcher)380     public void setDefaultMatcher(org.easymock.ArgumentsMatcher matcher) {
381         behavior.setDefaultMatcher(matcher);
382     }
383 
384     @SuppressWarnings("deprecation")
setMatcher(Method method, org.easymock.ArgumentsMatcher matcher)385     public void setMatcher(Method method, org.easymock.ArgumentsMatcher matcher) {
386         requireMethodCall("matcher");
387         behavior.setMatcher(lastInvocation.getMethod(), matcher);
388     }
389 }