/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.injection.full;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.logging.*;
import javax.annotation.*;
import javax.enterprise.context.*;
import javax.inject.*;
import javax.sql.*;
import static java.lang.reflect.Modifier.*;
import mockit.internal.injection.*;
import mockit.internal.reflection.*;
import static mockit.external.asm.Opcodes.*;
import static mockit.internal.injection.InjectionPoint.*;
import static mockit.internal.reflection.ConstructorReflection.*;
import static mockit.internal.util.Utilities.*;
/**
* Responsible for recursive injection of dependencies into a {@code @Tested(fullyInitialized = true)} object.
*/
public final class FullInjection
{
private static final int INVALID_TYPES = ACC_ABSTRACT + ACC_ANNOTATION + ACC_ENUM;
@Nonnull private final InjectionState injectionState;
@Nonnull private final Class<?> testedClass;
@Nonnull private final String testedName;
@Nullable private final ServletDependencies servletDependencies;
@Nullable private final JPADependencies jpaDependencies;
@Nullable private Class<?> dependencyClass;
@Nullable private InjectionPointProvider injectionProvider;
public FullInjection(
@Nonnull InjectionState injectionState, @Nonnull Class<?> testedClass, @Nonnull String testedName)
{
this.injectionState = injectionState;
this.testedClass = testedClass;
this.testedName = testedName;
servletDependencies = SERVLET_CLASS == null ? null : new ServletDependencies(injectionState);
jpaDependencies = PERSISTENCE_UNIT_CLASS == null ? null : new JPADependencies(injectionState);
}
@Nullable
public Object reuseInstance(
@Nonnull TestedClass testedClass, @Nonnull InjectionPointProvider injectionProvider,
@Nullable String qualifiedName)
{
this.injectionProvider = injectionProvider;
InjectionPoint injectionPoint = getInjectionPoint(testedClass.reflection, injectionProvider, qualifiedName);
Object dependency = injectionState.getInstantiatedDependency(testedClass, injectionProvider, injectionPoint);
return dependency;
}
@Nonnull
private InjectionPoint getInjectionPoint(
@Nonnull GenericTypeReflection reflection, @Nonnull InjectionPointProvider injectionProvider,
@Nullable String qualifiedName)
{
Type dependencyType = injectionProvider.getDeclaredType();
if (dependencyType instanceof TypeVariable<?>) {
dependencyType = reflection.resolveTypeVariable((TypeVariable<?>) dependencyType);
dependencyClass = getClassType(dependencyType);
}
else {
dependencyClass = injectionProvider.getClassOfDeclaredType();
}
if (qualifiedName != null && !qualifiedName.isEmpty()) {
return new InjectionPoint(dependencyClass, qualifiedName);
}
if (jpaDependencies != null && JPADependencies.isApplicable(dependencyClass)) {
for (Annotation annotation : injectionProvider.getAnnotations()) {
String id = jpaDependencies.getDependencyIdIfAvailable(annotation);
if (id != null) {
return new InjectionPoint(dependencyClass, id);
}
}
}
return new InjectionPoint(dependencyType);
}
@Nullable
public Object createOrReuseInstance(
@Nonnull Injector injector, @Nonnull InjectionPointProvider injectionProvider, @Nullable String qualifiedName)
{
TestedClass testedClass = injector.testedClass;
setInjectionProvider(injectionProvider);
InjectionPoint injectionPoint = getInjectionPoint(testedClass.reflection, injectionProvider, qualifiedName);
Object dependency = injectionState.getInstantiatedDependency(testedClass, injectionProvider, injectionPoint);
if (dependency != null) {
return dependency;
}
Class<?> typeToInject = dependencyClass;
if (typeToInject == Logger.class) {
return Logger.getLogger(testedClass.nameOfTestedClass);
}
if (typeToInject == null || !isInstantiableType(typeToInject)) {
return null;
}
dependency = typeToInject.isInterface() ?
createInstanceOfSupportedInterfaceIfApplicable(testedClass, typeToInject, injectionPoint, injectionProvider) :
createAndRegisterNewInstance(typeToInject, injector, injectionPoint, injectionProvider);
return dependency;
}
private void setInjectionProvider(@Nonnull InjectionPointProvider injectionProvider)
{
injectionProvider.parent = this.injectionProvider;
this.injectionProvider = injectionProvider;
}
private static boolean isInstantiableType(@Nonnull Class<?> type)
{
if (type.isPrimitive() || type.isArray() || type.isAnnotation()) {
return false;
}
if (!type.isInterface()) {
int typeModifiers = type.getModifiers();
if ((typeModifiers & INVALID_TYPES) != 0 || !isStatic(typeModifiers) && type.isMemberClass()) {
return false;
}
if (type.getClassLoader() == null) {
return false;
}
}
return true;
}
@Nullable
private Object createInstanceOfSupportedInterfaceIfApplicable(
@Nonnull TestedClass testedClass, @Nonnull Class<?> typeToInject,
@Nonnull InjectionPoint injectionPoint, @Nonnull InjectionPointProvider injectionProvider)
{
Object dependency = null;
if (CommonDataSource.class.isAssignableFrom(typeToInject)) {
dependency = createAndRegisterDataSource(testedClass, injectionPoint);
}
else if (INJECT_CLASS != null && typeToInject == Provider.class) {
dependency = createProviderInstance(injectionProvider);
}
else if (CONVERSATION_CLASS != null && typeToInject == Conversation.class) {
dependency = createAndRegisterConversationInstance();
}
else if (servletDependencies != null && ServletDependencies.isApplicable(typeToInject)) {
dependency = servletDependencies.createAndRegisterDependency(typeToInject);
}
else if (jpaDependencies != null && JPADependencies.isApplicable(typeToInject)) {
dependency = jpaDependencies.createAndRegisterDependency(typeToInject, injectionPoint);
}
return dependency;
}
@Nullable
private Object createAndRegisterDataSource(@Nonnull TestedClass testedClass, @Nonnull InjectionPoint injectionPoint)
{
TestDataSource dsCreation = new TestDataSource(injectionPoint);
CommonDataSource dataSource = dsCreation.createIfDataSourceDefinitionAvailable(testedClass);
if (dataSource != null) {
injectionState.saveInstantiatedDependency(injectionPoint, dataSource);
}
return dataSource;
}
@Nonnull
private Object createProviderInstance(@Nonnull InjectionPointProvider injectionProvider)
{
ParameterizedType genericType = (ParameterizedType) injectionProvider.getDeclaredType();
final Class<?> providedClass = (Class<?>) genericType.getActualTypeArguments()[0];
if (providedClass.isAnnotationPresent(Singleton.class)) {
return new Provider<Object>() {
private Object dependency;
@Override
public synchronized Object get()
{
if (dependency == null) {
dependency = createNewInstance(providedClass);
}
return dependency;
}
};
}
return new Provider<Object>() {
@Override
public Object get()
{
Object dependency = createNewInstance(providedClass);
return dependency;
}
};
}
@Nullable
private Object createNewInstance(@Nonnull Class<?> dependencyClass)
{
if (dependencyClass.isInterface()) {
return null;
}
if (dependencyClass.getClassLoader() == null) {
return newInstanceUsingDefaultConstructorIfAvailable(dependencyClass);
}
return new TestedObjectCreation(injectionState, this, dependencyClass).create();
}
@Nonnull
private Object createAndRegisterConversationInstance()
{
Conversation conversation = new TestConversation();
InjectionPoint injectionPoint = new InjectionPoint(Conversation.class);
injectionState.saveInstantiatedDependency(injectionPoint, conversation);
return conversation;
}
@Nullable
private Object createAndRegisterNewInstance(
@Nonnull Class<?> typeToInstantiate, @Nonnull Injector injector, @Nonnull InjectionPoint injectionPoint,
@Nonnull InjectionPointProvider injectionProvider)
{
Object dependency = createNewInstance(typeToInstantiate);
if (dependency != null) {
if (injectionPoint.name == null) {
injectionPoint = new InjectionPoint(injectionPoint.type, injectionProvider.getName());
}
registerNewInstance(injector, injectionPoint, dependency);
}
return dependency;
}
private void registerNewInstance(
@Nonnull Injector injector, @Nonnull InjectionPoint injectionPoint, @Nonnull Object dependency)
{
injectionState.saveInstantiatedDependency(injectionPoint, dependency);
Class<?> instantiatedClass = dependency.getClass();
if (injector.testedClass.isClassFromSameModuleOrSystemAsTestedClass(instantiatedClass)) {
injector.fillOutDependenciesRecursively(dependency);
injectionState.lifecycleMethods.findLifecycleMethods(instantiatedClass);
injectionState.lifecycleMethods.executeInitializationMethodsIfAny(instantiatedClass, dependency);
}
}
@Override
public String toString()
{
String description = "@Tested object \"" + testedClass.getSimpleName() + ' ' + testedName + '"';
if (injectionProvider != null) {
InjectionPointProvider parentInjectionProvider = injectionProvider.parent;
if (parentInjectionProvider != null) {
description = parentInjectionProvider + "\r\n of " + description;
}
}
return description;
}
}