Home | History | Annotate | Download | only in internal
      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 {
     42         emptyReturnValues.put(Void.TYPE, null);
     43         emptyReturnValues.put(Boolean.TYPE, Boolean.FALSE);
     44         emptyReturnValues.put(Byte.TYPE, Byte.valueOf((byte) 0));
     45         emptyReturnValues.put(Short.TYPE, Short.valueOf((short) 0));
     46         emptyReturnValues.put(Character.TYPE, Character.valueOf((char)0));
     47         emptyReturnValues.put(Integer.TYPE, Integer.valueOf(0));
     48         emptyReturnValues.put(Long.TYPE, Long.valueOf(0));
     49         emptyReturnValues.put(Float.TYPE, Float.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 {
     56         primitiveToWrapperType.put(Boolean.TYPE, Boolean.class);
     57         primitiveToWrapperType.put(Byte.TYPE, Byte.class);
     58         primitiveToWrapperType.put(Short.TYPE, Short.class);
     59         primitiveToWrapperType.put(Character.TYPE, Character.class);
     60         primitiveToWrapperType.put(Integer.TYPE, Integer.class);
     61         primitiveToWrapperType.put(Long.TYPE, Long.class);
     62         primitiveToWrapperType.put(Float.TYPE, Float.class);
     63         primitiveToWrapperType.put(Double.TYPE, Double.class);
     64     }
     65 
     66     public RecordState(IMocksBehavior behavior) {
     67         this.behavior = behavior;
     68     }
     69 
     70     public void assertRecordState() {
     71     }
     72 
     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 
     81     public void replay() {
     82         closeMethod();
     83         if (LastControl.pullMatchers() != null) {
     84             throw new IllegalStateException("matcher calls were used outside expectations");
     85         }
     86     }
     87 
     88     public void verify() {
     89         throw new RuntimeExceptionWrapper(new IllegalStateException(
     90                 "calling verify is not allowed in record state"));
     91     }
     92 
     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 
    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 
    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 
    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 
    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")
    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 
    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")
    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 
    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")
    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 
    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 
    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 
    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 
    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 
    250     private Object convertNumberClassIfNeccessary(Object o) {
    251         Class<?> returnType = lastInvocation.getMethod().getReturnType();
    252         return createNumberObject(o, returnType);
    253     }
    254 
    255     @SuppressWarnings("deprecation")
    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 
    268     public static Object emptyReturnValueFor(Class<?> type) {
    269         return type.isPrimitive() ? emptyReturnValues.get(type) : null;
    270     }
    271 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    341     private boolean isLastResultOrVoidMethod() {
    342         return lastResult != null || lastMethodIsVoidMethod();
    343     }
    344 
    345     private boolean lastMethodIsVoidMethod() {
    346         Class<?> returnType = lastInvocation.getMethod().getReturnType();
    347         return returnType.equals(Void.TYPE);
    348     }
    349 
    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 
    366     public void checkOrder(boolean value) {
    367         closeMethod();
    368         behavior.checkOrder(value);
    369     }
    370 
    371     public void makeThreadSafe(boolean threadSafe) {
    372         behavior.makeThreadSafe(threadSafe);
    373     }
    374 
    375     public void checkIsUsedInOneThread(boolean shouldBeUsedInOneThread) {
    376         behavior.shouldBeUsedInOneThread(shouldBeUsedInOneThread);
    377     }
    378 
    379     @SuppressWarnings("deprecation")
    380     public void setDefaultMatcher(org.easymock.ArgumentsMatcher matcher) {
    381         behavior.setDefaultMatcher(matcher);
    382     }
    383 
    384     @SuppressWarnings("deprecation")
    385     public void setMatcher(Method method, org.easymock.ArgumentsMatcher matcher) {
    386         requireMethodCall("matcher");
    387         behavior.setMatcher(lastInvocation.getMethod(), matcher);
    388     }
    389 }