package com.netflix.governator.providers;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.UnaryOperator;
import javax.inject.Provider;
class AdvisedProvider<T> implements ProviderWithExtensionVisitor<T>, HasDependencies {
private final Set<Dependency<?>> dependencies = new HashSet<>();
private final String name;
private final Provider<T> delegate;
private final List<ProvisionAdviceHolder<UnaryOperator<T>>> adviceBindings = new ArrayList<>();
private final TypeLiteral<UnaryOperator<T>> advisesType;
public AdvisedProvider(TypeLiteral<T> typeLiteral, String name, Annotation annotation, Provider<T> delegate) {
this.name = name;
this.delegate = delegate;
this.advisesType = (TypeLiteral<UnaryOperator<T>>) TypeLiteral.get(Types.newParameterizedType(UnaryOperator.class, typeLiteral.getType()));
}
@Override
public T get() {
return adviceBindings
.stream()
.map(advice -> advice.binding.getProvider().get())
.reduce(delegate.get(),
(advised, advice) -> advice.apply(advised),
(current, next) -> next);
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
@Override
public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor,
ProviderInstanceBinding<? extends B> binding) {
return visitor.visit(binding);
}
@SuppressWarnings("unchecked")
@Toolable
@javax.inject.Inject
protected void initialize(Injector injector) {
for (Binding<?> binding : injector.findBindingsByType(advisesType)) {
Key<?> bindingKey = binding.getKey();
if (bindingKey.hasAttributes() && AdviceElement.class.isAssignableFrom(bindingKey.getAnnotationType())) {
AdviceElementImpl adviceElement = (AdviceElementImpl) bindingKey.getAnnotation();
if (name.equals(adviceElement.name())) {
if (adviceElement.type() == AdviceElement.Type.ADVICE) {
adviceBindings.add(new ProvisionAdviceHolder<UnaryOperator<T>>((Binding<UnaryOperator<T>>) binding, adviceElement.getOrder()));
}
dependencies.add(Dependency.get(bindingKey));
}
}
}
adviceBindings.sort(ByOrder);
}
static Comparator<ProvisionAdviceHolder<?>> ByOrder = new Comparator<ProvisionAdviceHolder<?>>() {
@Override
public int compare(ProvisionAdviceHolder<?> o1, ProvisionAdviceHolder<?> o2) {
int rv = Integer.compare(o1.order, o2.order);
if (rv == 0) {
return Integer.compare(System.identityHashCode(o1.hashCode()), System.identityHashCode(o2.hashCode()));
}
return rv;
}
};
static class ProvisionAdviceHolder<T> {
Binding<T> binding;
int order;
public ProvisionAdviceHolder(Binding<T> binding, int order) {
this.order = order;
this.binding = binding;
}
}
}