Make injection order predictable (#402)

This commit is contained in:
Pablo Herrera 2022-11-06 19:30:12 +01:00 committed by Jason
parent 2f46b0c71d
commit c36cf6b937

View file

@ -26,16 +26,16 @@ package cloud.commandframework.annotations.injection;
import cloud.commandframework.annotations.AnnotationAccessor; import cloud.commandframework.annotations.AnnotationAccessor;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.services.ServicePipeline; import cloud.commandframework.services.ServicePipeline;
import cloud.commandframework.types.tuples.Pair;
import cloud.commandframework.types.tuples.Triplet; import cloud.commandframework.types.tuples.Triplet;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apiguardian.api.API; import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -43,6 +43,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Registry containing mappings between {@link Class classes} and {@link ParameterInjector injectors} * Registry containing mappings between {@link Class classes} and {@link ParameterInjector injectors}
* *
* The order injectors are tested is the same order they were registered in.
*
* @param <C> Command sender type * @param <C> Command sender type
* @since 1.2.0 * @since 1.2.0
*/ */
@ -50,8 +52,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
@API(status = API.Status.STABLE, since = "1.2.0") @API(status = API.Status.STABLE, since = "1.2.0")
public final class ParameterInjectorRegistry<C> implements InjectionService<C> { public final class ParameterInjectorRegistry<C> implements InjectionService<C> {
private volatile int injectorCount = 0; private final List<Pair<Predicate<Class<?>>, ParameterInjector<C, ?>>> injectors = new ArrayList<>();
private final Map<Class<?>, List<ParameterInjector<C, ?>>> injectors = new HashMap<>();
private final ServicePipeline servicePipeline = ServicePipeline.builder().build(); private final ServicePipeline servicePipeline = ServicePipeline.builder().build();
/** /**
@ -63,7 +64,7 @@ public final class ParameterInjectorRegistry<C> implements InjectionService<C> {
} }
/** /**
* Register an injector for a particular type * Register an injector for a particular type or any of it's assignable supertypes.
* *
* @param clazz Type that the injector should inject for. This type will matched using * @param clazz Type that the injector should inject for. This type will matched using
* {@link Class#isAssignableFrom(Class)} * {@link Class#isAssignableFrom(Class)}
@ -74,8 +75,23 @@ public final class ParameterInjectorRegistry<C> implements InjectionService<C> {
final @NonNull Class<T> clazz, final @NonNull Class<T> clazz,
final @NonNull ParameterInjector<C, T> injector final @NonNull ParameterInjector<C, T> injector
) { ) {
this.injectors.computeIfAbsent(clazz, missingClass -> new LinkedList<>()).add(injector); this.registerInjector(cl -> clazz.isAssignableFrom(cl), injector);
this.injectorCount++; }
/**
* Register an injector for a particular type predicate.
*
* @param predicate A predicate that matches if the injector should be used for a type
* @param injector The injector that should inject the value into the command method
* @param <T> Injected type
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public synchronized <T> void registerInjector(
final @NonNull Predicate<Class<?>> predicate,
final @NonNull ParameterInjector<C, T> injector
) {
this.injectors.add(Pair.of(predicate, injector));
} }
/** /**
@ -92,13 +108,10 @@ public final class ParameterInjectorRegistry<C> implements InjectionService<C> {
public synchronized <T> @NonNull Collection<@NonNull ParameterInjector<C, ?>> injectors( public synchronized <T> @NonNull Collection<@NonNull ParameterInjector<C, ?>> injectors(
final @NonNull Class<T> clazz final @NonNull Class<T> clazz
) { ) {
final List<@NonNull ParameterInjector<C, ?>> injectors = new ArrayList<>(this.injectorCount); return Collections.unmodifiableCollection(this.injectors.stream()
for (final Map.Entry<Class<?>, List<ParameterInjector<C, ?>>> entry : this.injectors.entrySet()) { .filter(pair -> pair.getFirst().test(clazz))
if (clazz.isAssignableFrom(entry.getKey())) { .map(Pair::getSecond)
injectors.addAll(entry.getValue()); .collect(Collectors.toList()));
}
}
return Collections.unmodifiableCollection(injectors);
} }
@Override @Override