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.ArrayList;
21import java.util.List;
22
23import org.easymock.EasyMock;
24
25public class MocksBehavior implements IMocksBehavior, Serializable {
26
27    private static final long serialVersionUID = 3265727009370529027L;
28
29    private final List<UnorderedBehavior> behaviorLists = new ArrayList<UnorderedBehavior>();
30
31    private final List<ExpectedInvocationAndResult> stubResults = new ArrayList<ExpectedInvocationAndResult>();
32
33    private final boolean nice;
34
35    private boolean checkOrder;
36
37    private boolean isThreadSafe;
38
39    private boolean shouldBeUsedInOneThread;
40
41    private int position = 0;
42
43    private transient volatile Thread lastThread;
44
45    private LegacyMatcherProvider legacyMatcherProvider;
46
47    public MocksBehavior(boolean nice) {
48        this.nice = nice;
49        this.isThreadSafe = !Boolean.valueOf(EasyMockProperties.getInstance()
50                .getProperty(EasyMock.NOT_THREAD_SAFE_BY_DEFAULT));
51        this.shouldBeUsedInOneThread = Boolean.valueOf(EasyMockProperties
52                .getInstance().getProperty(
53                        EasyMock.ENABLE_THREAD_SAFETY_CHECK_BY_DEFAULT));
54    }
55
56    public final void addStub(ExpectedInvocation expected, Result result) {
57        stubResults.add(new ExpectedInvocationAndResult(expected, result));
58    }
59
60    public void addExpected(ExpectedInvocation expected, Result result,
61            Range count) {
62        if (legacyMatcherProvider != null) {
63            expected = expected.withMatcher(legacyMatcherProvider
64                    .getMatcher(expected.getMethod()));
65        }
66        addBehaviorListIfNecessary(expected);
67        lastBehaviorList().addExpected(expected, result, count);
68    }
69
70    private final Result getStubResult(Invocation actual) {
71        for (ExpectedInvocationAndResult each : stubResults) {
72            if (each.getExpectedInvocation().matches(actual)) {
73                return each.getResult();
74            }
75        }
76        return null;
77    }
78
79    private void addBehaviorListIfNecessary(ExpectedInvocation expected) {
80        if (behaviorLists.isEmpty()
81                || !lastBehaviorList().allowsExpectedInvocation(expected,
82                        checkOrder)) {
83            behaviorLists.add(new UnorderedBehavior(checkOrder));
84        }
85    }
86
87    private UnorderedBehavior lastBehaviorList() {
88        return behaviorLists.get(behaviorLists.size() - 1);
89    }
90
91    @SuppressWarnings("deprecation")
92    public final Result addActual(Invocation actual) {
93        int initialPosition = position;
94
95        while (position < behaviorLists.size()) {
96            Result result = behaviorLists.get(position).addActual(actual);
97            if (result != null) {
98                return result;
99            }
100            if (!behaviorLists.get(position).verify()) {
101                break;
102            }
103            position++;
104        }
105        Result stubOrNice = getStubResult(actual);
106        if (stubOrNice == null && nice) {
107            stubOrNice = Result.createReturnResult(RecordState
108                    .emptyReturnValueFor(actual.getMethod().getReturnType()));
109        }
110
111        int endPosition = position;
112
113        // Do not move the cursor in case of stub, nice or error
114        position = initialPosition;
115
116        if (stubOrNice != null) {
117            actual.validateCaptures();
118            actual.clearCaptures();
119            return stubOrNice;
120        }
121
122        // Case where the loop was exited at the end of the behaviorLists
123        if (endPosition == behaviorLists.size()) {
124            endPosition--;
125        }
126
127        // Loop all around the behaviors left to generate the message
128        StringBuilder errorMessage = new StringBuilder(70 * (endPosition
129                - initialPosition + 1)); // rough approximation of the length
130        errorMessage.append("\n  Unexpected method call ").append(
131                actual.toString(org.easymock.MockControl.EQUALS_MATCHER));
132
133        List<ErrorMessage> messages = new ArrayList<ErrorMessage>();
134
135        int matches = 0;
136
137        // First find how many match we have
138        for (int i = initialPosition; i <= endPosition; i++) {
139            List<ErrorMessage> thisListMessages = behaviorLists.get(i)
140                    .getMessages(actual);
141            messages.addAll(thisListMessages);
142            for (ErrorMessage m : thisListMessages) {
143                if (m.isMatching()) {
144                    matches++;
145                }
146            }
147        }
148
149        if (matches > 1) {
150            errorMessage
151                    .append(". Possible matches are marked with (+1):");
152        } else {
153            errorMessage.append(":");
154        }
155
156        for (ErrorMessage m : messages) {
157            m.appendTo(errorMessage, matches);
158        }
159
160        // And finally throw the error
161        throw new AssertionErrorWrapper(new AssertionError(errorMessage));
162    }
163
164    public void verify() {
165        boolean verified = true;
166
167        for (UnorderedBehavior behaviorList : behaviorLists.subList(position,
168                behaviorLists.size())) {
169            if (!behaviorList.verify()) {
170                verified = false;
171            }
172        }
173        if (verified) {
174            return;
175        }
176
177        StringBuilder errorMessage = new StringBuilder(70 * (behaviorLists
178                .size()
179                - position + 1));
180
181        errorMessage.append("\n  Expectation failure on verify:");
182        for (UnorderedBehavior behaviorList : behaviorLists.subList(position,
183                behaviorLists.size())) {
184            for (ErrorMessage m : behaviorList.getMessages(null)) {
185                m.appendTo(errorMessage, 0);
186            }
187        }
188
189        throw new AssertionErrorWrapper(new AssertionError(errorMessage
190                .toString()));
191    }
192
193    public void checkOrder(boolean value) {
194        this.checkOrder = value;
195    }
196
197    public void makeThreadSafe(boolean isThreadSafe) {
198        this.isThreadSafe = isThreadSafe;
199    }
200
201    public void shouldBeUsedInOneThread(boolean shouldBeUsedInOneThread) {
202        this.shouldBeUsedInOneThread = shouldBeUsedInOneThread;
203    }
204
205    public boolean isThreadSafe() {
206        return this.isThreadSafe;
207    }
208
209    public void checkThreadSafety() {
210        if (!shouldBeUsedInOneThread) {
211            return;
212        }
213        if (lastThread == null) {
214            lastThread = Thread.currentThread();
215        } else if(lastThread != Thread.currentThread()) {
216            throw new AssertionErrorWrapper(new AssertionError(
217                    "\n Mock isn't supposed to be called from multiple threads. Last: "
218                            + lastThread +
219                    " Current: " + Thread.currentThread()));
220        }
221    }
222
223    public LegacyMatcherProvider getLegacyMatcherProvider() {
224        if (legacyMatcherProvider == null) {
225            legacyMatcherProvider = new LegacyMatcherProvider();
226        }
227        return legacyMatcherProvider;
228    }
229
230    @SuppressWarnings("deprecation")
231    public void setDefaultMatcher(org.easymock.ArgumentsMatcher matcher) {
232        getLegacyMatcherProvider().setDefaultMatcher(matcher);
233    }
234
235    @SuppressWarnings("deprecation")
236    public void setMatcher(Method method, org.easymock.ArgumentsMatcher matcher) {
237        getLegacyMatcherProvider().setMatcher(method, matcher);
238    }
239}
240