All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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