Remove util/di from project.
This commit is contained in:
parent
50b04d9c2c
commit
3087393607
@ -1,31 +0,0 @@
|
||||
plugins {
|
||||
id 'groowt-conventions'
|
||||
id 'groowt-testing'
|
||||
id 'groowt-logging'
|
||||
id 'groowt-publish'
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api libs.jakarta.inject
|
||||
api libs.groovy
|
||||
compileOnlyApi libs.jetbrains.anotations
|
||||
implementation libs.slf4j.api, libs.groovy
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
archiveBaseName = 'groowt-util-di'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create('di', MavenPublication) {
|
||||
artifactId = 'util-di'
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
@ -1,357 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static groowt.util.di.ObjectFactoryUtil.toTypes;
|
||||
|
||||
// TODO: maybe inject fields
|
||||
public abstract class AbstractInjectingObjectFactory implements ObjectFactory {
|
||||
|
||||
protected record CachedInjectConstructor<T>(Class<T> clazz, Constructor<T> constructor) {}
|
||||
|
||||
protected record CachedNonInjectConstructor<T>(
|
||||
Class<T> clazz,
|
||||
Constructor<T> constructor,
|
||||
Class<?>[] paramTypes
|
||||
) {}
|
||||
|
||||
protected record Resolved(Class<?> type, Object object) {}
|
||||
|
||||
private static final class DeferredSetters {
|
||||
|
||||
private final Class<?> forType;
|
||||
private final List<Consumer<Object>> actions = new ArrayList<>();
|
||||
|
||||
public DeferredSetters(Class<?> forType) {
|
||||
this.forType = forType;
|
||||
}
|
||||
|
||||
public Class<?> getForType() {
|
||||
return this.forType;
|
||||
}
|
||||
|
||||
public List<Consumer<Object>> getActions() {
|
||||
return this.actions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class CreateContext {
|
||||
|
||||
private final Deque<Class<?>> constructionStack = new LinkedList<>();
|
||||
private final Deque<DeferredSetters> deferredSettersStack = new LinkedList<>();
|
||||
private final List<Resolved> allResolved = new ArrayList<>();
|
||||
|
||||
public CreateContext(Class<?> targetType) {
|
||||
this.pushConstruction(targetType);
|
||||
}
|
||||
|
||||
public void checkForCircularDependency(Class<?> typeToConstruct) {
|
||||
if (constructionStack.contains(typeToConstruct)) {
|
||||
throw new IllegalStateException(
|
||||
"Detected a circular constructor dependency for " + typeToConstruct.getName()
|
||||
+ " . Please use setter methods instead. Current construction stack: "
|
||||
+ constructionStack.stream()
|
||||
.map(Class::getName)
|
||||
.collect(Collectors.joining(", "))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void pushConstruction(Class<?> type) {
|
||||
this.constructionStack.push(type);
|
||||
this.deferredSettersStack.push(new DeferredSetters(type));
|
||||
}
|
||||
|
||||
public void popConstruction() {
|
||||
this.constructionStack.pop();
|
||||
this.deferredSettersStack.pop();
|
||||
}
|
||||
|
||||
public boolean containsConstruction(Class<?> type) {
|
||||
return this.constructionStack.contains(type);
|
||||
}
|
||||
|
||||
public void addDeferredSetterAction(Class<?> forType, Consumer<Object> action) {
|
||||
boolean found = false;
|
||||
for (final DeferredSetters deferredSetters : this.deferredSettersStack) {
|
||||
if (deferredSetters.getForType().equals(forType)) {
|
||||
found = true;
|
||||
deferredSetters.getActions().add(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw new IllegalArgumentException(
|
||||
"There is no construction for type " + forType.getName() + " which should be deferred."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Consumer<Object>> getDeferredSetterActions() {
|
||||
return this.deferredSettersStack.getFirst().getActions();
|
||||
}
|
||||
|
||||
public List<Resolved> getAllResolved() {
|
||||
return this.allResolved;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Map<Class<?>, Constructor<?>[]> cachedAllConstructors = new HashMap<>();
|
||||
private final Collection<CachedInjectConstructor<?>> cachedInjectConstructors = new ArrayList<>();
|
||||
private final Collection<CachedNonInjectConstructor<?>> cachedNonInjectConstructors = new ArrayList<>();
|
||||
private final Map<Class<?>, Collection<Method>> cachedSetters = new HashMap<>();
|
||||
private final Map<Method, Parameter> cachedSetterParameters = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> @Nullable Constructor<T> findCachedInjectConstructor(Class<T> clazz) {
|
||||
for (final CachedInjectConstructor<?> cachedConstructor : this.cachedInjectConstructors) {
|
||||
if (clazz.equals(cachedConstructor.clazz())) {
|
||||
return (Constructor<T>) cachedConstructor.constructor();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @implNote If overridden, please cache any found inject constructors using {@link #putCachedInjectConstructor}.
|
||||
*
|
||||
* @param clazz the {@link Class} in which to search for an <code>{@literal @}Inject</code> annotated constructor.
|
||||
* @return the inject constructor, or {@code null} if none found.
|
||||
* @param <T> the type of the class
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> @Nullable Constructor<T> findInjectConstructor(Class<T> clazz) {
|
||||
final Constructor<T> cachedInjectConstructor = this.findCachedInjectConstructor(clazz);
|
||||
if (cachedInjectConstructor != null) {
|
||||
return cachedInjectConstructor;
|
||||
}
|
||||
|
||||
final Constructor<?>[] constructors = this.cachedAllConstructors.computeIfAbsent(clazz, Class::getConstructors);
|
||||
|
||||
final List<Constructor<?>> injectConstructors = Arrays.stream(constructors)
|
||||
.filter(constructor -> constructor.isAnnotationPresent(Inject.class))
|
||||
.toList();
|
||||
|
||||
if (injectConstructors.size() > 1) {
|
||||
// one day maybe support multiple inject constructors
|
||||
throw new UnsupportedOperationException("Cannot have more than one @Inject constructor in class: " + clazz);
|
||||
} else if (injectConstructors.size() == 1) {
|
||||
final Constructor<T> injectConstructor = (Constructor<T>) injectConstructors.getFirst();
|
||||
this.putCachedInjectConstructor(new CachedInjectConstructor<>(clazz, injectConstructor));
|
||||
return injectConstructor;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected final void putCachedInjectConstructor(CachedInjectConstructor<?> cached) {
|
||||
this.cachedInjectConstructors.add(cached);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> @Nullable Constructor<T> findCachedNonInjectConstructor(Class<T> clazz, Class<?>[] paramTypes) {
|
||||
for (final CachedNonInjectConstructor<?> cachedConstructor : this.cachedNonInjectConstructors) {
|
||||
if (clazz.equals(cachedConstructor.clazz()) && Arrays.equals(cachedConstructor.paramTypes(), paramTypes)) {
|
||||
return (Constructor<T>) cachedConstructor.constructor();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean areArgsAssignable(Class<?>[] paramTypes, Object[] givenArgs) {
|
||||
if (paramTypes.length != givenArgs.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
if (!paramTypes[i].isInstance(givenArgs[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @implNote If overridden, please cache any found non-inject constructors using
|
||||
* {@link #putCachedNonInjectConstructor}.
|
||||
*
|
||||
* @param clazz the {@link Class} in which to search for a constructor which does
|
||||
* not have an <code>{@literal @}Inject</code> annotation.
|
||||
* @param constructorArgs the given constructor args
|
||||
* @return the found non-inject constructor appropriate for the given constructor args,
|
||||
* or {@code null} if no such constructor exists
|
||||
* @param <T> the type
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> @Nullable Constructor<T> findNonInjectConstructor(Class<T> clazz, Object[] constructorArgs) {
|
||||
final Class<?>[] types = toTypes(constructorArgs);
|
||||
final Constructor<T> cachedConstructor = this.findCachedNonInjectConstructor(clazz, types);
|
||||
if (cachedConstructor != null) {
|
||||
return cachedConstructor;
|
||||
}
|
||||
|
||||
final Constructor<?>[] constructors = this.cachedAllConstructors.computeIfAbsent(clazz, Class::getConstructors);
|
||||
for (Constructor<?> constructor : constructors) {
|
||||
if (areArgsAssignable(constructor.getParameterTypes(), constructorArgs)) {
|
||||
final Constructor<T> found = (Constructor<T>) constructor;
|
||||
this.putCachedNonInjectConstructor(new CachedNonInjectConstructor<>(clazz, found, types));
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final void putCachedNonInjectConstructor(CachedNonInjectConstructor<?> cached) {
|
||||
this.cachedNonInjectConstructors.add(cached);
|
||||
}
|
||||
|
||||
/**
|
||||
* @implNote Please call {@code super.findConstructor()} first, and then implement custom
|
||||
* constructor finding logic. If the custom logic finds a constructor, please cache it
|
||||
* using either {@link #putCachedNonInjectConstructor} or {@link #putCachedInjectConstructor}.
|
||||
*/
|
||||
protected <T> Constructor<T> findConstructor(Class<T> clazz, Object[] args) {
|
||||
final Constructor<T> injectConstructor = this.findInjectConstructor(clazz);
|
||||
if (injectConstructor != null) {
|
||||
return injectConstructor;
|
||||
}
|
||||
final Constructor<T> nonInjectConstructor = this.findNonInjectConstructor(clazz, args);
|
||||
if (nonInjectConstructor != null) {
|
||||
return nonInjectConstructor;
|
||||
}
|
||||
throw new RuntimeException("Could not find an appropriate constructor for " + clazz.getName()
|
||||
+ " with args " + Arrays.toString(toTypes(args))
|
||||
);
|
||||
}
|
||||
|
||||
protected Collection<Method> getAllInjectSetters(Class<?> clazz) {
|
||||
final Method[] allMethods = clazz.getMethods();
|
||||
final Collection<Method> injectSetters = new ArrayList<>();
|
||||
for (final var method : allMethods) {
|
||||
if (
|
||||
method.isAnnotationPresent(Inject.class)
|
||||
&& method.getName().startsWith("set")
|
||||
&& !Modifier.isStatic(method.getModifiers())
|
||||
&& method.getParameterCount() == 1
|
||||
) {
|
||||
injectSetters.add(method);
|
||||
}
|
||||
}
|
||||
return injectSetters;
|
||||
}
|
||||
|
||||
protected Collection<Method> getCachedSettersFor(Object target) {
|
||||
return this.cachedSetters.computeIfAbsent(target.getClass(), this::getAllInjectSetters);
|
||||
}
|
||||
|
||||
protected Parameter getCachedInjectParameter(Method setter) {
|
||||
return this.cachedSetterParameters.computeIfAbsent(setter, s -> {
|
||||
if (s.getParameterCount() != 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Setter " + s.getName() + " has a parameter count other than one (1)!"
|
||||
);
|
||||
}
|
||||
return s.getParameters()[0];
|
||||
});
|
||||
}
|
||||
|
||||
private @Nullable Object findInContext(CreateContext context, Class<?> type) {
|
||||
for (final Resolved resolved : context.getAllResolved()) {
|
||||
if (type.isAssignableFrom(resolved.type)) {
|
||||
return resolved.object;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void injectSetter(CreateContext context, Object target, Method setter) {
|
||||
final Parameter injectParam = this.getCachedInjectParameter(setter);
|
||||
final Class<?> typeToInject = injectParam.getType();
|
||||
final @Nullable Object fromContext = this.findInContext(context, typeToInject);
|
||||
|
||||
final Consumer<Object> setterAction = arg -> {
|
||||
try {
|
||||
setter.invoke(target, arg);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (context.containsConstruction(typeToInject)) {
|
||||
context.addDeferredSetterAction(typeToInject, setterAction);
|
||||
} else {
|
||||
final Object arg;
|
||||
if (fromContext != null) {
|
||||
arg = fromContext;
|
||||
} else {
|
||||
arg = this.getSetterInjectArg(context, target.getClass(), setter, injectParam);
|
||||
context.getAllResolved().add(new Resolved(typeToInject, arg));
|
||||
}
|
||||
setterAction.accept(arg);
|
||||
}
|
||||
}
|
||||
|
||||
protected void injectSetters(CreateContext context, Object target) {
|
||||
this.getCachedSettersFor(target).forEach(setter -> this.injectSetter(context, target, setter));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public <T> T createInstance(Class<T> type, Object... constructorArgs) {
|
||||
final Constructor<T> constructor = this.findConstructor(type, constructorArgs);
|
||||
final CreateContext context = new CreateContext(type);
|
||||
final Object[] allArgs = this.createArgs(context, constructor, constructorArgs);
|
||||
try {
|
||||
final T instance = constructor.newInstance(allArgs);
|
||||
context.getAllResolved().add(new Resolved(type, instance));
|
||||
this.injectSetters(context, instance);
|
||||
final List<Consumer<Object>> deferredSetterActions = context.getDeferredSetterActions();
|
||||
context.popConstruction();
|
||||
deferredSetterActions.forEach(setterAction -> setterAction.accept(instance));
|
||||
return instance;
|
||||
} catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
|
||||
throw new RuntimeException(e); // In the future, we might have an option to ignore exceptions
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> T createInstance(CreateContext context, Class<T> type, Object... givenArgs) {
|
||||
final Constructor<T> constructor = this.findConstructor(type, givenArgs);
|
||||
context.checkForCircularDependency(type);
|
||||
context.pushConstruction(type);
|
||||
final Object[] allArgs = this.createArgs(context, constructor, givenArgs);
|
||||
try {
|
||||
final T instance = constructor.newInstance(allArgs);
|
||||
context.getAllResolved().add(new Resolved(type, instance));
|
||||
this.injectSetters(context, instance);
|
||||
final List<Consumer<Object>> deferredSetterActions = context.getDeferredSetterActions();
|
||||
context.popConstruction();
|
||||
deferredSetterActions.forEach(setterAction -> setterAction.accept(instance));
|
||||
return instance;
|
||||
} catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Object[] createArgs(
|
||||
CreateContext context,
|
||||
Constructor<?> constructor,
|
||||
Object[] constructorArgs
|
||||
);
|
||||
|
||||
protected abstract Object getSetterInjectArg(
|
||||
CreateContext context,
|
||||
Class<?> targetType,
|
||||
Method setter,
|
||||
Parameter toInject
|
||||
);
|
||||
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import groowt.util.di.filters.FilterHandler;
|
||||
import groowt.util.di.filters.IterableFilterHandler;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static groowt.util.di.RegistryObjectFactoryUtil.orElseSupply;
|
||||
|
||||
public abstract class AbstractRegistryObjectFactory extends AbstractInjectingObjectFactory
|
||||
implements RegistryObjectFactory {
|
||||
|
||||
public static abstract class AbstractBuilder<T extends DefaultRegistryObjectFactory> implements Builder<T> {
|
||||
|
||||
private final Collection<FilterHandler<?, ?>> filterHandlers = new ArrayList<>();
|
||||
private final Collection<IterableFilterHandler<?, ?>> iterableFilterHandlers = new ArrayList<>();
|
||||
private final Registry registry;
|
||||
private @Nullable RegistryObjectFactory parent;
|
||||
|
||||
public AbstractBuilder(Registry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public AbstractBuilder() {
|
||||
this.registry = new DefaultRegistry();
|
||||
}
|
||||
|
||||
protected Registry getRegistry() {
|
||||
return this.registry;
|
||||
}
|
||||
|
||||
protected Collection<FilterHandler<?, ?>> getFilterHandlers() {
|
||||
return this.filterHandlers;
|
||||
}
|
||||
|
||||
protected Collection<IterableFilterHandler<?, ?>> getIterableFilterHandlers() {
|
||||
return this.iterableFilterHandlers;
|
||||
}
|
||||
|
||||
protected @Nullable RegistryObjectFactory getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureRegistry(Consumer<? super Registry> configure) {
|
||||
configure.accept(this.registry);
|
||||
}
|
||||
|
||||
public void addFilterHandler(FilterHandler<?, ?> handler) {
|
||||
this.filterHandlers.add(handler);
|
||||
}
|
||||
|
||||
public void addIterableFilterHandler(IterableFilterHandler<?, ?> handler) {
|
||||
this.iterableFilterHandlers.add(handler);
|
||||
}
|
||||
|
||||
public void setParent(@Nullable RegistryObjectFactory parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected final Registry registry;
|
||||
@Nullable private final RegistryObjectFactory parent;
|
||||
|
||||
public AbstractRegistryObjectFactory(Registry registry, @Nullable RegistryObjectFactory parent) {
|
||||
this.registry = registry;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registry getRegistry() {
|
||||
return this.registry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> @Nullable ScopeHandler<A> findScopeHandler(Class<A> scopeType) {
|
||||
return this.registry.getScopeHandler(scopeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> @Nullable QualifierHandler<A> findQualifierHandler(Class<A> qualifierType) {
|
||||
return this.registry.getQualifierHandler(qualifierType);
|
||||
}
|
||||
|
||||
protected final <T> Optional<T> findInParent(Function<? super RegistryObjectFactory, @Nullable T> finder) {
|
||||
return this.parent != null ? Optional.ofNullable(finder.apply(this.parent)) : Optional.empty();
|
||||
}
|
||||
|
||||
protected final <T> Optional<T> findInSelfOrParent(Function<? super RegistryObjectFactory, @Nullable T> finder) {
|
||||
return orElseSupply(
|
||||
finder.apply(this),
|
||||
() -> this.parent != null ? finder.apply(this.parent) : null
|
||||
);
|
||||
}
|
||||
|
||||
protected final <T> T getInSelfOrParent(
|
||||
Function<? super RegistryObjectFactory, @Nullable T> finder,
|
||||
Supplier<? extends RuntimeException> exceptionSupplier
|
||||
) {
|
||||
return orElseSupply(
|
||||
finder.apply(this),
|
||||
() -> this.parent != null ? finder.apply(this.parent) : null
|
||||
).orElseThrow(exceptionSupplier);
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
sealed public interface Binding<T> permits ClassBinding, ProviderBinding, SingletonBinding, LazySingletonBinding {}
|
@ -1,12 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Provider;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface BindingConfigurator<T> {
|
||||
void to(Class<? extends T> target);
|
||||
void toProvider(Provider<? extends T> provider);
|
||||
void toSingleton(T target);
|
||||
void toLazySingleton(Supplier<T> singletonSupplier);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Provider;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class BindingUtil {
|
||||
|
||||
public static <T> Consumer<BindingConfigurator<T>> toClass(Class<? extends T> clazz) {
|
||||
return bc -> bc.to(clazz);
|
||||
}
|
||||
|
||||
public static <T> Consumer<BindingConfigurator<T>> toProvider(Provider<? extends T> provider) {
|
||||
return bc -> bc.toProvider(provider);
|
||||
}
|
||||
|
||||
public static <T> Consumer<BindingConfigurator<T>> toSingleton(T singleton) {
|
||||
return bc -> bc.toSingleton(singleton);
|
||||
}
|
||||
|
||||
public static <T> Consumer<BindingConfigurator<T>> toLazySingleton(Supplier<T> singletonSupplier) {
|
||||
return bc -> bc.toLazySingleton(singletonSupplier);
|
||||
}
|
||||
|
||||
public static <T> Consumer<BindingConfigurator<T>> toSelf() {
|
||||
return bc -> {};
|
||||
}
|
||||
|
||||
public static <T> KeyHolder<NamedRegistryExtension, String, T> named(String name, Class<T> type) {
|
||||
return new SimpleKeyHolder<>(NamedRegistryExtension.class, type, name);
|
||||
}
|
||||
|
||||
private BindingUtil() {}
|
||||
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
public record ClassBinding<T>(Class<T> from, Class<? extends T> to) implements Binding<T> {}
|
@ -1,80 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Named;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class DefaultNamedRegistryExtension implements NamedRegistryExtension {
|
||||
|
||||
protected static class NamedQualifierHandler implements QualifierHandler<Named> {
|
||||
|
||||
private final DefaultNamedRegistryExtension extension;
|
||||
|
||||
public NamedQualifierHandler(DefaultNamedRegistryExtension extension) {
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable Binding<T> handle(Named named, Class<T> dependencyClass) {
|
||||
return this.extension.getBinding(
|
||||
new SimpleKeyHolder<>(NamedRegistryExtension.class, dependencyClass, named.value())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected final Map<String, Binding<?>> bindings = new HashMap<>();
|
||||
protected final QualifierHandler<Named> qualifierHandler = this.getNamedQualifierHandler();
|
||||
|
||||
protected QualifierHandler<Named> getNamedQualifierHandler() {
|
||||
return new NamedQualifierHandler(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @Nullable <A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> qualifierType) {
|
||||
return Named.class.equals(qualifierType) ? (QualifierHandler<A>) this.qualifierHandler : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getKeyClass() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B extends KeyBinder<String>, T> void bind(KeyHolder<B, ? extends String, T> keyHolder, Consumer<? super BindingConfigurator<T>> configure) {
|
||||
final var configurator = new SimpleBindingConfigurator<>(keyHolder.type());
|
||||
configure.accept(configurator);
|
||||
this.bindings.put(keyHolder.key(), configurator.getBinding());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @Nullable <B extends KeyBinder<String>, T> Binding<T> getBinding(KeyHolder<B, ? extends String, T> keyHolder) {
|
||||
return (Binding<T>) this.bindings.getOrDefault(keyHolder.key(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B extends KeyBinder<String>, T> void removeBinding(KeyHolder<B, ? extends String, T> keyHolder) {
|
||||
this.bindings.remove(keyHolder.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B extends KeyBinder<String>, T> void removeBindingIf(KeyHolder<B, ? extends String, T> keyHolder, Predicate<? super Binding<T>> filter) {
|
||||
final String key = keyHolder.key();
|
||||
if (this.bindings.containsKey(key) && filter.test(this.getBinding(keyHolder))) {
|
||||
this.bindings.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllBindings() {
|
||||
this.bindings.clear();
|
||||
}
|
||||
|
||||
}
|
@ -1,221 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class DefaultRegistry implements Registry {
|
||||
|
||||
protected static class BindingContainer {
|
||||
|
||||
private final Map<Class<?>, Binding<?>> bindings = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> @Nullable Binding<T> get(Class<T> key) {
|
||||
for (final var entry : bindings.entrySet()) {
|
||||
if (entry.getKey().isAssignableFrom(key)) {
|
||||
return (Binding<T>) entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> void put(Class<T> key, Binding<T> binding) {
|
||||
this.bindings.put(key, binding);
|
||||
}
|
||||
|
||||
public void remove(Class<?> key) {
|
||||
this.bindings.remove(key);
|
||||
}
|
||||
|
||||
public <T> void removeIf(Class<T> key, Predicate<? super Binding<T>> filter) {
|
||||
if (filter.test(this.get(key))) {
|
||||
this.bindings.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.bindings.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected final BindingContainer bindingContainer = new BindingContainer();
|
||||
protected final Collection<RegistryExtension> extensions = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void removeBinding(Class<?> key) {
|
||||
this.bindingContainer.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void removeBindingIf(Class<T> key, Predicate<Binding<T>> filter) {
|
||||
this.bindingContainer.removeIf(key, filter);
|
||||
}
|
||||
|
||||
private <E extends RegistryExtension> List<E> getAllRegistryExtensions(Class<E> extensionType) {
|
||||
return this.extensions.stream()
|
||||
.filter(extension -> extensionType.isAssignableFrom(extension.getClass()))
|
||||
.map(extensionType::cast)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private <E extends RegistryExtension> E getOneRegistryExtension(Class<E> extensionType) {
|
||||
final List<E> extensions = this.getAllRegistryExtensions(extensionType);
|
||||
if (extensions.size() == 1) {
|
||||
return extensions.getFirst();
|
||||
} else if (extensions.isEmpty()) {
|
||||
throw new IllegalArgumentException("There is no " + extensionType + " registered for this " + this);
|
||||
} else {
|
||||
throw new IllegalArgumentException("There is more than one " + extensionType + " registered for this " + this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtension(RegistryExtension extension) {
|
||||
final List<? extends RegistryExtension> existing = this.getAllRegistryExtensions(extension.getClass());
|
||||
if (existing.isEmpty()) {
|
||||
this.extensions.add(extension);
|
||||
} else {
|
||||
throw new IllegalArgumentException("There is already at least one " + extension.getClass() + " registered in " + this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends RegistryExtension> E getExtension(Class<E> extensionType) {
|
||||
return this.getOneRegistryExtension(extensionType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends RegistryExtension> Collection<E> getExtensions(Class<E> extensionType) {
|
||||
return this.getAllRegistryExtensions(extensionType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExtension(RegistryExtension extension) {
|
||||
this.extensions.remove(extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> qualifierType) {
|
||||
final List<QualifierHandler<A>> handlers = new ArrayList<>();
|
||||
for (final var extension : this.extensions) {
|
||||
if (extension instanceof QualifierHandlerContainer handlerContainer) {
|
||||
final var handler = handlerContainer.getQualifierHandler(qualifierType);
|
||||
if (handler != null) {
|
||||
handlers.add(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handlers.isEmpty()) {
|
||||
return null;
|
||||
} else if (handlers.size() > 1) {
|
||||
throw new RuntimeException("There is more than one QualifierHandler for " + qualifierType.getName());
|
||||
} else {
|
||||
return handlers.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <A extends Annotation> ScopeHandler<A> getScopeHandler(Class<A> scopeType) {
|
||||
final List<ScopeHandler<A>> handlers = new ArrayList<>();
|
||||
for (final var extension : this.extensions) {
|
||||
if (extension instanceof ScopeHandlerContainer handlerContainer) {
|
||||
final var handler = handlerContainer.getScopeHandler(scopeType);
|
||||
if (handler != null) {
|
||||
handlers.add(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handlers.isEmpty()) {
|
||||
return null;
|
||||
} else if (handlers.size() > 1) {
|
||||
throw new RuntimeException("There is more than one ScopeHandler for " + scopeType.getName());
|
||||
} else {
|
||||
return handlers.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void bind(Class<T> key, Consumer<? super BindingConfigurator<T>> configure) {
|
||||
final var configurator = new SimpleBindingConfigurator<>(key);
|
||||
configure.accept(configurator);
|
||||
this.bindingContainer.put(key, configurator.getBinding());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable Binding<T> getBinding(Class<T> key) {
|
||||
return this.bindingContainer.get(key);
|
||||
}
|
||||
|
||||
private KeyBinder<?> findKeyBinder(Class<?> keyClass) {
|
||||
final List<KeyBinder<?>> binders = new ArrayList<>();
|
||||
for (final var extension : this.extensions) {
|
||||
if (extension instanceof KeyBinder<?> keyBinder && keyBinder.getKeyClass().isAssignableFrom(keyClass)) {
|
||||
binders.add(keyBinder);
|
||||
}
|
||||
}
|
||||
if (binders.isEmpty()) {
|
||||
throw new IllegalArgumentException("There are no configured RegistryExtensions that can handle keys with type " + keyClass.getName());
|
||||
} else if (binders.size() > 1) {
|
||||
throw new IllegalArgumentException("There is more than one configured RegistryExtension that can handle keys with type " + keyClass.getName());
|
||||
} else {
|
||||
return binders.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected final void withKeyBinder(KeyHolder<?, ?, ?> keyHolder, Consumer<KeyBinder> action) {
|
||||
action.accept(this.findKeyBinder(keyHolder.key().getClass()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected final <R> @Nullable R tapKeyBinder(
|
||||
KeyHolder<?, ?, ?> keyHolder,
|
||||
Function<KeyBinder, @Nullable R> function
|
||||
) {
|
||||
return function.apply(this.findKeyBinder(keyHolder.key().getClass()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> void bind(KeyHolder<?, ?, T> keyHolder, Consumer<? super BindingConfigurator<T>> configure) {
|
||||
this.withKeyBinder(keyHolder, b -> b.bind(keyHolder, configure));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @Nullable <T> Binding<T> getBinding(KeyHolder<?, ?, T> keyHolder) {
|
||||
return this.tapKeyBinder(keyHolder, b -> b.getBinding(keyHolder));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> void removeBinding(KeyHolder<?, ?, T> keyHolder) {
|
||||
this.withKeyBinder(keyHolder, b -> b.removeBinding(keyHolder));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> void removeBindingIf(
|
||||
KeyHolder<?, ?, T> keyHolder,
|
||||
Predicate<? super Binding<T>> filter
|
||||
) {
|
||||
this.withKeyBinder(keyHolder, b -> b.removeBindingIf(keyHolder, filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllBindings() {
|
||||
this.bindingContainer.clear();
|
||||
for (final var extension : this.extensions) {
|
||||
if (extension instanceof KeyBinder<?> keyBinder) {
|
||||
keyBinder.clearAllBindings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,348 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import groowt.util.di.filters.FilterHandler;
|
||||
import groowt.util.di.filters.IterableFilterHandler;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static groowt.util.di.RegistryObjectFactoryUtil.*;
|
||||
|
||||
public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory {
|
||||
|
||||
public static final class Builder
|
||||
extends AbstractRegistryObjectFactory.AbstractBuilder<DefaultRegistryObjectFactory> {
|
||||
|
||||
/**
|
||||
* Creates a {@code Builder} initialized with a {@link DefaultRegistry}, which is in-turn configured with a
|
||||
* {@link NamedRegistryExtension} and a {@link SingletonScopeHandler}.
|
||||
*
|
||||
* @return the builder
|
||||
*/
|
||||
public static Builder withDefaults() {
|
||||
final var b = new Builder();
|
||||
|
||||
b.configureRegistry(r -> {
|
||||
r.addExtension(new DefaultNamedRegistryExtension());
|
||||
r.addExtension(new SingletonRegistryExtension(r));
|
||||
});
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a blank builder with a {@link Registry} from the given {@link Supplier}.
|
||||
*/
|
||||
public static Builder withRegistry(Supplier<? extends Registry> registrySupplier) {
|
||||
return new Builder(registrySupplier.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a blank builder which will use {@link DefaultRegistry}.
|
||||
*/
|
||||
public static Builder blank() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
private Builder(Registry registry) {
|
||||
super(registry);
|
||||
}
|
||||
|
||||
private Builder() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultRegistryObjectFactory build() {
|
||||
return new DefaultRegistryObjectFactory(
|
||||
this.getRegistry(),
|
||||
this.getParent(),
|
||||
this.getFilterHandlers(),
|
||||
this.getIterableFilterHandlers()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
|
||||
|
||||
private final Collection<FilterHandler<?, ?>> filterHandlers;
|
||||
private final Collection<IterableFilterHandler<?, ?>> iterableFilterHandlers;
|
||||
|
||||
protected DefaultRegistryObjectFactory(
|
||||
Registry registry,
|
||||
@Nullable RegistryObjectFactory parent,
|
||||
Collection<? extends FilterHandler<?, ?>> filterHandlers,
|
||||
Collection<? extends IterableFilterHandler<?, ?>> iterableFilterHandlers
|
||||
) {
|
||||
super(registry, parent);
|
||||
this.filterHandlers = new ArrayList<>(filterHandlers);
|
||||
this.filterHandlers.forEach(handler -> checkIsValidFilter(handler.getAnnotationClass()));
|
||||
|
||||
this.iterableFilterHandlers = new ArrayList<>(iterableFilterHandlers);
|
||||
this.iterableFilterHandlers.forEach(handler -> checkIsValidIterableFilter(handler.getAnnotationClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given parameter has any qualifier annotations; if it does,
|
||||
* it delegates finding the desired object to the registered {@link QualifierHandler}.
|
||||
*
|
||||
* @param parameter the parameter
|
||||
* @return the object returned from the {@code QualifierHandler}, or {@code null} if no qualifier
|
||||
* is present or the {@code QualifierHandler} itself returns {@code null}.
|
||||
*
|
||||
* @throws RuntimeException if no {@code QualifierHandler} is registered for a qualifier annotation present on the
|
||||
* given parameter, or if the handler itself throws an exception.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final @Nullable Object tryQualifiers(Parameter parameter) {
|
||||
final Class<?> paramType = parameter.getType();
|
||||
final List<Annotation> qualifiers = RegistryObjectFactoryUtil.getQualifierAnnotations(
|
||||
parameter.getAnnotations()
|
||||
);
|
||||
if (qualifiers.size() > 1) {
|
||||
throw new RuntimeException("Parameter " + parameter + " cannot have more than one Qualifier annotation.");
|
||||
} else if (qualifiers.size() == 1) {
|
||||
final Annotation qualifier = qualifiers.getFirst();
|
||||
@SuppressWarnings("rawtypes")
|
||||
final QualifierHandler handler = this.getInSelfOrParent(
|
||||
f -> f.findQualifierHandler(qualifier.annotationType()),
|
||||
() -> new RuntimeException("There is no configured QualifierHandler for "
|
||||
+ qualifier.annotationType().getName()
|
||||
)
|
||||
);
|
||||
final Binding<?> binding = handler.handle(qualifier, paramType);
|
||||
if (binding != null) {
|
||||
return this.handleBinding(binding, EMPTY_OBJECT_ARRAY);
|
||||
}
|
||||
}
|
||||
// no Qualifier or the QualifierHandler didn't return a Binding
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the {@code resolvedArg} against all filters present on the given parameter.
|
||||
*
|
||||
* @param parameter the parameter
|
||||
* @param resolvedArg the resolved argument
|
||||
*
|
||||
* @throws RuntimeException if the {@link FilterHandler} itself throws an exception.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
protected final void checkFilters(Parameter parameter, Object resolvedArg) {
|
||||
final Annotation[] allAnnotations = parameter.getAnnotations();
|
||||
final Collection<Annotation> filterAnnotations = getFilterAnnotations(allAnnotations);
|
||||
if (!filterAnnotations.isEmpty()) {
|
||||
final Collection<FilterHandler<?, ?>> filtersForParamType = this.filterHandlers.stream()
|
||||
.filter(filterHandler ->
|
||||
filterHandler.getArgumentClass().isAssignableFrom(parameter.getType())
|
||||
)
|
||||
.toList();
|
||||
for (final Annotation filterAnnotation : filterAnnotations) {
|
||||
for (final FilterHandler<?, ?> filterHandler : filtersForParamType) {
|
||||
if (filterAnnotation.annotationType().equals(filterHandler.getAnnotationClass())) {
|
||||
// hopefully we've checked everything
|
||||
((FilterHandler<Annotation, Object>) filterHandler).check(filterAnnotation, resolvedArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final Collection<Annotation> iterableFilterAnnotations = getIterableFilterAnnotations(allAnnotations);
|
||||
if (!iterableFilterAnnotations.isEmpty() && resolvedArg instanceof Iterable iterable) {
|
||||
for (final var annotation : iterableFilterAnnotations) {
|
||||
this.iterableFilterHandlers.stream()
|
||||
.filter(handler -> handler.getAnnotationClass().equals(annotation.annotationType()))
|
||||
.forEach(handler -> {
|
||||
((IterableFilterHandler<Annotation, Object>) handler).check(annotation, iterable);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Object resolveInjectedArg(CreateContext context, Parameter parameter) {
|
||||
final Object qualifierProvidedArg = this.tryQualifiers(parameter);
|
||||
if (qualifierProvidedArg != null) {
|
||||
this.checkFilters(parameter, qualifierProvidedArg);
|
||||
context.getAllResolved().add(new Resolved(parameter.getType(), qualifierProvidedArg));
|
||||
return qualifierProvidedArg;
|
||||
} else {
|
||||
final Object created = this.get(context, parameter.getType());
|
||||
this.checkFilters(parameter, created);
|
||||
context.getAllResolved().add(new Resolved(parameter.getType(), created));
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
protected final void resolveInjectedArgs(CreateContext context, Object[] dest, Parameter[] params) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
dest[i] = this.resolveInjectedArg(context, params[i]);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void resolveGivenArgs(Object[] dest, Parameter[] params, Object[] givenArgs, int startIndex) {
|
||||
for (int i = startIndex; i < dest.length; i++) {
|
||||
final int resolveIndex = i - startIndex;
|
||||
final Object arg = givenArgs[resolveIndex];
|
||||
this.checkFilters(params[resolveIndex], arg);
|
||||
dest[i] = arg;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: when there is a null arg, we lose the type. Therefore this algorithm breaks. Fix this.
|
||||
@Override
|
||||
protected Object[] createArgs(CreateContext context, Constructor<?> constructor, Object[] givenArgs) {
|
||||
final Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||
|
||||
// check no arg
|
||||
if (paramTypes.length == 0 && givenArgs.length == 0) {
|
||||
// no args given, none needed, so return empty array
|
||||
return EMPTY_OBJECT_ARRAY;
|
||||
} else if (paramTypes.length == 0) { // implicit that givenArgs.length != 0
|
||||
// zero expected, but got given args
|
||||
throw new RuntimeException(
|
||||
"Expected zero args for constructor " + constructor + " but received " + Arrays.toString(givenArgs)
|
||||
);
|
||||
} else if (givenArgs.length > paramTypes.length) {
|
||||
// expected is more than zero, but received too many given
|
||||
throw new RuntimeException(
|
||||
"Too many args given for constructor " + constructor + "; received " + Arrays.toString(givenArgs)
|
||||
);
|
||||
}
|
||||
|
||||
final Parameter[] allParams = constructor.getParameters();
|
||||
final Object[] resolvedArgs = new Object[allParams.length];
|
||||
|
||||
if (givenArgs.length == 0) {
|
||||
// if no given args, then they are all injected
|
||||
this.resolveInjectedArgs(context, resolvedArgs, allParams);
|
||||
} else if (givenArgs.length == paramTypes.length) {
|
||||
// all are given
|
||||
this.resolveGivenArgs(resolvedArgs, allParams, givenArgs, 0);
|
||||
} else {
|
||||
// some are injected, some are given
|
||||
// everything before (non-inclusive) is injected
|
||||
// everything after (inclusive) is given
|
||||
// ex: 1 inject, 1 given -> 2 (allParams) - 1 = 1
|
||||
// ex: 0 inject, 1 given -> 1 - 1 = 0
|
||||
final int firstGivenIndex = allParams.length - givenArgs.length;
|
||||
|
||||
final Parameter[] injectedParams = new Parameter[firstGivenIndex];
|
||||
final Parameter[] givenParams = new Parameter[allParams.length - firstGivenIndex];
|
||||
|
||||
System.arraycopy(allParams, 0, injectedParams, 0, injectedParams.length);
|
||||
System.arraycopy(
|
||||
allParams, firstGivenIndex, givenParams, 0, allParams.length - firstGivenIndex
|
||||
);
|
||||
|
||||
this.resolveInjectedArgs(context, resolvedArgs, injectedParams);
|
||||
this.resolveGivenArgs(resolvedArgs, givenParams, givenArgs, firstGivenIndex);
|
||||
}
|
||||
|
||||
return resolvedArgs;
|
||||
}
|
||||
|
||||
private <T> T handleBinding(Binding<T> binding, Object[] constructorArgs) {
|
||||
return this.handleBinding(binding, null, constructorArgs);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T handleBinding(Binding<T> binding, @Nullable CreateContext context, Object[] constructorArgs) {
|
||||
return switch (binding) {
|
||||
case ClassBinding<T>(Class<T> ignored, Class<? extends T> to) -> {
|
||||
final Annotation scopeAnnotation = getScopeAnnotation(to);
|
||||
if (scopeAnnotation != null) {
|
||||
final Class<? extends Annotation> scopeClass = scopeAnnotation.annotationType();
|
||||
@SuppressWarnings("rawtypes")
|
||||
final ScopeHandler scopeHandler = this.getInSelfOrParent(
|
||||
f -> f.findScopeHandler(scopeClass),
|
||||
() -> new RuntimeException(
|
||||
"There is no configured ScopeHandler for " + scopeClass.getName()
|
||||
)
|
||||
);
|
||||
final Binding<T> scopedBinding = scopeHandler.onScopedDependencyRequest(
|
||||
scopeAnnotation, to, this
|
||||
);
|
||||
yield this.handleBinding(scopedBinding, constructorArgs);
|
||||
} else {
|
||||
if (context != null) {
|
||||
yield this.createInstance(context, to, constructorArgs);
|
||||
} else {
|
||||
yield this.createInstance(to, constructorArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
case ProviderBinding<T> providerBinding -> providerBinding.provider().get();
|
||||
case SingletonBinding<T> singletonBinding -> singletonBinding.to();
|
||||
case LazySingletonBinding<T> lazySingletonBinding -> lazySingletonBinding.singletonSupplier().get();
|
||||
};
|
||||
}
|
||||
|
||||
protected final <T> @Nullable Binding<T> searchRegistry(Class<T> from) {
|
||||
return this.registry.getBinding(from);
|
||||
}
|
||||
|
||||
protected @Nullable <T> T tryParent(Class<T> clazz, Object[] constructorArgs) {
|
||||
return this.findInParent(f -> f.getOrNull(clazz, constructorArgs)).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getSetterInjectArg(CreateContext context, Class<?> targetType, Method setter, Parameter toInject) {
|
||||
return this.resolveInjectedArg(context, toInject);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public <T> T get(Class<T> clazz, Object... constructorArgs) {
|
||||
final Binding<T> binding = this.searchRegistry(clazz);
|
||||
if (binding != null) {
|
||||
return this.handleBinding(binding, constructorArgs);
|
||||
}
|
||||
final T parentResult = this.tryParent(clazz, constructorArgs);
|
||||
if (parentResult != null) {
|
||||
return parentResult;
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"No bindings for " + clazz + " with args " + Arrays.toString(constructorArgs) + "."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> T get(CreateContext context, Class<T> type) {
|
||||
final Binding<T> binding = this.searchRegistry(type);
|
||||
if (binding != null) {
|
||||
return this.handleBinding(binding, context, EMPTY_OBJECT_ARRAY);
|
||||
}
|
||||
final T parentResult = this.tryParent(type, EMPTY_OBJECT_ARRAY);
|
||||
if (parentResult != null) {
|
||||
return parentResult;
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"No bindings for " + type + " with args " + Arrays.toString(EMPTY_OBJECT_ARRAY) + "."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public <T> T getOrDefault(Class<T> clazz, T defaultValue, Object... constructorArgs) {
|
||||
final Binding<T> binding = this.searchRegistry(clazz);
|
||||
if (binding != null) {
|
||||
return this.handleBinding(binding, constructorArgs);
|
||||
}
|
||||
final T parentResult = this.tryParent(clazz, constructorArgs);
|
||||
return parentResult != null ? parentResult : defaultValue;
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ExtensionContainer {
|
||||
void addExtension(RegistryExtension extension);
|
||||
<E extends RegistryExtension> E getExtension(Class<E> extensionType);
|
||||
<E extends RegistryExtension> Collection<E> getExtensions(Class<E> extensionType);
|
||||
void removeExtension(RegistryExtension extension);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface KeyBinder<K> {
|
||||
Class<K> getKeyClass();
|
||||
<B extends KeyBinder<K>, T> void bind(KeyHolder<B, ? extends K, T> keyHolder, Consumer<? super BindingConfigurator<T>> configure);
|
||||
<B extends KeyBinder<K>, T> @Nullable Binding<T> getBinding(KeyHolder<B, ? extends K, T> keyHolder);
|
||||
<B extends KeyBinder<K>, T> void removeBinding(KeyHolder<B, ? extends K, T> keyHolder);
|
||||
<B extends KeyBinder<K>, T> void removeBindingIf(KeyHolder<B, ? extends K, T> keyHolder, Predicate<? super Binding<T>> filter);
|
||||
void clearAllBindings();
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
public interface KeyHolder<B extends KeyBinder<K>, K, T> {
|
||||
Class<B> binderType();
|
||||
Class<T> type();
|
||||
K key();
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public record LazySingletonBinding<T>(Supplier<T> singletonSupplier) implements Binding<T> {}
|
@ -1,3 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
public interface NamedRegistryExtension extends RegistryExtension, KeyBinder<String>, QualifierHandlerContainer {}
|
@ -1,61 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An {@link ObjectFactory} is an object that can construct objects of given types.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ObjectFactory {
|
||||
|
||||
/**
|
||||
* Create a new instance of the given {@code instanceType} with the given constructor args.
|
||||
*
|
||||
* @apiNote An implementation may provide a subclass of the given instance type,
|
||||
* or it may directly instantiate the given type, if it is a class
|
||||
* and it can determine the correct constructor from the given arguments.
|
||||
* See individual implementation documentation for exact behavior.
|
||||
*
|
||||
* @implSpec It is up to individual implementations of {@link ObjectFactory} to determine how to
|
||||
* select the appropriate constructor for the given type. The returned
|
||||
* instance must be new and in a valid state.
|
||||
*
|
||||
* @param instanceType the {@link Class} of the desired type
|
||||
* @param constructorArgs any arguments to pass to the constructor(s) of the class.
|
||||
* @return the new instance
|
||||
* @param <T> the desired type
|
||||
*/
|
||||
@Contract("_, _-> new")
|
||||
<T> T createInstance(Class<T> instanceType, Object... constructorArgs);
|
||||
|
||||
/**
|
||||
* Very similar to {@link #createInstance(Class, Object...)}, but catches any {@link RuntimeException}
|
||||
* thrown by {@link #createInstance} and subsequently passes it to the given {@link Function}, returning
|
||||
* instead the return value of the {@link Function}.
|
||||
*
|
||||
* @param instanceType the desired type of the created instance
|
||||
* @param onException a {@link Function} to handle when an exception occurs and return a value nonetheless
|
||||
* @param constructorArgs arguments to pass to the constructor
|
||||
* @return the created instance
|
||||
* @param <T> the desired type
|
||||
*
|
||||
* @throws RuntimeException if the given {@link Function} itself throws a RuntimeException
|
||||
*
|
||||
* @see #createInstance(Class, Object...)
|
||||
*/
|
||||
@Contract("_, _, _ -> new")
|
||||
default <T> T createInstanceCatching(
|
||||
Class<T> instanceType,
|
||||
Function<? super RuntimeException, ? extends T> onException,
|
||||
Object... constructorArgs
|
||||
) {
|
||||
try {
|
||||
return this.createInstance(instanceType, constructorArgs);
|
||||
} catch (RuntimeException runtimeException) {
|
||||
return onException.apply(runtimeException);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class ObjectFactoryUtil {
|
||||
|
||||
public static Class<?>[] toTypes(Object... objects) {
|
||||
final Class<?>[] types = new Class<?>[objects.length];
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
final Object o = objects[i];
|
||||
if (o != null) {
|
||||
types[i] = o.getClass();
|
||||
} else {
|
||||
types[i] = Object.class;
|
||||
}
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
private ObjectFactoryUtil() {}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Provider;
|
||||
|
||||
public record ProviderBinding<T>(Class<T> to, Provider<? extends T> provider) implements Binding<T> {}
|
@ -1,10 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface QualifierHandler<A extends Annotation> {
|
||||
<T> @Nullable Binding<T> handle(A annotation, Class<T> dependencyClass);
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Qualifier;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public interface QualifierHandlerContainer {
|
||||
|
||||
static void checkIsValidQualifier(Class<? extends Annotation> annotationClass) {
|
||||
if (!annotationClass.isAnnotationPresent(Qualifier.class)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The given qualifier annotation + " + annotationClass + " is itself not annotated with @Qualifier"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
<A extends Annotation> @Nullable QualifierHandler<A> getQualifierHandler(Class<A> qualifierType);
|
||||
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface Registry extends ExtensionContainer, QualifierHandlerContainer, ScopeHandlerContainer {
|
||||
|
||||
<T> void bind(Class<T> key, Consumer<? super BindingConfigurator<T>> configure);
|
||||
@Nullable <T> Binding<T> getBinding(Class<T> key);
|
||||
void removeBinding(Class<?> key);
|
||||
<T> void removeBindingIf(Class<T> key, Predicate<Binding<T>> filter);
|
||||
|
||||
<T> void bind(KeyHolder<?, ?, T> keyHolder, Consumer<? super BindingConfigurator<T>> configure);
|
||||
<T> @Nullable Binding<T> getBinding(KeyHolder<?, ?, T> keyHolder);
|
||||
<T> void removeBinding(KeyHolder<?, ?, T> keyHolder);
|
||||
<T> void removeBindingIf(KeyHolder<?, ?, T> keyHolder, Predicate<? super Binding<T>> filter);
|
||||
void clearAllBindings();
|
||||
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
public interface RegistryExtension {}
|
@ -1,126 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groovy.lang.DelegatesTo;
|
||||
import groowt.util.di.filters.FilterHandler;
|
||||
import groowt.util.di.filters.IterableFilterHandler;
|
||||
import jakarta.inject.Provider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A {@link RegistryObjectFactory} is an {@link ObjectFactory} that offers the ability
|
||||
* to provide desired objects based on an instance of
|
||||
* {@link Registry} to determine how to provide those objects.
|
||||
*/
|
||||
public interface RegistryObjectFactory extends ObjectFactory {
|
||||
|
||||
interface Builder<T extends RegistryObjectFactory> {
|
||||
|
||||
void configureRegistry(Consumer<? super Registry> configure);
|
||||
|
||||
default void configureRegistry(@DelegatesTo(Registry.class) Closure<?> configureClosure) {
|
||||
this.configureRegistry(registry -> {
|
||||
configureClosure.setDelegate(registry);
|
||||
configureClosure.call();
|
||||
});
|
||||
}
|
||||
|
||||
void addFilterHandler(FilterHandler<?, ?> handler);
|
||||
|
||||
void addIterableFilterHandler(IterableFilterHandler<?, ?> handler);
|
||||
|
||||
T build();
|
||||
|
||||
}
|
||||
|
||||
Registry getRegistry();
|
||||
|
||||
default void configureRegistry(Consumer<? super Registry> use) {
|
||||
use.accept(this.getRegistry());
|
||||
}
|
||||
|
||||
default void configureRegistry(
|
||||
@DelegatesTo(value = Registry.class)
|
||||
Closure<?> configureClosure
|
||||
) {
|
||||
final Registry registry = this.getRegistry();
|
||||
configureClosure.setDelegate(registry);
|
||||
configureClosure.call();
|
||||
}
|
||||
|
||||
<A extends Annotation> @Nullable ScopeHandler<A> findScopeHandler(Class<A> scopeType);
|
||||
|
||||
<A extends Annotation> @Nullable QualifierHandler<A> findQualifierHandler(Class<A> qualifierType);
|
||||
|
||||
/**
|
||||
* Get an object with the desired type. How it is retrieved/created
|
||||
* depends upon the {@link Binding} present in this {@link RegistryObjectFactory}'s held
|
||||
* instances of {@link Registry}. The type of the {@link Binding} determines
|
||||
* how the object is fetched:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link ClassBinding}: A new instance of the object is created using the given {@code constructorArgs}.</li>
|
||||
* <li>{@link ProviderBinding}: An instance of the object is fetched from the bound {@link Provider}.
|
||||
* Whether the instance is new or not depends on the {@link Provider}.</li>
|
||||
* <li>{@link SingletonBinding}: The bound singleton object is returned.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @implNote If {@code constructorArgs} are provided
|
||||
* and the {@link Binding} for the desired type is not a
|
||||
* {@link ClassBinding}, the implementation should
|
||||
* either throw an exception or log a warning at the least.
|
||||
*
|
||||
* @param clazz the {@link Class} of the desired type
|
||||
* @param constructorArgs As in {@link #createInstance(Class, Object...)},
|
||||
* the arguments which will be used to create the desired object
|
||||
* if the {@link Binding} is a {@link ClassBinding}.
|
||||
* @return an object of the desired type
|
||||
* @param <T> the desired type
|
||||
*
|
||||
* @throws RuntimeException if there is no registered {@link Binding} or there is a problem
|
||||
* fetching or constructing the object.
|
||||
*/
|
||||
<T> T get(Class<T> clazz, Object... constructorArgs);
|
||||
|
||||
/**
|
||||
* Similarly to {@link #get(Class, Object...)}, fetches an object
|
||||
* of the desired type, but does not throw if there is no registered {@link Binding}
|
||||
* in any of the held instances of {@link Registry},
|
||||
* and instead returns the given {@code defaultValue}.
|
||||
*
|
||||
* @param clazz the {@link Class} of the desired type
|
||||
* @param defaultValue the defaultValue to return
|
||||
* @param constructorArgs see {@link #get(Class, Object...)}
|
||||
* @return an object of the desired type
|
||||
* @param <T> the desired type
|
||||
*
|
||||
* @throws RuntimeException if there <em>is</em> a registered {@link Binding} and there is a problem
|
||||
* fetching or constructing the object.
|
||||
*
|
||||
* @see #get(Class, Object...)
|
||||
*/
|
||||
<T> T getOrDefault(Class<T> clazz, T defaultValue, Object... constructorArgs);
|
||||
|
||||
/**
|
||||
* Similar to {@link #getOrDefault(Class, Object, Object...)}, except that
|
||||
* it returns null by default if there is no registered {@link Binding}.
|
||||
*
|
||||
* @param clazz the {@link Class} of the desired type
|
||||
* @param constructorArgs see {@link RegistryObjectFactory#get(Class, Object...)}
|
||||
* @return an object of the desired type
|
||||
* @param <T> the desired type
|
||||
*
|
||||
* @see RegistryObjectFactory#get(Class, Object...)
|
||||
* @see RegistryObjectFactory#getOrDefault(Class, Object, Object...)
|
||||
*
|
||||
* @throws RuntimeException if there <em>is</em> a registered {@code Binding} and there
|
||||
* is a problem fetching or constructing the object.
|
||||
*/
|
||||
default <T> @Nullable T getOrNull(Class<T> clazz, Object... constructorArgs) {
|
||||
return this.getOrDefault(clazz, null, constructorArgs);
|
||||
}
|
||||
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import groowt.util.di.filters.Filter;
|
||||
import groowt.util.di.filters.IterableFilter;
|
||||
import jakarta.inject.Qualifier;
|
||||
import jakarta.inject.Scope;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class RegistryObjectFactoryUtil {
|
||||
|
||||
private RegistryObjectFactoryUtil() {}
|
||||
|
||||
public static List<Annotation> getQualifierAnnotations(Annotation[] annotations) {
|
||||
return Arrays.stream(annotations)
|
||||
.filter(a -> a.annotationType().isAnnotationPresent(Qualifier.class))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static List<Annotation> getFilterAnnotations(Annotation[] annotations) {
|
||||
return Arrays.stream(annotations)
|
||||
.filter(a -> a.annotationType().isAnnotationPresent(Filter.class))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static List<Annotation> getIterableFilterAnnotations(Annotation[] annotations) {
|
||||
return Arrays.stream(annotations)
|
||||
.filter(a -> a.annotationType().isAnnotationPresent(IterableFilter.class))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static <T> Optional<T> orElseSupply(T first, Supplier<T> onNullFirst) {
|
||||
return first != null ? Optional.of(first) : Optional.ofNullable(onNullFirst.get());
|
||||
}
|
||||
|
||||
public static void checkIsValidFilter(Class<? extends Annotation> annotationClass) {
|
||||
if (!annotationClass.isAnnotationPresent(Filter.class)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The given filter annotation " + annotationClass.getName() + " is itself not annotated with @Filter"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkIsValidIterableFilter(Class<? extends Annotation> annotationClass) {
|
||||
if (!annotationClass.isAnnotationPresent(IterableFilter.class)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The given iterable filter annotation " + annotationClass.getName() + " is itself not annotated with @IterableFilter"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable Annotation getScopeAnnotation(Class<?> clazz) {
|
||||
final List<Annotation> scopeAnnotations = Arrays.stream(clazz.getAnnotations())
|
||||
.filter(annotation -> annotation.annotationType().isAnnotationPresent(Scope.class))
|
||||
.toList();
|
||||
if (scopeAnnotations.size() > 1) {
|
||||
throw new RuntimeException(clazz.getName() + " has too many annotations that are themselves annotated with @Scope");
|
||||
}
|
||||
return scopeAnnotations.size() == 1 ? scopeAnnotations.getFirst() : null;
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public interface ScopeHandler<A extends Annotation> {
|
||||
<T> Binding<T> onScopedDependencyRequest(A annotation, Class<T> dependencyClass, RegistryObjectFactory objectFactory);
|
||||
void reset();
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Scope;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public interface ScopeHandlerContainer {
|
||||
|
||||
static void checkIsValidScope(Class<? extends Annotation> scope) {
|
||||
if (!scope.isAnnotationPresent(Scope.class)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The given scope annotation " + scope + " is itself not annotated with @Scope"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
<A extends Annotation> @Nullable ScopeHandler<A> getScopeHandler(Class<A> scopeType);
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Provider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SimpleBindingConfigurator<T> implements BindingConfigurator<T> {
|
||||
|
||||
private final Class<T> from;
|
||||
private @Nullable Binding<T> binding;
|
||||
|
||||
public SimpleBindingConfigurator(Class<T> from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public final Binding<T> getBinding() {
|
||||
return this.binding != null
|
||||
? this.binding
|
||||
: new ClassBinding<>(this.from, this.from); // return SelfBinding in case we never called anything
|
||||
}
|
||||
|
||||
@Override
|
||||
public void to(Class<? extends T> target) {
|
||||
this.binding = new ClassBinding<>(this.from, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toProvider(Provider<? extends T> provider) {
|
||||
this.binding = new ProviderBinding<>(this.from, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toSingleton(T target) {
|
||||
this.binding = new SingletonBinding<>(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toLazySingleton(Supplier<T> singletonSupplier) {
|
||||
this.binding = new LazySingletonBinding<>(singletonSupplier);
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
public record SimpleKeyHolder<B extends KeyBinder<K>, K, T>(Class<B> binderType, Class<T> type, K key)
|
||||
implements KeyHolder<B, K, T> {}
|
@ -1,3 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
public record SingletonBinding<T>(T to) implements Binding<T> {}
|
@ -1,22 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public class SingletonRegistryExtension implements RegistryExtension, ScopeHandlerContainer {
|
||||
|
||||
private final SingletonScopeHandler handler;
|
||||
|
||||
public SingletonRegistryExtension(Registry owner) {
|
||||
this.handler = new SingletonScopeHandler(owner);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @Nullable <A extends Annotation> ScopeHandler<A> getScopeHandler(Class<A> scopeType) {
|
||||
return Singleton.class.isAssignableFrom(scopeType) ? (ScopeHandler<A>) this.handler : null;
|
||||
}
|
||||
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import static groowt.util.di.BindingUtil.toLazySingleton;
|
||||
|
||||
public final class SingletonScopeHandler implements ScopeHandler<Singleton> {
|
||||
|
||||
private final Registry owner;
|
||||
|
||||
public SingletonScopeHandler(Registry owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Binding<T> onScopedDependencyRequest(
|
||||
Singleton annotation,
|
||||
Class<T> dependencyClass,
|
||||
RegistryObjectFactory objectFactory
|
||||
) {
|
||||
final Binding<T> potentialBinding = this.owner.getBinding(dependencyClass);
|
||||
return switch (potentialBinding) {
|
||||
case ClassBinding<T>(Class<T> from, Class<? extends T> to) -> {
|
||||
this.owner.bind(from, toLazySingleton(() -> objectFactory.createInstance(to)));
|
||||
yield this.owner.getBinding(from);
|
||||
}
|
||||
case ProviderBinding<T>(Class<T> from, Provider<? extends T> provider) -> {
|
||||
this.owner.bind(from, toLazySingleton(provider::get));
|
||||
yield this.owner.getBinding(from);
|
||||
}
|
||||
case SingletonBinding<T> singletonBinding -> singletonBinding;
|
||||
case LazySingletonBinding<T> lazySingletonBinding -> lazySingletonBinding;
|
||||
case null -> {
|
||||
this.owner.bind(dependencyClass, toLazySingleton(() -> objectFactory.createInstance(dependencyClass)));
|
||||
yield this.owner.getBinding(dependencyClass);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
throw new UnsupportedOperationException("Cannot reset the Singleton scope!");
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package groowt.util.di.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface Given {}
|
@ -1,10 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.ANNOTATION_TYPE })
|
||||
public @interface Filter {}
|
@ -1,39 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
public interface FilterHandler<A extends Annotation, T> {
|
||||
|
||||
boolean check(A annotation, T arg);
|
||||
Class<A> getAnnotationClass();
|
||||
Class<T> getArgumentClass();
|
||||
|
||||
default FilterHandler<A, T> and(BiPredicate<A, T> and) {
|
||||
Objects.requireNonNull(and);
|
||||
return new SimpleFilterHandler<>(
|
||||
(a, t) -> this.check(a, t) && and.test(a, t),
|
||||
this.getAnnotationClass(),
|
||||
this.getArgumentClass()
|
||||
);
|
||||
}
|
||||
|
||||
default FilterHandler<A, T> or(BiPredicate<A, T> or) {
|
||||
Objects.requireNonNull(or);
|
||||
return new SimpleFilterHandler<>(
|
||||
(a, t) -> this.check(a, t) || or.test(a, t),
|
||||
this.getAnnotationClass(),
|
||||
this.getArgumentClass()
|
||||
);
|
||||
}
|
||||
|
||||
default FilterHandler<A, T> negate() {
|
||||
return new SimpleFilterHandler<>(
|
||||
(a, t) -> !this.check(a, t),
|
||||
this.getAnnotationClass(),
|
||||
this.getArgumentClass()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import static groowt.util.di.filters.FilterUtil.isAssignableToAnyOf;
|
||||
|
||||
public final class FilterHandlers {
|
||||
|
||||
private FilterHandlers() {}
|
||||
|
||||
public static <A extends Annotation, T> FilterHandler<A, T> of(
|
||||
Class<A> annotationClass,
|
||||
Class<T> argClass,
|
||||
BiPredicate<A, T> predicate
|
||||
) {
|
||||
return new SimpleFilterHandler<>(predicate, annotationClass, argClass);
|
||||
}
|
||||
|
||||
@Filter
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface AllowTypes {
|
||||
Class<?>[] value();
|
||||
}
|
||||
|
||||
public static <T> FilterHandler<AllowTypes, T> getAllowsTypesFilterHandler(Class<T> targetType) {
|
||||
return of(
|
||||
AllowTypes.class,
|
||||
targetType,
|
||||
(annotation, target) -> isAssignableToAnyOf(target.getClass(), annotation.value())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
public final class FilterUtil {
|
||||
|
||||
private FilterUtil() {}
|
||||
|
||||
public static boolean isAssignableToAnyOf(Class<?> subject, Class<?>[] tests) {
|
||||
for (final var test : tests) {
|
||||
if (test.isAssignableFrom(subject)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
public @interface IterableFilter {}
|
@ -1,8 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public interface IterableFilterHandler<A extends Annotation, E> {
|
||||
boolean check(A annotation, Iterable<E> iterable);
|
||||
Class<A> getAnnotationClass();
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import static groowt.util.di.filters.FilterUtil.isAssignableToAnyOf;
|
||||
|
||||
public final class IterableFilterHandlers {
|
||||
|
||||
private IterableFilterHandlers() {}
|
||||
|
||||
public static <A extends Annotation, E> IterableFilterHandler<A, E> of(
|
||||
Class<A> filterType,
|
||||
BiPredicate<A, E> elementPredicate
|
||||
) {
|
||||
return new SimpleIterableFilterHandler<>(filterType, elementPredicate);
|
||||
}
|
||||
|
||||
@IterableFilter
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface IterableElementTypes {
|
||||
Class<?>[] value();
|
||||
}
|
||||
|
||||
public static IterableFilterHandler<IterableElementTypes, Object> getIterableElementTypesFilterHandler() {
|
||||
return of(
|
||||
IterableElementTypes.class,
|
||||
(annotation, element) -> isAssignableToAnyOf(element.getClass(), annotation.value())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
final class SimpleFilterHandler<A extends Annotation, T> implements FilterHandler<A, T> {
|
||||
|
||||
private final BiPredicate<A, T> predicate;
|
||||
private final Class<A> annotationClass;
|
||||
private final Class<T> argClass;
|
||||
|
||||
public SimpleFilterHandler(BiPredicate<A, T> predicate, Class<A> annotationClass, Class<T> argClass) {
|
||||
this.predicate = predicate;
|
||||
this.annotationClass = annotationClass;
|
||||
this.argClass = argClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(A annotation, T arg) {
|
||||
return this.predicate.test(annotation, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<A> getAnnotationClass() {
|
||||
return this.annotationClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getArgumentClass() {
|
||||
return this.argClass;
|
||||
}
|
||||
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package groowt.util.di.filters;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
final class SimpleIterableFilterHandler<A extends Annotation, E> implements IterableFilterHandler<A, E> {
|
||||
|
||||
private final Class<A> annotationClass;
|
||||
private final BiPredicate<A, E> elementPredicate;
|
||||
|
||||
public SimpleIterableFilterHandler(Class<A> annotationClass, BiPredicate<A, E> elementPredicate) {
|
||||
this.annotationClass = annotationClass;
|
||||
this.elementPredicate = elementPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(A annotation, Iterable<E> iterable) {
|
||||
for (final var e : Objects.requireNonNull(iterable)) {
|
||||
if (!this.elementPredicate.test(annotation, e)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<A> getAnnotationClass() {
|
||||
return this.annotationClass;
|
||||
}
|
||||
|
||||
}
|
@ -1,275 +0,0 @@
|
||||
package groowt.util.di;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static groowt.util.di.BindingUtil.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class DefaultRegistryObjectFactoryTests {
|
||||
|
||||
public interface Greeter {
|
||||
String greet();
|
||||
}
|
||||
|
||||
public static final class DefaultGreeter implements Greeter {
|
||||
|
||||
@Override
|
||||
public String greet() {
|
||||
return "Hello, World!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class GivenArgGreeter implements Greeter {
|
||||
|
||||
private final String greeting;
|
||||
|
||||
@Inject
|
||||
public GivenArgGreeter(String greeting) {
|
||||
this.greeting = greeting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String greet() {
|
||||
return this.greeting;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class InjectedArgGreeter implements Greeter {
|
||||
|
||||
private final String greeting;
|
||||
|
||||
@Inject
|
||||
public InjectedArgGreeter(String greeting) {
|
||||
this.greeting = greeting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String greet() {
|
||||
return this.greeting;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class InjectedNamedArgGreeter implements Greeter {
|
||||
|
||||
private final String greeting;
|
||||
|
||||
@Inject
|
||||
public InjectedNamedArgGreeter(@Named("greeting") String greeting) {
|
||||
this.greeting = greeting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String greet() {
|
||||
return this.greeting;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class InjectedNamedSetterGreeter implements Greeter {
|
||||
|
||||
private String greeting;
|
||||
|
||||
@Inject
|
||||
public void setGreeting(@Named("greeting") String greeting) {
|
||||
this.greeting = greeting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String greet() {
|
||||
return this.greeting;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void classSmokeScreen() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(registry -> {
|
||||
registry.bind(Greeter.class, bc -> bc.to(DefaultGreeter.class));
|
||||
});
|
||||
final RegistryObjectFactory container = b.build();
|
||||
final Greeter greeter = container.get(Greeter.class);
|
||||
assertEquals("Hello, World!", greeter.greet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singletonSmokeScreen() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(registry -> {
|
||||
registry.bind(Greeter.class, toSingleton(new DefaultGreeter()));
|
||||
});
|
||||
final RegistryObjectFactory container = b.build();
|
||||
final Greeter greeter = container.get(Greeter.class);
|
||||
assertEquals("Hello, World!", greeter.greet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void providerSmokeScreen() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(registry -> {
|
||||
registry.bind(Greeter.class, toProvider(DefaultGreeter::new));
|
||||
});
|
||||
final RegistryObjectFactory container = b.build();
|
||||
final Greeter greeter = container.get(Greeter.class);
|
||||
assertEquals("Hello, World!", greeter.greet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenArgSmokeScreen() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(registry -> {
|
||||
registry.bind(Greeter.class, bc -> bc.to(GivenArgGreeter.class));
|
||||
});
|
||||
final RegistryObjectFactory container = b.build();
|
||||
final Greeter greeter = container.get(Greeter.class, "Hello, World!");
|
||||
assertEquals("Hello, World!", greeter.greet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void injectedArg() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(registry -> {
|
||||
registry.bind(Greeter.class, bc -> bc.to(InjectedArgGreeter.class));
|
||||
registry.bind(String.class, toSingleton("Hello, World!"));
|
||||
});
|
||||
final RegistryObjectFactory container = b.build();
|
||||
final Greeter greeter = container.get(Greeter.class);
|
||||
assertEquals("Hello, World!", greeter.greet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void injectedNamedArg() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(registry -> {
|
||||
registry.bind(Greeter.class, bc -> bc.to(InjectedNamedArgGreeter.class));
|
||||
registry.bind(named("greeting", String.class), toSingleton("Hello, World!"));
|
||||
});
|
||||
final RegistryObjectFactory container = b.build();
|
||||
final Greeter greeter = container.get(Greeter.class);
|
||||
assertEquals("Hello, World!", greeter.greet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void injectedSetter() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(r -> {
|
||||
r.bind(Greeter.class, toClass(InjectedNamedSetterGreeter.class));
|
||||
r.bind(named("greeting", String.class), toSingleton("Hello, World!"));
|
||||
});
|
||||
final RegistryObjectFactory f = b.build();
|
||||
final Greeter greeter = f.get(Greeter.class);
|
||||
assertEquals("Hello, World!", greeter.greet());
|
||||
}
|
||||
|
||||
public static final class GreeterDependency {
|
||||
|
||||
private GreeterDependencyUser greeter;
|
||||
|
||||
@Inject
|
||||
public void setGreeter(GreeterDependencyUser greeter) {
|
||||
this.greeter = greeter;
|
||||
}
|
||||
|
||||
public String filterGreeting() {
|
||||
return this.greeter.getGreeting().toUpperCase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class GreeterDependencyUser implements Greeter {
|
||||
|
||||
private final GreeterDependency greeterDependency;
|
||||
|
||||
@Inject
|
||||
public GreeterDependencyUser(GreeterDependency greeterDependency) {
|
||||
this.greeterDependency = greeterDependency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String greet() {
|
||||
return this.greeterDependency.filterGreeting();
|
||||
}
|
||||
|
||||
public String getGreeting() {
|
||||
return "hello, world!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void injectedDeferred() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(r -> {
|
||||
r.bind(GreeterDependencyUser.class, toSelf());
|
||||
r.bind(GreeterDependency.class, toSelf());
|
||||
});
|
||||
final var f = b.build();
|
||||
final var g = f.get(GreeterDependencyUser.class);
|
||||
assertEquals("HELLO, WORLD!", g.greet());
|
||||
}
|
||||
|
||||
public static final class NoInjectGreeter implements Greeter {
|
||||
|
||||
private final String greeting;
|
||||
|
||||
public NoInjectGreeter(String greeting) {
|
||||
this.greeting = greeting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String greet() {
|
||||
return this.greeting;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noInjectFoundViaGet() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(r -> {
|
||||
r.bind(NoInjectGreeter.class, toSelf());
|
||||
});
|
||||
final var f = b.build();
|
||||
final var g = f.get(NoInjectGreeter.class, "Given Greeting");
|
||||
assertEquals("Given Greeting", g.greet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noInjectFindViaCreate() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(r -> {
|
||||
r.bind(NoInjectGreeter.class, toSelf());
|
||||
});
|
||||
final var f = b.build();
|
||||
final var g = f.createInstance(NoInjectGreeter.class, "Given Greeting");
|
||||
assertEquals("Given Greeting", g.greet());
|
||||
}
|
||||
|
||||
@Singleton
|
||||
public static final class SingletonGreeter implements Greeter {
|
||||
|
||||
@Override
|
||||
public String greet() {
|
||||
return "Hello, World!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singletonDoesNotOverflow() {
|
||||
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||
b.configureRegistry(r -> {
|
||||
r.bind(SingletonGreeter.class, toSelf());
|
||||
});
|
||||
final var f = b.build();
|
||||
final var g = f.get(SingletonGreeter.class);
|
||||
assertEquals("Hello, World!", g.greet());
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Configuration xmlns="http://logging.apache.org/log4j/2.0/config">
|
||||
<Appenders>
|
||||
<Console name="root">
|
||||
<PatternLayout>
|
||||
<LevelPatternSelector defaultPattern="[%t] %-5level %logger{1.} %msg%n">
|
||||
<PatternMatch key="DEBUG" pattern="[%t] %-5level %logger{1.}.%M() %msg%n" />
|
||||
</LevelPatternSelector>
|
||||
</PatternLayout>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="DEBUG">
|
||||
<AppenderRef ref="root" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
Loading…
Reference in New Issue
Block a user