Ad
Reflection

A simple dependency injector.

The intent of this kumite is to gradually add features and tests, such that eventually a kata (or series of kata to separate out the features) can be made.

import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;

public class Injector {
    private final Collection<?> dependencies;
    
    public Injector(Collection<?> dependencies) {
        this.dependencies = dependencies;
    }
    
    public <T> T createInstance(Constructor<T> constructor)
        throws InvocationTargetException, InstantiationException, IllegalAccessException {
        final Class<?>[] paramTypes = constructor.getParameterTypes();
        final Object[] params = new Object[paramTypes.length];
        for (int i = 0; i < params.length; i++) {
            for(Object dep : dependencies) {
                if(paramTypes[i].isInstance(dep)) {
                    if(params[i] == null) {
                        params[i] = dep;
                    } else {
                        throw new UnclearDependencyException(paramTypes[i], List.of(params[i], dep));
                    }
                }
            }
            if(params[i] == null) throw new MissingDependencyException(paramTypes[i]);
        }
        return constructor.newInstance(params);
    }
    
    public <T> T createInstance(Class<T> clazz)
        throws InstantiationException, InvocationTargetException, IllegalAccessException {
        if ((clazz.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE)) != 0)
            throw new IllegalArgumentException("Cannot instantiate " + clazz.getCanonicalName());
        Constructor<?>[] constructors = clazz.getConstructors();
        Constructor<?> constructor = null;
        if (constructors.length == 1) {
            constructor = constructors[0];
        } else {
            for (Constructor<?> con : constructors) {
                if (con.getDeclaredAnnotation(Inject.class) != null) {
                    if (constructor == null) {
                        constructor = con;
                    } else {
                        throw new IllegalArgumentException(clazz.getCanonicalName() + " has multiple constructors annotated with @Inject");
                    }
                }
            }
        }
        if (constructor == null)
            throw new IllegalArgumentException(clazz.getCanonicalName() + " has no viable constructor");
        return clazz.cast(createInstance(constructor));
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
@interface Inject {
}

class MissingDependencyException extends RuntimeException {
    public final Class<?> parameterType;
    
    public MissingDependencyException(Class<?> parameterType) {
        super("No dependency for type: " + parameterType.getCanonicalName());
        this.parameterType = parameterType;
    }
}

class UnclearDependencyException extends RuntimeException {
    public final Class<?> parameterType;
    public final Collection<?> possibleDependencies;
    
    public UnclearDependencyException(Class<?> parameterType, Collection<?> possibleDependencies) {
        super("Multiple options for dependency of type " + parameterType.getCanonicalName() + "\n" + possibleDependencies.stream()
            .map(Object::toString)
            .collect(Collectors.joining("\n")));
        this.parameterType = parameterType;
        this.possibleDependencies = Collections.unmodifiableCollection(possibleDependencies);
    }
}