001: package net.sf.mockcreator;
002:
003: import java.util.ArrayList;
004: import java.util.Collections;
005: import java.util.HashMap;
006: import java.util.HashSet;
007: import java.util.Iterator;
008: import java.util.LinkedList;
009: import java.util.List;
010: import java.util.Map;
011: import java.util.Set;
012:
013: import net.sf.mockcreator.exceptions.AmbiguousConfigurationException;
014: import net.sf.mockcreator.exceptions.MockException;
015: import net.sf.mockcreator.utils.CompareByValue;
016:
017: public class MockKernel {
018: private static final String IN_PROCESS_ERROR = "MockCore.reset() should be called before each test (e.g. in setUp()).\n"
019: + "In-test setup (i.e. setup after the test has started) is not allowed\n"
020: + "due to a high risk of test interference.";
021:
022: static boolean inBlock;
023: static List expectations;
024:
025: private static boolean inProcess = false;
026: static List dummiesWithParams;
027: static List dummiesWithNoParams;
028: static Set expects;
029:
030: static {
031: reset();
032: }
033:
034: public static void reset() {
035: inBlock = false;
036: expectations = new LinkedList();
037: dummiesWithParams = new ArrayList();
038: dummiesWithNoParams = new ArrayList();
039: expects = new HashSet();
040: inProcess = false;
041:
042: CompareByValue.setTimeTolerance(0);
043: }
044:
045: // ===================================================
046: // Setup methods
047: // ===================================================
048:
049: /**
050: * Registers expected object.method call and expected return value into Core.
051: * @param objMethod Unique marker of class.obj.method.
052: * @param params Expected parameters.
053: * @param returnValue Expected return.
054: */
055: public static IReturnable addExpectedMethodCall(String marker,
056: List params, Object returnValue) {
057: if (inProcess)
058: throw makeException(IN_PROCESS_ERROR);
059:
060: IExpectation exp = new ExpectationWithParams(marker, params,
061: returnValue);
062: if (inBlock) {
063: addExpectationToBlock(exp);
064: } else {
065: expectations.add(exp);
066: }
067:
068: expects.add(marker);
069:
070: return exp;
071: }
072:
073: public static IReturnable addExpectedMethodCall(String marker,
074: List params) {
075: if (inProcess)
076: throw makeException(IN_PROCESS_ERROR);
077: return addExpectedMethodCall(marker, params, null);
078: }
079:
080: public static IReturnable setDummy(String marker, List params) {
081: if (inProcess)
082: throw makeException(IN_PROCESS_ERROR);
083:
084: IExpectation exp = null;
085: if (params == null) {
086: exp = new ExpectationWithNoParams(marker);
087: dummiesWithNoParams.add(exp);
088: Collections.sort(dummiesWithNoParams);
089: } else {
090: exp = new ExpectationWithParams(marker, params);
091: dummiesWithParams.add(exp);
092: Collections.sort(dummiesWithParams);
093: }
094:
095: return exp;
096: }
097:
098: public static void startBlock() {
099: if (inProcess)
100: throw makeException(IN_PROCESS_ERROR);
101:
102: if (inBlock) {
103: throw makeException("nested blocks are not supported");
104: }
105:
106: inBlock = true;
107: expectations.add(new HashMap());
108: }
109:
110: public static void endBlock() {
111: if (inProcess)
112: throw makeException(IN_PROCESS_ERROR);
113:
114: if (!inBlock) {
115: throw makeException("unexpected endBlock()");
116: }
117:
118: Map map = getBlockMap();
119:
120: if (map.size() == 0) {
121: throw makeException("empty Blocks are not allowed");
122: }
123:
124: inBlock = false;
125: }
126:
127: /**
128: * Set level of tolerance when comparing java.util.Date objects
129: * @param milliseconds
130: */
131: public static void setTimeTolerance(long milliseconds) {
132: if (inProcess)
133: throw makeException(IN_PROCESS_ERROR);
134: CompareByValue.setTimeTolerance(milliseconds);
135: }
136:
137: // ===================================================
138: // Runtime methods
139: // ===================================================
140:
141: /**
142: * Generates id for mock identification.
143: * Required to distinguish two+ mocks of the same type.
144: *
145: * @param cls Fully-qualified class name
146: * @param fqn Marker
147: * @param params list of ctor params
148: * @return id of the instance.
149: */
150: private static int nextId = 1;
151:
152: public static synchronized String generateId() {
153: return String.valueOf(nextId++);
154: }
155:
156: /**
157: * Returns return value for a method.
158: */
159: public static Object getReturnValue(String marker, List params)
160: throws Throwable {
161: inProcess = true;
162:
163: // expectations
164: IExpectation expRet = getExpectationResult(marker, params);
165: if (expRet != null) {
166: // check there are no dummy with the same parameters set
167: IExpectation exp = getDummyResult(marker, params);
168: if (exp != null) {
169: throw makeCfgException("expectation and dummy are both configured");
170: }
171:
172: return returnOrThrow(expRet);
173: }
174:
175: // fully configured dummies
176: IExpectation exp = getDummyResult(marker, params);
177: if (exp != null) {
178: return returnOrThrow(exp);
179: }
180:
181: // weak-configured dummies
182: exp = getDummyResult(marker);
183: if (exp != null) {
184: return returnOrThrow(exp);
185: }
186:
187: // not found
188: throw unexpectedCall(marker, params);
189: }
190:
191: private static Object returnOrThrow(IExpectation exp)
192: throws Throwable {
193: if (exp.getThrowableValue() != null)
194: throw exp.getThrowableValue();
195: return exp.getReturnValue();
196: }
197:
198: // ===================================================
199: // Verification methods
200: // ===================================================
201: public static void verify() {
202: try {
203: if (expectations.size() > 0) {
204: throw makeException("not all expected calls was performed");
205: }
206: } finally {
207: reset();
208: }
209: }
210:
211: // ===================================================
212: // Helper methods
213: // ===================================================
214:
215: /**
216: * Accept actual method call and find appropriate return value for it.
217: *
218: * @param objects Actual parameters of call.
219: * @return Return value.
220: */
221: private static IExpectation getExpectationResult(String marker,
222: List params) {
223: if (expectations.size() == 0)
224: return null;
225:
226: if (inActualBlock()) {
227: return getExpectationFromBlock(marker, params);
228: }
229:
230: IExpectation key = new ExpectationWithParams(marker, params);
231: if (!CompareByValue.equals(expectations.get(0), key)) {
232: return null;
233: }
234:
235: return (IExpectation) expectations.remove(0);
236: }
237:
238: private static IExpectation getDummyResult(String marker,
239: List params) {
240: IExpectation key = new ExpectationWithParams(marker, params);
241: for (Iterator iter = dummiesWithParams.iterator(); iter
242: .hasNext();) {
243: IExpectation exp = (IExpectation) iter.next();
244: if (CompareByValue.equals(exp, key)) {
245: return exp;
246: }
247: }
248: return null;
249: }
250:
251: private static IExpectation getDummyResult(String marker) {
252: IExpectation key = new ExpectationWithNoParams(marker);
253: for (Iterator iter = dummiesWithNoParams.iterator(); iter
254: .hasNext();) {
255: IExpectation exp = (IExpectation) iter.next();
256: if (CompareByValue.equals(exp, key)) {
257: return exp;
258: }
259: }
260: return null;
261: }
262:
263: private static void addExpectationToBlock(IExpectation exp) {
264: Map map = getBlockMap();
265:
266: // dozen: list is for duplicate calls in one block
267: List rets = (List) map.get(exp);
268: if (rets == null) {
269: rets = new LinkedList();
270: map.put(exp, rets);
271: }
272: rets.add(exp);
273: }
274:
275: private static IExpectation getExpectationFromBlock(String marker,
276: List params) throws IllegalStateException {
277: Map map = (Map) expectations.get(0);
278:
279: IExpectation key = new ExpectationWithParams(marker, params);
280: List lst = (List) map.get(key);
281:
282: if (lst == null || lst.size() == 0) {
283: return null;
284: }
285:
286: // FIXME! why it's first element??? it could be any element, right?
287: ExpectationWithParams exp = (ExpectationWithParams) lst
288: .remove(0);
289:
290: if (lst.isEmpty()) {
291: map.remove(key);
292: }
293:
294: if (map.isEmpty()) {
295: expectations.remove(0);
296: }
297:
298: return exp;
299: }
300:
301: private static boolean inActualBlock() {
302: return expectations.get(0) instanceof Map;
303: }
304:
305: private static MockException unexpectedCall(String marker,
306: List params) {
307: MockException ex = new MockException("unexpected call to "
308: + marker + " with params " + params, expectations);
309: reset();
310: return ex;
311: }
312:
313: public static MockException makeCfgException(String msg) {
314: MockException ex = new AmbiguousConfigurationException(msg);
315: reset();
316: return ex;
317: }
318:
319: public static MockException makeException(String msg) {
320: MockException ex = new MockException(msg, expectations);
321: reset();
322: return ex;
323: }
324:
325: private static Map getBlockMap() {
326: if (expectations.size() == 0) {
327: throw makeException("internal error: empty expectation, not in block");
328: }
329:
330: Object o = expectations.get(expectations.size() - 1);
331:
332: if (!(o instanceof Map)) {
333: throw makeException("internal error: regular expectation, not a block");
334: }
335:
336: return (Map) o;
337: }
338: }
|