/* * 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.invocation; import java.lang.reflect.*; import java.util.*; import mockit.internal.expectations.mocking.*; import mockit.internal.state.*; import mockit.internal.util.*; public final class MockedTypeCascade { private final MockedType mockedType; private final Map<String, Class<?>> cascadedTypesAndMocks; public MockedTypeCascade(MockedType mockedType) { this.mockedType = mockedType; cascadedTypesAndMocks = new HashMap<String, Class<?>>(4); } public boolean isSharedBetweenTests() { return mockedType != null && mockedType.fieldFromTestClass; } public static Object getMock( String mockedTypeDesc, Object mockInstance, String returnTypeDesc, String genericReturnTypeDesc) { char typeCode = returnTypeDesc.charAt(0); if (typeCode != 'L') { return null; } MockedTypeCascade cascade = TestRun.getExecutingTest().getMockedTypeCascade(mockedTypeDesc, mockInstance); if (cascade == null) { return null; } String returnTypeInternalName = null; if (genericReturnTypeDesc != null) { returnTypeInternalName = getGenericReturnType(genericReturnTypeDesc, cascade); } if (returnTypeInternalName == null) { returnTypeInternalName = getReturnTypeIfCascadingSupportedForIt(returnTypeDesc); } return returnTypeInternalName == null ? null : cascade.getCascadedMock(returnTypeInternalName); } private static String getGenericReturnType(String genericReturnTypeDesc, MockedTypeCascade cascade) { if (cascade.mockedType == null) { return null; } String typeName = getInternalTypeName(genericReturnTypeDesc); Type mockedType = cascade.mockedType.declaredType; if (!(mockedType instanceof ParameterizedType)) { mockedType = ((Class<?>) mockedType).getGenericSuperclass(); } if (mockedType instanceof ParameterizedType) { ParameterizedType mockedGenericType = (ParameterizedType) mockedType; TypeVariable<?>[] typeParameters = ((Class<?>) mockedGenericType.getRawType()).getTypeParameters(); Type[] actualTypeArguments = mockedGenericType.getActualTypeArguments(); for (int i = 0; i < typeParameters.length; i++) { TypeVariable<?> typeParameter = typeParameters[i]; if (typeName.equals(typeParameter.getName())) { Type actualType = actualTypeArguments[i]; Class<?> actualClass; if (actualType instanceof Class<?>) { actualClass = (Class<?>) actualType; } else if (actualType instanceof WildcardType) { actualClass = (Class<?>) ((WildcardType) actualType).getUpperBounds()[0]; } else { return null; } return getReturnTypeIfCascadingSupportedForIt(actualClass); } } } return null; } private static String getInternalTypeName(String typeDesc) { return typeDesc.substring(1, typeDesc.length() - 1); } private static String getReturnTypeIfCascadingSupportedForIt(Class<?> returnType) { String typeName = returnType.getName().replace('.', '/'); return isTypeSupportedForCascading(typeName) ? typeName : null; } private static boolean isTypeSupportedForCascading(String typeName) { return !typeName.startsWith("java/lang/") || typeName.contains("/Process") || typeName.endsWith("/Runnable"); } private static String getReturnTypeIfCascadingSupportedForIt(String typeDesc) { String typeName = getInternalTypeName(typeDesc); return isTypeSupportedForCascading(typeName) ? typeName : null; } private Object getCascadedMock(String returnTypeInternalName) { Class<?> returnType = cascadedTypesAndMocks.get(returnTypeInternalName); if (returnType == null) { returnType = registerIntermediateCascadingType(returnTypeInternalName); } return createNewCascadedInstanceOrUseNonCascadedOneIfAvailable(returnType); } private Class<?> registerIntermediateCascadingType(String returnTypeInternalName) { Class<?> returnType = ClassLoad.loadByInternalName(returnTypeInternalName); cascadedTypesAndMocks.put(returnTypeInternalName, returnType); TestRun.getExecutingTest().addCascadingType(returnTypeInternalName, null); return returnType; } private Object createNewCascadedInstanceOrUseNonCascadedOneIfAvailable(Class<?> mockedType) { InstanceFactory instanceFactory = TestRun.mockFixture().findInstanceFactory(mockedType); if (instanceFactory == null) { CascadingTypeRedefinition typeRedefinition = new CascadingTypeRedefinition(mockedType); instanceFactory = typeRedefinition.redefineType(); } else { Object lastInstance = instanceFactory.getLastInstance(); if (lastInstance != null) { return lastInstance; } } Object cascadedInstance = instanceFactory.create(); instanceFactory.clearLastInstance(); TestRun.getExecutingTest().addInjectableMock(cascadedInstance); return cascadedInstance; } public void discardCascadedMocks() { cascadedTypesAndMocks.clear(); } }