blob: 433b40ee25de2e0b3fac68af90f09db4cf17c8fb [file] [log] [blame]
/*
* Copyright 2001-2009 OFFIS, Tammo Freese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.easymock.internal;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.easymock.IAnswer;
import org.easymock.IArgumentMatcher;
public class RecordState implements IMocksControlState, Serializable {
private static final long serialVersionUID = -5418279681566430252L;
private ExpectedInvocation lastInvocation = null;
private boolean lastInvocationUsed = true;
private Result lastResult;
private final IMocksBehavior behavior;
private static Map<Class<?>, Object> emptyReturnValues = new HashMap<Class<?>, Object>();
static {
emptyReturnValues.put(Void.TYPE, null);
emptyReturnValues.put(Boolean.TYPE, Boolean.FALSE);
emptyReturnValues.put(Byte.TYPE, Byte.valueOf((byte) 0));
emptyReturnValues.put(Short.TYPE, Short.valueOf((short) 0));
emptyReturnValues.put(Character.TYPE, Character.valueOf((char)0));
emptyReturnValues.put(Integer.TYPE, Integer.valueOf(0));
emptyReturnValues.put(Long.TYPE, Long.valueOf(0));
emptyReturnValues.put(Float.TYPE, Float.valueOf(0));
emptyReturnValues.put(Double.TYPE, Double.valueOf(0));
}
private static Map<Class<?>, Class<?>> primitiveToWrapperType = new HashMap<Class<?>, Class<?>>();
static {
primitiveToWrapperType.put(Boolean.TYPE, Boolean.class);
primitiveToWrapperType.put(Byte.TYPE, Byte.class);
primitiveToWrapperType.put(Short.TYPE, Short.class);
primitiveToWrapperType.put(Character.TYPE, Character.class);
primitiveToWrapperType.put(Integer.TYPE, Integer.class);
primitiveToWrapperType.put(Long.TYPE, Long.class);
primitiveToWrapperType.put(Float.TYPE, Float.class);
primitiveToWrapperType.put(Double.TYPE, Double.class);
}
public RecordState(IMocksBehavior behavior) {
this.behavior = behavior;
}
public void assertRecordState() {
}
public java.lang.Object invoke(Invocation invocation) {
closeMethod();
List<IArgumentMatcher> lastMatchers = LastControl.pullMatchers();
lastInvocation = new ExpectedInvocation(invocation, lastMatchers);
lastInvocationUsed = false;
return emptyReturnValueFor(invocation.getMethod().getReturnType());
}
public void replay() {
closeMethod();
if (LastControl.pullMatchers() != null) {
throw new IllegalStateException("matcher calls were used outside expectations");
}
}
public void verify() {
throw new RuntimeExceptionWrapper(new IllegalStateException(
"calling verify is not allowed in record state"));
}
public void andReturn(Object value) {
requireMethodCall("return value");
value = convertNumberClassIfNeccessary(value);
requireAssignable(value);
if (lastResult != null) {
times(MocksControl.ONCE);
}
lastResult = Result.createReturnResult(value);
}
public void andThrow(Throwable throwable) {
requireMethodCall("Throwable");
requireValidThrowable(throwable);
if (lastResult != null) {
times(MocksControl.ONCE);
}
lastResult = Result.createThrowResult(throwable);
}
public void andAnswer(IAnswer<?> answer) {
requireMethodCall("answer");
requireValidAnswer(answer);
if (lastResult != null) {
times(MocksControl.ONCE);
}
lastResult = Result.createAnswerResult(answer);
}
public void andDelegateTo(Object delegateTo) {
requireMethodCall("delegate");
requireValidDelegation(delegateTo);
if (lastResult != null) {
times(MocksControl.ONCE);
}
lastResult = Result.createDelegatingResult(delegateTo);
}
public void andStubReturn(Object value) {
requireMethodCall("stub return value");
value = convertNumberClassIfNeccessary(value);
requireAssignable(value);
if (lastResult != null) {
times(MocksControl.ONCE);
}
behavior.addStub(lastInvocation, Result.createReturnResult(value));
lastInvocationUsed = true;
}
@SuppressWarnings("deprecation")
public void setDefaultReturnValue(Object value) {
requireMethodCall("default return value");
value = convertNumberClassIfNeccessary(value);
requireAssignable(value);
if (lastResult != null) {
times(MocksControl.ONCE);
}
behavior.addStub(
lastInvocation.withMatcher(org.easymock.MockControl.ALWAYS_MATCHER), Result
.createReturnResult(value));
lastInvocationUsed = true;
}
public void asStub() {
requireMethodCall("stub behavior");
requireVoidMethod();
behavior.addStub(lastInvocation, Result.createReturnResult(null));
lastInvocationUsed = true;
}
@SuppressWarnings("deprecation")
public void setDefaultVoidCallable() {
requireMethodCall("default void callable");
requireVoidMethod();
behavior.addStub(
lastInvocation.withMatcher(org.easymock.MockControl.ALWAYS_MATCHER), Result
.createReturnResult(null));
lastInvocationUsed = true;
}
public void andStubThrow(Throwable throwable) {
requireMethodCall("stub Throwable");
requireValidThrowable(throwable);
if (lastResult != null) {
times(MocksControl.ONCE);
}
behavior.addStub(lastInvocation, Result.createThrowResult(throwable));
lastInvocationUsed = true;
}
@SuppressWarnings("deprecation")
public void setDefaultThrowable(Throwable throwable) {
requireMethodCall("default Throwable");
requireValidThrowable(throwable);
if (lastResult != null) {
times(MocksControl.ONCE);
}
behavior.addStub(
lastInvocation.withMatcher(org.easymock.MockControl.ALWAYS_MATCHER), Result
.createThrowResult(throwable));
lastInvocationUsed = true;
}
public void andStubAnswer(IAnswer<?> answer) {
requireMethodCall("stub answer");
requireValidAnswer(answer);
if (lastResult != null) {
times(MocksControl.ONCE);
}
behavior.addStub(lastInvocation, Result.createAnswerResult(answer));
lastInvocationUsed = true;
}
public void andStubDelegateTo(Object delegateTo) {
requireMethodCall("stub delegate");
requireValidDelegation(delegateTo);
if (lastResult != null) {
times(MocksControl.ONCE);
}
behavior.addStub(lastInvocation, Result
.createDelegatingResult(delegateTo));
lastInvocationUsed = true;
}
public void times(Range range) {
requireMethodCall("times");
requireLastResultOrVoidMethod();
behavior.addExpected(lastInvocation, lastResult != null ? lastResult
: Result.createReturnResult(null), range);
lastInvocationUsed = true;
lastResult = null;
}
private Object createNumberObject(Object value, Class<?> returnType) {
if (!(value instanceof Number)) {
return value;
}
Number number = (Number) value;
if (returnType.equals(Byte.TYPE)) {
return number.byteValue();
} else if (returnType.equals(Short.TYPE)) {
return number.shortValue();
} else if (returnType.equals(Character.TYPE)) {
return (char) number.intValue();
} else if (returnType.equals(Integer.TYPE)) {
return number.intValue();
} else if (returnType.equals(Long.TYPE)) {
return number.longValue();
} else if (returnType.equals(Float.TYPE)) {
return number.floatValue();
} else if (returnType.equals(Double.TYPE)) {
return number.doubleValue();
} else {
return number;
}
}
private Object convertNumberClassIfNeccessary(Object o) {
Class<?> returnType = lastInvocation.getMethod().getReturnType();
return createNumberObject(o, returnType);
}
@SuppressWarnings("deprecation")
private void closeMethod() {
if (lastInvocationUsed && lastResult == null) {
return;
}
if (!isLastResultOrVoidMethod()) {
throw new RuntimeExceptionWrapper(new IllegalStateException(
"missing behavior definition for the preceding method call "
+ lastInvocation.toString()));
}
this.times(org.easymock.MockControl.ONE);
}
public static Object emptyReturnValueFor(Class<?> type) {
return type.isPrimitive() ? emptyReturnValues.get(type) : null;
}
private void requireMethodCall(String failMessage) {
if (lastInvocation == null) {
throw new RuntimeExceptionWrapper(new IllegalStateException(
"method call on the mock needed before setting "
+ failMessage));
}
}
private void requireAssignable(Object returnValue) {
if (lastMethodIsVoidMethod()) {
throw new RuntimeExceptionWrapper(new IllegalStateException(
"void method cannot return a value"));
}
if (returnValue == null) {
return;
}
Class<?> returnedType = lastInvocation.getMethod().getReturnType();
if (returnedType.isPrimitive()) {
returnedType = primitiveToWrapperType.get(returnedType);
}
if (!returnedType.isAssignableFrom(returnValue.getClass())) {
throw new RuntimeExceptionWrapper(new IllegalStateException(
"incompatible return value type"));
}
}
private void requireValidThrowable(Throwable throwable) {
if (throwable == null)
throw new RuntimeExceptionWrapper(new NullPointerException(
"null cannot be thrown"));
if (isValidThrowable(throwable))
return;
throw new RuntimeExceptionWrapper(new IllegalArgumentException(
"last method called on mock cannot throw "
+ throwable.getClass().getName()));
}
private void requireValidAnswer(IAnswer<?> answer) {
if (answer == null)
throw new RuntimeExceptionWrapper(new NullPointerException(
"answer object must not be null"));
}
private void requireValidDelegation(Object delegateTo) {
if (delegateTo == null)
throw new RuntimeExceptionWrapper(new NullPointerException(
"delegated to object must not be null"));
// Would be nice to validate delegateTo is implementing the mock
// interface (not possible right now)
}
private void requireLastResultOrVoidMethod() {
if (isLastResultOrVoidMethod()) {
return;
}
throw new RuntimeExceptionWrapper(new IllegalStateException(
"last method called on mock is not a void method"));
}
private void requireVoidMethod() {
if (lastMethodIsVoidMethod()) {
return;
}
throw new RuntimeExceptionWrapper(new IllegalStateException(
"last method called on mock is not a void method"));
}
private boolean isLastResultOrVoidMethod() {
return lastResult != null || lastMethodIsVoidMethod();
}
private boolean lastMethodIsVoidMethod() {
Class<?> returnType = lastInvocation.getMethod().getReturnType();
return returnType.equals(Void.TYPE);
}
private boolean isValidThrowable(Throwable throwable) {
if (throwable instanceof RuntimeException) {
return true;
}
if (throwable instanceof Error) {
return true;
}
Class<?>[] exceptions = lastInvocation.getMethod().getExceptionTypes();
Class<?> throwableClass = throwable.getClass();
for (Class<?> exception : exceptions) {
if (exception.isAssignableFrom(throwableClass))
return true;
}
return false;
}
public void checkOrder(boolean value) {
closeMethod();
behavior.checkOrder(value);
}
public void makeThreadSafe(boolean threadSafe) {
behavior.makeThreadSafe(threadSafe);
}
public void checkIsUsedInOneThread(boolean shouldBeUsedInOneThread) {
behavior.shouldBeUsedInOneThread(shouldBeUsedInOneThread);
}
@SuppressWarnings("deprecation")
public void setDefaultMatcher(org.easymock.ArgumentsMatcher matcher) {
behavior.setDefaultMatcher(matcher);
}
@SuppressWarnings("deprecation")
public void setMatcher(Method method, org.easymock.ArgumentsMatcher matcher) {
requireMethodCall("matcher");
behavior.setMatcher(lastInvocation.getMethod(), matcher);
}
}