/* * Copyright (c) 2006-2013 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.expectations; import java.util.*; import static mockit.internal.util.Utilities.containsReference; import mockit.internal.expectations.invocation.*; import mockit.internal.state.*; import mockit.internal.util.*; final class PhasedExecutionState { final List<Expectation> expectations; final List<Expectation> nonStrictExpectations; final List<VerifiedExpectation> verifiedExpectations; final Map<Object, Object> instanceMap; final Map<Object, Object> replacementMap; private List<?> dynamicMockInstancesToMatch; private List<Class<?>> mockedTypesToMatchOnInstances; PhasedExecutionState() { expectations = new ArrayList<Expectation>(); nonStrictExpectations = new ArrayList<Expectation>(); verifiedExpectations = new ArrayList<VerifiedExpectation>(); instanceMap = new IdentityHashMap<Object, Object>(); replacementMap = new IdentityHashMap<Object, Object>(); } void setDynamicMockInstancesToMatch(List<?> dynamicMockInstancesToMatch) { this.dynamicMockInstancesToMatch = dynamicMockInstancesToMatch; } void discoverMockedTypesToMatchOnInstances(List<Class<?>> targetClasses) { int numClasses = targetClasses.size(); if (numClasses > 1) { for (int i = 0; i < numClasses; i++) { Class<?> targetClass = targetClasses.get(i); if (targetClasses.lastIndexOf(targetClass) > i) { addMockedTypeToMatchOnInstance(targetClass); } } } } void addMockedTypeToMatchOnInstance(Class<?> mockedType) { if (mockedTypesToMatchOnInstances == null) { mockedTypesToMatchOnInstances = new LinkedList<Class<?>>(); } if (!containsReference(mockedTypesToMatchOnInstances, mockedType)) { mockedTypesToMatchOnInstances.add(mockedType); } } void addExpectation(Expectation expectation, boolean nonStrict) { ExpectedInvocation invocation = expectation.invocation; forceMatchingOnMockInstanceIfRequired(invocation); removeMatchingExpectationsCreatedBefore(invocation); if (nonStrict) { nonStrictExpectations.add(expectation); } else { expectations.add(expectation); } } private void forceMatchingOnMockInstanceIfRequired(ExpectedInvocation invocation) { if (isToBeMatchedOnInstance(invocation.instance, invocation.getMethodNameAndDescription())) { invocation.matchInstance = true; } } boolean isToBeMatchedOnInstance(Object mock, String mockNameAndDesc) { if (mock == null || mockNameAndDesc.charAt(0) == '<') { return false; } else if (dynamicMockInstancesToMatch != null && containsReference(dynamicMockInstancesToMatch, mock)) { return true; } else if (mockedTypesToMatchOnInstances != null) { Class<?> mockedClass = GeneratedClasses.getMockedClass(mock); if (containsReference(mockedTypesToMatchOnInstances, mockedClass)) { return true; } } else if (TestRun.getExecutingTest().isInjectableMock(mock)) { return true; } return false; } private void removeMatchingExpectationsCreatedBefore(ExpectedInvocation invocation) { Expectation previousExpectation = findPreviousNonStrictExpectation(invocation); if (previousExpectation != null) { nonStrictExpectations.remove(previousExpectation); invocation.copyDefaultReturnValue(previousExpectation.invocation); } } private Expectation findPreviousNonStrictExpectation(ExpectedInvocation newInvocation) { Object mock = newInvocation.instance; String mockClassDesc = newInvocation.getClassDesc(); String mockNameAndDesc = newInvocation.getMethodNameAndDescription(); InvocationArguments arguments = newInvocation.arguments; Object[] argValues = arguments.getValues(); boolean staticOrConstructorInvocation = mock == null || newInvocation.isConstructor(); boolean newInvocationWithMatchers = arguments.getMatchers() != null; for (int i = 0, n = nonStrictExpectations.size(); i < n; i++) { Expectation previousExpectation = nonStrictExpectations.get(i); ExpectedInvocation previousInvocation = previousExpectation.invocation; if ( previousInvocation.isMatch(mockClassDesc, mockNameAndDesc) && (staticOrConstructorInvocation || isMatchingInstance(mock, previousExpectation)) && (newInvocationWithMatchers && arguments.hasEquivalentMatchers(previousInvocation.arguments) || !newInvocationWithMatchers && previousInvocation.arguments.isMatch(argValues, instanceMap)) ) { return previousExpectation; } } return null; } Expectation findNonStrictExpectation(Object mock, String mockClassDesc, String mockNameAndDesc, Object[] args) { boolean constructorInvocation = mockNameAndDesc.charAt(0) == '<'; boolean staticOrConstructorInvocation = mock == null || constructorInvocation; Expectation replayExpectationFound = null; // Note: new expectations might get added to the list, so a regular loop would cause a CME: for (int i = 0, n = nonStrictExpectations.size(); i < n; i++) { Expectation nonStrict = nonStrictExpectations.get(i); if (replayExpectationFound != null && nonStrict.recordPhase == null) { continue; } ExpectedInvocation invocation = nonStrict.invocation; if ( invocation.isMatch(mockClassDesc, mockNameAndDesc) && (staticOrConstructorInvocation || isMatchingInstance(mock, nonStrict)) && invocation.arguments.isMatch(args, instanceMap) ) { if (nonStrict.recordPhase == null) { replayExpectationFound = nonStrict; continue; } if (constructorInvocation && invocation.replacementInstance != null) { replacementMap.put(mock, invocation.replacementInstance); } return nonStrict; } } return replayExpectationFound; } private boolean isMatchingInstance(Object mock, Expectation expectation) { ExpectedInvocation invocation = expectation.invocation; if (invocation.instance == replacementMap.get(mock) || invocation.isEquivalentInstance(mock, instanceMap)) { return true; } if (TestRun.getExecutingTest().isInjectableMock(mock)) { return false; } if (dynamicMockInstancesToMatch != null) { if (containsReference(dynamicMockInstancesToMatch, mock)) { return false; } Class<?> invokedClass = invocation.instance.getClass(); for (Object dynamicMock : dynamicMockInstancesToMatch) { if (dynamicMock.getClass() == invokedClass) { return false; } } } return !invocation.matchInstance && expectation.recordPhase != null; } Object getReplacementInstanceForMethodInvocation(Object invokedInstance, String methodNameAndDesc) { return methodNameAndDesc.charAt(0) == '<' ? null : replacementMap.get(invokedInstance); } }