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 */ 16package org.easymock.internal; 17 18import java.io.Serializable; 19import java.lang.reflect.Method; 20import java.util.HashMap; 21import java.util.List; 22import java.util.Map; 23 24import org.easymock.IAnswer; 25import org.easymock.IArgumentMatcher; 26 27public 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}