001: /*******************************************************************************
002: * Copyright (c) 2005, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: * Brad Reynolds (bug 146435)
011: *******************************************************************************/package org.eclipse.ui.tests.harness.util;
012:
013: import java.lang.reflect.InvocationHandler;
014: import java.lang.reflect.Method;
015: import java.lang.reflect.Proxy;
016: import java.util.ArrayList;
017: import java.util.Iterator;
018: import java.util.List;
019:
020: import junit.framework.AssertionFailedError;
021:
022: /**
023: * Utility class for creating mock objects for public interfaces.
024: *
025: * @since 3.3
026: *
027: */
028: public class Mocks {
029:
030: public static interface EqualityComparator {
031: public boolean equals(Object o1, Object o2);
032: }
033:
034: private static EqualityComparator defaultEqualityComparator = new EqualityComparator() {
035: public boolean equals(Object o1, Object o2) {
036: return o1 == null ? o2 == null : o1.equals(o2);
037: }
038: };
039:
040: private static EqualityComparator indifferentEqualityComparator = new EqualityComparator() {
041: public boolean equals(Object o1, Object o2) {
042: return true;
043: }
044: };
045:
046: private static interface Mock {
047: public MockInvocationHandler getMockInvocationHandler();
048: }
049:
050: private static Method getMockInvocationHandlerMethod;
051:
052: private static Method equalsMethod;
053:
054: static {
055: try {
056: getMockInvocationHandlerMethod = Mock.class.getMethod(
057: "getMockInvocationHandler", new Class[0]);
058: equalsMethod = Object.class.getMethod("equals",
059: new Class[] { Object.class });
060: } catch (Exception e) {
061: // ignore, will lead to NullPointerExceptions later on
062: }
063: }
064:
065: private static final class MockInvocationHandler implements
066: InvocationHandler {
067:
068: private class MethodCall {
069: private final Method method;
070:
071: private final Object[] args;
072:
073: private Object returnValue = null;
074:
075: public MethodCall(Method method, Object[] args) {
076: this .method = method;
077: this .args = args;
078: }
079:
080: public boolean equals(Object obj) {
081: if (!(obj instanceof MethodCall)) {
082: return false;
083: }
084: MethodCall other = (MethodCall) obj;
085: if (other.method != method
086: || (other.args == null && args != null)
087: || (other.args != null && args == null)
088: || (args != null && other.args.length != args.length)) {
089: return false;
090: }
091: if (args != null) {
092: for (int i = 0; i < args.length; i++) {
093: if (!equalityComparator.equals(args[i],
094: other.args[i])) {
095: return false;
096: }
097: }
098: }
099: return true;
100: }
101:
102: public void setReturnValue(Object object) {
103: returnValue = object;
104: }
105:
106: public Object getReturnValue() {
107: return returnValue;
108: }
109:
110: public String toString() {
111: return method.toString();
112: }
113: }
114:
115: List previousCallHistory = null;
116:
117: List currentCallHistory = new ArrayList();
118:
119: private final boolean ordered;
120:
121: private final EqualityComparator equalityComparator;
122:
123: public MockInvocationHandler(boolean ordered,
124: EqualityComparator equalityComparator) {
125: this .ordered = ordered;
126: this .equalityComparator = equalityComparator;
127: }
128:
129: public Object invoke(Object proxy, Method method, Object[] args)
130: throws Throwable {
131: if (getMockInvocationHandlerMethod.equals(method)) {
132: return this ;
133: }
134: if (equalsMethod.equals(method)) {
135: return new Boolean(proxy == args[0]);
136: }
137: MethodCall methodCall = new MethodCall(method, args);
138: if (previousCallHistory != null) {
139: // we are in replay mode
140: int indexOfMethodCall = previousCallHistory
141: .indexOf(methodCall);
142: if (indexOfMethodCall != -1) {
143: // copy return value over to this method call
144: methodCall
145: .setReturnValue(((MethodCall) previousCallHistory
146: .get(indexOfMethodCall))
147: .getReturnValue());
148: } else {
149: throw new AssertionFailedError(
150: "unexpected method call: "
151: + method.getName());
152: }
153: if (ordered) {
154: if (previousCallHistory.size() <= currentCallHistory
155: .size()) {
156: throw new AssertionFailedError(
157: "extra method call: "
158: + method.getName());
159: }
160: MethodCall previousCall = (MethodCall) previousCallHistory
161: .get(currentCallHistory.size());
162: if (!methodCall.equals(previousCall)) {
163: throw new AssertionFailedError(
164: "different method call (expected:"
165: + previousCall.method.getName()
166: + ", actual:"
167: + method.getName() + ")");
168: }
169: }
170: }
171: currentCallHistory.add(methodCall);
172: Class returnType = method.getReturnType();
173: if (returnType.isPrimitive() && void.class != returnType) {
174: Object result = null;
175: Object returnValue = methodCall.getReturnValue();
176:
177: if (returnType == boolean.class) {
178: result = (returnValue != null) ? (Boolean) returnValue
179: : Boolean.FALSE;
180: } else if (returnType == byte.class) {
181: result = (returnValue != null) ? (Byte) returnValue
182: : new Byte((byte) 0);
183: } else if (returnType == char.class) {
184: result = (returnValue != null) ? (Character) returnValue
185: : new Character((char) 0);
186: } else if (returnType == short.class) {
187: result = (returnValue != null) ? (Short) returnValue
188: : new Short((short) 0);
189: } else if (returnType == int.class) {
190: result = (returnValue != null) ? (Integer) returnValue
191: : new Integer(0);
192: } else if (returnType == long.class) {
193: result = (returnValue != null) ? (Long) returnValue
194: : new Long(0);
195: } else if (returnType == float.class) {
196: result = (returnValue != null) ? (Float) returnValue
197: : new Float(0);
198: } else if (returnType == double.class) {
199: result = (returnValue != null) ? (Double) returnValue
200: : new Double(0);
201: }
202:
203: return result;
204: }
205: return methodCall.getReturnValue();
206: }
207:
208: public void replay() {
209: previousCallHistory = currentCallHistory;
210: currentCallHistory = new ArrayList();
211: }
212:
213: public void verify() {
214: if (previousCallHistory == null) {
215: if (currentCallHistory.size() == 0) {
216: // mock object was not used at all
217: return;
218: }
219: throw new AssertionFailedError("unexpected");
220: }
221: if (ordered) {
222: int numMissingCalls = previousCallHistory.size()
223: - currentCallHistory.size();
224: if (numMissingCalls > 0) {
225: throw new AssertionFailedError(
226: "missing method calls ("
227: + numMissingCalls
228: + ", first is: "
229: + previousCallHistory
230: .get(currentCallHistory
231: .size()) + ")");
232: }
233: for (int i = 0; i < previousCallHistory.size(); i++) {
234: if (!previousCallHistory.get(i).equals(
235: currentCallHistory.get(i))) {
236: throw new AssertionFailedError(
237: "method call did not match (" + i
238: + " of "
239: + currentCallHistory.size()
240: + ")");
241: }
242: }
243: } else {
244: for (Iterator it = previousCallHistory.iterator(); it
245: .hasNext();) {
246: MethodCall methodCall = (MethodCall) it.next();
247: if (!currentCallHistory.contains(methodCall)) {
248: throw new AssertionFailedError(
249: "missing method call:"
250: + methodCall.method.getName());
251: }
252: }
253: }
254: reset();
255: }
256:
257: public void reset() {
258: previousCallHistory = null;
259: currentCallHistory = new ArrayList();
260: }
261:
262: public void setLastReturnValue(Object object) {
263: MethodCall methodCall = (MethodCall) currentCallHistory
264: .get(currentCallHistory.size() - 1);
265: methodCall.setReturnValue(object);
266: }
267: }
268:
269: /**
270: * Creates a mock object that neither looks at the order of method calls nor
271: * at the arguments.
272: *
273: * @param interfaceType
274: * @return a mock object that checks for the order of method invocations but
275: * not for equality of method arguments
276: */
277: public static Object createRelaxedMock(Class interfaceType) {
278: return createMock(interfaceType, false,
279: indifferentEqualityComparator);
280: }
281:
282: /**
283: * Creates a mock object that does not look at the arguments, but checks
284: * that the order of calls is as expected.
285: *
286: * @param interfaceType
287: * @return a mock object that checks for the order of method invocations but
288: * not for equality of method arguments
289: */
290: public static Object createOrderedMock(Class interfaceType) {
291: return createMock(interfaceType, true,
292: indifferentEqualityComparator);
293: }
294:
295: /**
296: * creates a fussy mock object
297: *
298: * @param interfaceType
299: * @return a mock object that checks for the order of method invocations and
300: * for equality of method arguments
301: */
302: public static Object createMock(Class interfaceType) {
303: return createMock(interfaceType, true,
304: defaultEqualityComparator);
305: }
306:
307: /**
308: * creates a fussy mock object with a comparator
309: *
310: * @param interfaceType
311: * @return a mock object that checks for the order of method invocations and
312: * uses the given comparator to compare method arguments
313: */
314: public static Object createMock(Class interfaceType,
315: EqualityComparator equalityComparator) {
316: return createMock(interfaceType, true, equalityComparator);
317: }
318:
319: private static Object createMock(Class interfaceType,
320: boolean ordered, EqualityComparator equalityComparator) {
321: if (!interfaceType.isInterface()) {
322: throw new IllegalArgumentException();
323: }
324: MockInvocationHandler mockInvocationHandler = new MockInvocationHandler(
325: ordered, equalityComparator);
326: Object newProxyInstance = Proxy.newProxyInstance(Mocks.class
327: .getClassLoader(), new Class[] { interfaceType,
328: Mock.class }, mockInvocationHandler);
329: return newProxyInstance;
330: }
331:
332: public static void startChecking(Object mock) {
333: getMockInvocationHandler(mock).replay();
334: }
335:
336: public static void verify(Object mock) {
337: getMockInvocationHandler(mock).verify();
338: }
339:
340: public static void reset(Object mock) {
341: getMockInvocationHandler(mock).reset();
342: }
343:
344: private static MockInvocationHandler getMockInvocationHandler(
345: Object mock) {
346: return ((Mock) mock).getMockInvocationHandler();
347: }
348:
349: public static void setLastReturnValue(Object mock, Object object) {
350: getMockInvocationHandler(mock).setLastReturnValue(object);
351: }
352: }
|