
org.jusecase.inject.Injector Maven / Gradle / Ivy
package org.jusecase.inject;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.function.BiConsumer;
public class Injector {
private static final Injector instance = new Injector();
private static boolean unitTestMode;
private Map, Object> implementations = new HashMap<>();
private Map, Map> implementationsByName = new HashMap<>();
private Map, List> injectableFields = new HashMap<>();
private boolean allowMissingDependencies;
public static Injector getInstance() {
if (unitTestMode) {
return UnitTestInstanceHolder.unitTestInstance.get();
}
return instance;
}
public void add(Object implementation) {
add(implementation.getClass(), implementation);
}
public void add(String name, Object implementation) {
add(name, implementation.getClass(), implementation);
}
public void add(Class implementationClass) {
add(implementationClass, newInstance(implementationClass));
}
public void add(String name, Class implementationClass) {
add(name, implementationClass, newInstance(implementationClass));
}
public > void addProvider(Class providerClass) {
addProvider((Provider>) newInstance(providerClass));
}
public > void addProviderForSingleInstance(Class providerClass) {
addProviderForSingleInstance((Provider>) newInstance(providerClass));
}
@SuppressWarnings("unchecked")
private T newInstance(Class clazz) {
for (Constructor> constructor : clazz.getDeclaredConstructors()) {
if (constructor.isAnnotationPresent(Inject.class)) {
Object[] arguments = new Object[constructor.getParameterCount()];
Parameter[] parameters = constructor.getParameters();
for (int i = 0; i < parameters.length; ++i) {
arguments[i] = resolveImplementation(parameters[i].getType(), clazz);
if (arguments[i] == null) {
throw new InjectorException(createInjectErrorMessage("No implementation found.", clazz, parameters[i]));
}
}
try {
return (T)constructor.newInstance(arguments);
} catch (Throwable e) {
throw new InjectorException("Failed to create instance of " + clazz , e);
}
}
}
try {
return clazz.getConstructor().newInstance();
} catch (Throwable e) {
throw new InjectorException("Failed to create instance of " + clazz , e);
}
}
public void addProvider(Provider provider) {
Class> providedClass = GenericTypeResolver.resolve(Provider.class, provider.getClass(), 0);
add(providedClass, provider);
add(provider.getClass(), provider);
}
public void addProviderForSingleInstance(Provider provider) {
Class> providedClass = GenericTypeResolver.resolve(Provider.class, provider.getClass(), 0);
add(providedClass, provider.get());
add(provider.getClass(), provider);
}
public void addProvider(PerClassProvider provider) {
Class> providedClass = GenericTypeResolver.resolve(PerClassProvider.class, provider.getClass(), 0);
add(providedClass, provider);
add(provider.getClass(), provider);
}
private void add(Class> clazz, Object implementationOrProvider) {
if (clazz.isAnnotationPresent((Named.class))) {
Named named = clazz.getAnnotation(Named.class);
if (named.value().isEmpty()) {
add(clazz, implementationOrProvider, implementations::put);
} else {
add(named.value(), clazz);
}
} else {
add(clazz, implementationOrProvider, implementations::put);
}
}
private void add(String name, Class> clazz, Object implementationOrProvider) {
add(clazz, implementationOrProvider, (c, i) -> {
Map implementationByName = implementationsByName.computeIfAbsent(c, (key) -> new HashMap<>());
implementationByName.put(name, implementationOrProvider);
});
}
private void add(Class> clazz, Object implementationOrProvider, BiConsumer, Object> consumer) {
consumer.accept(clazz, implementationOrProvider);
for (Class> interfaceClass : clazz.getInterfaces()) {
consumer.accept(interfaceClass, implementationOrProvider);
}
if (clazz.getSuperclass() != null) {
add(clazz.getSuperclass(), implementationOrProvider, consumer);
}
}
public T resolve(Class clazz) {
return resolveImplementation(clazz, null);
}
public void inject(Object instance, Class> declaringType) {
for(Field field : getInjectableFields(declaringType)) {
Object implementation = resolveImplementation(field, declaringType);
if (implementation == null) {
if (allowMissingDependencies) {
continue;
}
throw new InjectorException(createInjectErrorMessage("No implementation found.", declaringType, field));
}
if (Modifier.isFinal(field.getModifiers())) {
throw new InjectorException(createInjectErrorMessage("@Inject field must not be final.", declaringType, field));
}
try {
field.setAccessible(true);
field.set(instance, implementation);
} catch (IllegalAccessException e) {
throw new InjectorException(createInjectErrorMessage("Failed to access field.", declaringType, field), e);
}
}
}
private String createInjectErrorMessage(String reason, Class> type, Field field) {
return reason + " Failed to inject " + field.getType().getName() + " " + field.getName() + " in " + type.getName();
}
private String createInjectErrorMessage(String reason, Class> type, Parameter parameter) {
return reason + " Failed to inject " + parameter.getType().getName() + " " + parameter.getName() + " in " + type.getName();
}
@SuppressWarnings("unchecked")
private T resolveImplementation(Class clazz, Class> toBeInjectedIn) {
return (T)resolveImplementation(implementations.get(clazz), clazz, toBeInjectedIn);
}
private Object resolveImplementation(Field field, Class> toBeInjectedIn) {
if (field.isAnnotationPresent(Named.class)) {
String name = field.getAnnotation(Named.class).value();
Map implementationByName = implementationsByName.get(field.getType());
if (implementationByName == null) {
throw new InjectorException(createInjectErrorMessage("No dependency named " + name + ".", toBeInjectedIn, field));
}
Object implementation = implementationByName.get(name);
if (implementation == null) {
TreeSet available = new TreeSet<>(implementationByName.keySet());
throw new InjectorException(createInjectErrorMessage("No dependency named " + name + ", got " + available + ".", toBeInjectedIn, field));
}
return implementation;
}
return resolveImplementation(field.getType(), toBeInjectedIn);
}
private List getInjectableFields(Class> type) {
List fields = injectableFields.get(type);
if (fields == null) {
fields = calculateInjectableFields(type);
injectableFields.put(type, fields);
}
return fields;
}
private List calculateInjectableFields(Class> type) {
List fields = new ArrayList<>();
for (Field field : type.getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
fields.add(field);
}
}
return fields;
}
private Object resolveImplementation(Object implementation, Class> requestedClass, Class> toBeInjectedIn) {
if (toBeInjectedIn != null && implementation instanceof PerClassProvider && !PerClassProvider.class.isAssignableFrom(requestedClass)) {
return ((PerClassProvider>)implementation).get(toBeInjectedIn);
}
if (implementation instanceof Provider && !Provider.class.isAssignableFrom(requestedClass)) {
return ((Provider>)implementation).get();
}
return implementation;
}
public void reset() {
implementations.clear();
implementationsByName.clear();
}
public void setAllowMissingDependencies(boolean allowMissingDependencies) {
this.allowMissingDependencies = allowMissingDependencies;
}
public static void enableUnitTestMode() {
unitTestMode = true;
}
private static class UnitTestInstanceHolder {
static final ThreadLocal unitTestInstance = ThreadLocal.withInitial(Injector::new);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy