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
	 Jesse Brault
						Jesse Brault