/* * 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.mocking; import java.lang.annotation.*; import java.lang.reflect.*; import static java.lang.reflect.Modifier.*; import mockit.*; import mockit.internal.state.*; import mockit.internal.util.*; @SuppressWarnings({"ClassWithTooManyFields", "EqualsAndHashcode"}) public final class MockedType { private static final boolean ANNOTATED_MOCK_PARAMETERS_ONLY = "annotated".equals(System.getProperty("jmockit-mockParameters")); @SuppressWarnings("UnusedDeclaration") @Mocked private static final Object DUMMY = null; private static final int DUMMY_HASHCODE; static { int h = 0; try { h = MockedType.class.getDeclaredField("DUMMY").getAnnotation(Mocked.class).hashCode(); } catch (NoSuchFieldException ignore) {} DUMMY_HASHCODE = h; } public final Field field; public final boolean fieldFromTestClass; private final int accessModifiers; private final Mocked mocked; final Capturing capturing; final Cascading cascading; public final boolean nonStrict; public final boolean injectable; public final Type declaredType; private final String testMethodDesc; public final String mockId; MockingConfiguration mockingCfg; Object providedValue; public MockedType(Field field, boolean fromTestClass) { this.field = field; fieldFromTestClass = fromTestClass; accessModifiers = field.getModifiers(); mocked = field.getAnnotation(Mocked.class); capturing = field.getAnnotation(Capturing.class); cascading = field.getAnnotation(Cascading.class); nonStrict = field.isAnnotationPresent(NonStrict.class); Injectable injectableAnnotation = field.getAnnotation(Injectable.class); injectable = injectableAnnotation != null; declaredType = field.getGenericType(); testMethodDesc = null; mockId = field.getName(); providedValue = getDefaultInjectableValue(injectableAnnotation); registerCascadingIfSpecified(); } private Object getDefaultInjectableValue(Injectable annotation) { if (annotation != null) { String value = annotation.value(); if (value.length() > 0) { Class<?> injectableClass = getClassType(); if (injectableClass == char.class) { return value.charAt(0); } else if (injectableClass == String.class) { return value; } else if (injectableClass.isPrimitive()) { Class<?> wrapperClass = AutoBoxing.getWrapperType(injectableClass); Class<?>[] constructorParameters = {String.class}; return ConstructorReflection.newInstance(wrapperClass, constructorParameters, value); } else if (injectableClass.isEnum()) { @SuppressWarnings({"rawtypes", "unchecked"}) Class<? extends Enum> enumType = (Class<? extends Enum>) injectableClass; return Enum.valueOf(enumType, value); } } } return null; } private void registerCascadingIfSpecified() { if (cascading != null) { String mockedTypeDesc = getClassType().getName().replace('.', '/'); TestRun.getExecutingTest().addCascadingType(mockedTypeDesc, this); } } MockedType( String testClassDesc, String testMethodDesc, int paramIndex, Type parameterType, Annotation[] annotationsOnParameter) { field = null; fieldFromTestClass = false; accessModifiers = 0; mocked = getAnnotation(annotationsOnParameter, Mocked.class); capturing = getAnnotation(annotationsOnParameter, Capturing.class); cascading = getAnnotation(annotationsOnParameter, Cascading.class); nonStrict = getAnnotation(annotationsOnParameter, NonStrict.class) != null; Injectable injectableAnnotation = getAnnotation(annotationsOnParameter, Injectable.class); injectable = injectableAnnotation != null; declaredType = parameterType; this.testMethodDesc = testMethodDesc; mockId = ParameterNames.getName(testClassDesc, testMethodDesc, paramIndex); providedValue = getDefaultInjectableValue(injectableAnnotation); registerCascadingIfSpecified(); } private <A extends Annotation> A getAnnotation(Annotation[] annotations, Class<A> annotation) { for (Annotation paramAnnotation : annotations) { if (paramAnnotation.annotationType() == annotation) { //noinspection unchecked return (A) paramAnnotation; } } return null; } MockedType(Class<?> cascadedType) { field = null; fieldFromTestClass = false; accessModifiers = 0; mocked = null; capturing = null; cascading = null; nonStrict = true; injectable = true; declaredType = cascadedType; testMethodDesc = null; mockId = "cascaded_" + cascadedType.getName(); } public Class<?> getClassType() { if (declaredType instanceof Class) { return (Class<?>) declaredType; } if (declaredType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) declaredType; return (Class<?>) parameterizedType.getRawType(); } return null; } boolean isMockField() { return (isAnnotated() || !fieldFromTestClass && !isPrivate(accessModifiers)) && isMockableType(); } private boolean isAnnotated() { return mocked != null || capturing != null || cascading != null || nonStrict || injectable; } boolean isMockableType() { if (ANNOTATED_MOCK_PARAMETERS_ONLY && field == null && !isAnnotated()) { return false; } if (!(declaredType instanceof Class)) { printWarningAboutMockFieldOrParameterLackingAnAnnotation(); return true; } Class<?> classType = (Class<?>) declaredType; if (classType.isPrimitive() || classType.isArray() || classType == Integer.class) { return false; } else if (injectable && providedValue != null) { if (classType == String.class || classType.isEnum()) { return false; } } printWarningAboutMockFieldOrParameterLackingAnAnnotation(); return true; } private void printWarningAboutMockFieldOrParameterLackingAnAnnotation() { if (!ANNOTATED_MOCK_PARAMETERS_ONLY && !isAnnotated()) { if (field == null) { System.out.println( "WARNING: Mock parameter \"" + mockId + "\" should use a mocking annotation such as @Mocked"); System.out.println(" at " + TestRun.getCurrentTestClass().getName() + '.' + testMethodDesc); } else { System.out.println( "WARNING: Mock field \"" + mockId + "\" should use a mocking annotation such as @Mocked"); System.out.println(" at " + new StackTrace().findPositionInTestMethod()); } } } boolean isFinalFieldOrParameter() { return field == null || isFinal(accessModifiers); } void buildMockingConfiguration() { if (mocked == null) { return; } String[] filters = getFilters(); if (filters.length > 0) { mockingCfg = new MockingConfiguration(filters, !mocked.inverse()); } } private String[] getFilters() { String[] filters = mocked.methods(); if (filters.length == 0) { filters = mocked.value(); } return filters; } boolean isClassInitializationToBeStubbedOut() { return mocked != null && mocked.stubOutClassInitialization(); } boolean withInstancesToCapture() { return getMaxInstancesToCapture() > 0; } int getMaxInstancesToCapture() { return capturing == null ? 0 : capturing.maxInstances(); } public Object getValueToInject(Object objectWithFields) { if (field == null) { return providedValue; } Object value = FieldReflection.getFieldValue(field, objectWithFields); if (!injectable) { return value; } if (value == null) { return providedValue; } Class<?> fieldType = field.getType(); if (!fieldType.isPrimitive()) { return value; } Object defaultValue = DefaultValues.defaultValueForPrimitiveType(fieldType); return value.equals(defaultValue) ? providedValue : value; } @Override public int hashCode() { int result = declaredType.hashCode(); if (isFinal(accessModifiers)) { result *= 31; } if (injectable) { result *= 37; } if (mocked != null) { int h = mocked.hashCode(); if (h != DUMMY_HASHCODE) { result = 31 * result + h; } } return result; } }