/* * Copyright (c) 2006 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.mockups; import java.lang.reflect.*; import javax.annotation.*; import mockit.internal.mockups.MockMethods.*; import mockit.internal.reflection.*; final class MockState { @Nonnull final MockMethod mockMethod; @Nullable private Method actualMockMethod; @Nullable private Member realMethodOrConstructor; @Nullable private Object realClass; // Current mock invocation state: private int invocationCount; @Nullable private ThreadLocal<MockInvocation> proceedingInvocation; // Helper field just for synchronization: @Nonnull private final Object invocationCountLock; MockState(@Nonnull MockMethod mockMethod) { this.mockMethod = mockMethod; invocationCountLock = new Object(); if (mockMethod.canBeReentered()) { makeReentrant(); } } MockState(@Nonnull MockState mockState) { mockMethod = mockState.mockMethod; actualMockMethod = mockState.actualMockMethod; realMethodOrConstructor = mockState.realMethodOrConstructor; invocationCountLock = new Object(); if (mockState.proceedingInvocation != null) { makeReentrant(); } } @Nonnull Class<?> getRealClass() { return mockMethod.getRealClass(); } private void makeReentrant() { proceedingInvocation = new ThreadLocal<MockInvocation>(); } boolean update() { if (proceedingInvocation != null) { MockInvocation invocation = proceedingInvocation.get(); if (invocation != null && invocation.proceeding) { invocation.proceeding = false; return false; } } synchronized (invocationCountLock) { invocationCount++; } return true; } int getTimesInvoked() { synchronized (invocationCountLock) { return invocationCount; } } @Nonnull Member getRealMethodOrConstructor( @Nonnull String mockedClassDesc, @Nonnull String mockedMethodName, @Nonnull String mockedMethodDesc) { if (realMethodOrConstructor == null || !mockedClassDesc.equals(realClass)) { String memberName = "$init".equals(mockedMethodName) ? "<init>" : mockedMethodName; RealMethodOrConstructor realMember; try { realMember = new RealMethodOrConstructor(mockedClassDesc, memberName, mockedMethodDesc); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } Member member = realMember.getMember(); if (mockMethod.isAdvice) { return member; } realMethodOrConstructor = member; realClass = mockedClassDesc; } return realMethodOrConstructor; } @Nonnull Member getRealMethodOrConstructor( @Nonnull Class<?> mockedClass, @Nonnull String mockedMethodName, @Nonnull String mockedMethodDesc) { if (realMethodOrConstructor == null || !mockedClass.equals(realClass)) { String memberName = "$init".equals(mockedMethodName) ? "<init>" : mockedMethodName; RealMethodOrConstructor realMember; try { realMember = new RealMethodOrConstructor(mockedClass, memberName, mockedMethodDesc); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } Member member = realMember.getMember(); if (mockMethod.isAdvice) { return member; } realMethodOrConstructor = member; realClass = mockedClass; } return realMethodOrConstructor; } public boolean shouldProceedIntoRealImplementation(@Nullable Object mock, @Nonnull String classDesc) { if (proceedingInvocation != null) { MockInvocation pendingInvocation = proceedingInvocation.get(); if (pendingInvocation != null && pendingInvocation.isMethodInSuperclass(mock, classDesc)) { return true; } } return false; } void prepareToProceed(@Nonnull MockInvocation invocation) { if (proceedingInvocation == null) { throw new UnsupportedOperationException("Cannot proceed into abstract/interface method"); } if (mockMethod.isForNativeMethod()) { throw new UnsupportedOperationException("Cannot proceed into real implementation of native method"); } MockInvocation previousInvocation = proceedingInvocation.get(); if (previousInvocation != null) { invocation.setPrevious(previousInvocation); } proceedingInvocation.set(invocation); } void prepareToProceedFromNonRecursiveMock(@Nonnull MockInvocation invocation) { assert proceedingInvocation != null; proceedingInvocation.set(invocation); } void clearProceedIndicator() { assert proceedingInvocation != null; MockInvocation currentInvocation = proceedingInvocation.get(); MockInvocation previousInvocation = (MockInvocation) currentInvocation.getPrevious(); proceedingInvocation.set(previousInvocation); } @Nonnull Method getMockMethod() { assert actualMockMethod != null; return actualMockMethod; } @Nonnull Method getMockMethod(@Nonnull Class<?> mockUpClass, @Nonnull Class<?>[] parameterTypes) { if (actualMockMethod == null) { actualMockMethod = MethodReflection.findCompatibleMethod(mockUpClass, mockMethod.name, parameterTypes); } return actualMockMethod; } @Override @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") public boolean equals(@Nonnull Object other) { return mockMethod.equals(((MockState) other).mockMethod); } @Override public int hashCode() { return mockMethod.hashCode(); } }