package org.springframework.boot.util;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
public class Instantiator<T> {
private static final Comparator<Constructor<?>> CONSTRUCTOR_COMPARATOR = Comparator
.<Constructor<?>>comparingInt(Constructor::getParameterCount).reversed();
private final Class<?> type;
private final Map<Class<?>, Function<Class<?>, Object>> availableParameters;
public Instantiator(Class<?> type, Consumer<AvailableParameters> availableParameters) {
this.type = type;
this.availableParameters = getAvailableParameters(availableParameters);
}
private Map<Class<?>, Function<Class<?>, Object>> getAvailableParameters(
Consumer<AvailableParameters> availableParameters) {
Map<Class<?>, Function<Class<?>, Object>> result = new LinkedHashMap<>();
availableParameters.accept(new AvailableParameters() {
@Override
public void add(Class<?> type, Object instance) {
result.put(type, (factoryType) -> instance);
}
@Override
public void add(Class<?> type, Function<Class<?>, Object> factory) {
result.put(type, factory);
}
});
return Collections.unmodifiableMap(result);
}
public List<T> instantiate(Collection<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
instances.add(instantiate(name));
}
AnnotationAwareOrderComparator.sort(instances);
return Collections.unmodifiableList(instances);
}
private T instantiate(String name) {
try {
Class<?> type = ClassUtils.forName(name, null);
Assert.isAssignable(this.type, type);
return instantiate(type);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate " + this.type.getName() + " [" + name + "]", ex);
}
}
@SuppressWarnings("unchecked")
private T instantiate(Class<?> type) throws Exception {
Constructor<?>[] constructors = type.getDeclaredConstructors();
Arrays.sort(constructors, CONSTRUCTOR_COMPARATOR);
for (Constructor<?> constructor : constructors) {
Object[] args = getArgs(constructor.getParameterTypes());
if (args != null) {
ReflectionUtils.makeAccessible(constructor);
return (T) constructor.newInstance(args);
}
}
throw new IllegalAccessException("Unable to find suitable constructor");
}
private Object[] getArgs(Class<?>[] parameterTypes) {
Object[] args = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Function<Class<?>, Object> parameter = getAvailableParameter(parameterTypes[i]);
if (parameter == null) {
return null;
}
args[i] = parameter.apply(this.type);
}
return args;
}
private Function<Class<?>, Object> getAvailableParameter(Class<?> parameterType) {
for (Map.Entry<Class<?>, Function<Class<?>, Object>> entry : this.availableParameters.entrySet()) {
if (entry.getKey().isAssignableFrom(parameterType)) {
return entry.getValue();
}
}
return null;
}
public interface AvailableParameters {
void add(Class<?> type, Object instance);
void add(Class<?> type, Function<Class<?>, Object> factory);
}
}