/* * Copyright (c) 2006 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.injection.constructor; import java.lang.reflect.*; import java.util.*; import javax.annotation.*; import mockit.internal.expectations.mocking.*; import mockit.internal.injection.*; import mockit.internal.injection.full.*; import mockit.internal.state.*; import mockit.internal.util.*; import static mockit.internal.injection.InjectionPoint.*; import static mockit.internal.injection.InjectionPointProvider.NULL; import static mockit.internal.reflection.ConstructorReflection.*; import static mockit.internal.util.Utilities.*; public final class ConstructorInjection extends Injector { @Nonnull private final Constructor<?> constructor; public ConstructorInjection( @Nonnull TestedClass testedClass, @Nonnull InjectionState injectionState, @Nullable FullInjection fullInjection, @Nonnull Constructor<?> constructor) { super(testedClass, injectionState, fullInjection); this.constructor = constructor; } @Nonnull public Object instantiate(@Nonnull List<InjectionPointProvider> parameterProviders) { Type[] parameterTypes = constructor.getGenericParameterTypes(); int n = parameterTypes.length; List<InjectionPointProvider> consumedInjectables = n == 0 ? null : injectionState.saveConsumedInjectables(); Object[] arguments = n == 0 ? NO_ARGS : new Object[n]; boolean varArgs = constructor.isVarArgs(); if (varArgs) { n--; } for (int i = 0; i < n; i++) { @Nonnull InjectionPointProvider parameterProvider = parameterProviders.get(i); Object value; if (parameterProvider instanceof ConstructorParameter) { value = createOrReuseArgumentValue((ConstructorParameter) parameterProvider); } else { value = getArgumentValueToInject(parameterProvider, i); } if (value != null) { Type parameterType = parameterTypes[i]; arguments[i] = wrapInProviderIfNeeded(parameterType, value); } } if (varArgs) { Type parameterType = parameterTypes[n]; arguments[n] = obtainInjectedVarargsArray(parameterType); } if (consumedInjectables != null) { injectionState.restoreConsumedInjectables(consumedInjectables); } return invokeConstructor(arguments); } @Nonnull private Object createOrReuseArgumentValue(@Nonnull ConstructorParameter constructorParameter) { Object value = constructorParameter.getValue(null); if (value != null) { return value; } injectionState.setTypeOfInjectionPoint(constructorParameter.getDeclaredType()); String qualifiedName = getQualifiedName(constructorParameter.getAnnotations()); assert fullInjection != null; value = fullInjection.createOrReuseInstance(this, constructorParameter, qualifiedName); if (value == null) { String parameterName = constructorParameter.getName(); String message = "Missing @Tested or @Injectable" + missingValueDescription(parameterName) + "\r\n when initializing " + fullInjection; throw new IllegalStateException(message); } return value; } @Nullable private Object getArgumentValueToInject(@Nonnull InjectionPointProvider injectable, int parameterIndex) { Object argument = injectionState.getValueToInject(injectable); if (argument == null) { String classDesc = getClassDesc(); String constructorDesc = getConstructorDesc(); String parameterName = ParameterNames.getName(classDesc, constructorDesc, parameterIndex); if (parameterName == null) { parameterName = injectable.getName(); } throw new IllegalArgumentException("No injectable value available" + missingValueDescription(parameterName)); } return argument == NULL ? null : argument; } @Nonnull private String getClassDesc() { return mockit.external.asm.Type.getInternalName(constructor.getDeclaringClass()); } @Nonnull private String getConstructorDesc() { return "<init>" + mockit.external.asm.Type.getConstructorDescriptor(constructor); } @Nonnull private Object obtainInjectedVarargsArray(@Nonnull Type parameterType) { Type varargsElementType = getTypeOfInjectionPointFromVarargsParameter(parameterType); injectionState.setTypeOfInjectionPoint(varargsElementType); List<Object> varargValues = new ArrayList<Object>(); MockedType injectable; while ((injectable = injectionState.findNextInjectableForInjectionPoint()) != null) { Object value = injectionState.getValueToInject(injectable); if (value != null) { value = wrapInProviderIfNeeded(varargsElementType, value); varargValues.add(value); } } Object varargArray = newArrayFromList(varargsElementType, varargValues); return varargArray; } @Nonnull private static Object newArrayFromList(@Nonnull Type elementType, @Nonnull List<Object> values) { Class<?> componentType = getClassType(elementType); int elementCount = values.size(); Object array = Array.newInstance(componentType, elementCount); for (int i = 0; i < elementCount; i++) { Array.set(array, i, values.get(i)); } return array; } @Nonnull private String missingValueDescription(@Nonnull String name) { String classDesc = getClassDesc(); String constructorDesc = getConstructorDesc(); String constructorDescription = new MethodFormatter(classDesc, constructorDesc).toString(); int p = constructorDescription.indexOf('#'); String friendlyConstructorDesc = constructorDescription.substring(p + 1).replace("java.lang.", ""); return " for parameter \"" + name + "\" in constructor " + friendlyConstructorDesc; } @Nonnull private Object invokeConstructor(@Nonnull Object[] arguments) { TestRun.exitNoMockingZone(); try { return invoke(constructor, arguments); } finally { TestRun.enterNoMockingZone(); } } }