package tc.oc.commons.core.inject; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Set; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Qualifier; import com.google.inject.Binder; import com.google.inject.BindingAnnotation; import com.google.inject.Key; import com.google.inject.TypeLiteral; import com.google.inject.binder.LinkedBindingBuilder; import com.google.inject.multibindings.Multibinder; import tc.oc.commons.core.reflect.ResolvableType; import tc.oc.commons.core.reflect.TypeArgument; import tc.oc.commons.core.reflect.Types; import tc.oc.commons.core.util.Streams; /** * Creates a transformable binding, which is associated with a set of {@link Transformer}s * that are applied every time the binding is provisioned. Each {@link Transformer} in the * chain can alter or replace the provisioned object. * * {@link Transformer}s are applied in the reverse order that they are bound. * The last one to be bound is the first to be called and the last to return. */ public class TransformableBinder<T> { private final Binder binder; private final Key<T> key; private final Key<T> untransformedKey; private final Key<Set<Transformer<T>>> transformerSetKey; private final Multibinder<Transformer<T>> transformerBinder; public TransformableBinder(Binder binder, @Nullable Class<T> type) { this(binder, type == null ? null : Key.get(type)); } public TransformableBinder(Binder binder, @Nullable TypeLiteral<T> type) { this(binder, type == null ? null : Key.get(type)); } public TransformableBinder(Binder binder, @Nullable Key<T> keyOrNull) { this.binder = binder.skipSources(TransformableBinder.class, TransformingProvider.class); this.key = keyOrNull != null ? keyOrNull : Key.get(new ResolvableType<T>(){}.in(getClass())); this.untransformedKey = Keys.get(key, new UntransformedImpl()); final TypeLiteral<Transformer<T>> transformerType = new ResolvableType<Transformer<T>>(){}.with(new TypeArgument<T>(key.getTypeLiteral()){}); final Annotation annotation = key.getAnnotation(); this.transformerSetKey = Keys.get(Types.setOf(transformerType), annotation); this.transformerBinder = Multibinder.newSetBinder(this.binder, Keys.get(transformerType, annotation)); this.binder.install(new TransformingProvider()); } public LinkedBindingBuilder<T> bindOriginal() { return binder.bind(untransformedKey); } public LinkedBindingBuilder<Transformer<T>> bindTransformer() { return transformerBinder.addBinding(); } @Qualifier @BindingAnnotation @Retention(RetentionPolicy.RUNTIME) private @interface Untransformed { boolean isWaterWet() default true; // Only here to trick Guice into thinking the annotation has attributes } private class UntransformedImpl implements Untransformed { Key<T> key() { return key; } @Override public int hashCode() { return key.hashCode(); } @Override public boolean equals(Object that) { return that instanceof TransformableBinder.UntransformedImpl && this.key().equals(((UntransformedImpl) that).key()); } @Override public Class<? extends Annotation> annotationType() { return Untransformed.class; } @Override public String toString() { return "@" + annotationType().getSimpleName() + "{" + key() + "}"; } @Override public boolean isWaterWet() { return true; } } private class TransformingProvider extends KeyedManifest.Impl implements Provider<T> { Provider<T> original, transformed; Provider<Set<Transformer<T>>> transformers; protected TransformingProvider() { super(key); } @Override public void configure() { original = getProvider(untransformedKey); transformers = getProvider(transformerSetKey); bind(key).toProvider(this); } @Inject void buildTransformedProvider() { transformed = Streams.reduce( transformers.get().stream(), original, (provider, transformer) -> () -> transformer.transform(provider) ); } @Override public T get() { return transformed.get(); } } }