diff options
Diffstat (limited to 'sangria-contextual/src/main')
4 files changed, 408 insertions, 0 deletions
diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/AnnotatedContextSensitiveBindingBuilder.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/AnnotatedContextSensitiveBindingBuilder.java new file mode 100644 index 0000000..d313bd6 --- /dev/null +++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/AnnotatedContextSensitiveBindingBuilder.java @@ -0,0 +1,33 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.contextual; + +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples {@link ContextSensitiveBinder here}. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public interface AnnotatedContextSensitiveBindingBuilder<T> extends ContextSensitiveBindingBuilder<T> { + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + ContextSensitiveBindingBuilder<T> annotatedWith(Class<? extends Annotation> annotationType); + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + ContextSensitiveBindingBuilder<T> annotatedWith(Annotation annotation); +} diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java new file mode 100644 index 0000000..b3fe00f --- /dev/null +++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java @@ -0,0 +1,276 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.contextual; + +import java.lang.annotation.Annotation; +import javax.annotation.Nullable; +import javax.inject.Inject; + +import com.google.common.base.Objects; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.AbstractMatcher; +import com.google.inject.matcher.Matcher; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.DependencyAndSource; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.ProvisionListener; +import com.google.inject.util.Providers; + +import com.tavianator.sangria.core.DelayedError; + +/** + * A binder for {@link ContextSensitiveProvider}s. + * + * <p> + * For example, to bind a custom logger provider, you can write this inside {@link AbstractModule#configure()}: + * </p> + * + * <pre> + * ContextSensitiveBinder.create(binder()) + * .bind(CustomLogger.class) + * .toContextSensitiveProvider(CustomLoggerProvider.class); + * </pre> + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public class ContextSensitiveBinder { + private static final Class<?>[] SKIPPED_SOURCES = { + ContextSensitiveBinder.class, + BindingBuilder.class, + }; + + private final Binder binder; + + /** + * Create a {@link ContextSensitiveBinder}. + * + * @param binder The {@link Binder} to use. + * @return A {@link ContextSensitiveBinder} instance. + */ + public static ContextSensitiveBinder create(Binder binder) { + return new ContextSensitiveBinder(binder); + } + + private ContextSensitiveBinder(Binder binder) { + this.binder = binder.skipSources(SKIPPED_SOURCES); + } + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + public <T> AnnotatedContextSensitiveBindingBuilder<T> bind(Class<T> type) { + return new BindingBuilder<>(Key.get(type)); + } + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + public <T> AnnotatedContextSensitiveBindingBuilder<T> bind(TypeLiteral<T> type) { + return new BindingBuilder<>(Key.get(type)); + } + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + public <T> ContextSensitiveBindingBuilder<T> bind(Key<T> key) { + return new BindingBuilder<>(key); + } + + /** + * Fluent binding builder implementation. + */ + private class BindingBuilder<T> implements AnnotatedContextSensitiveBindingBuilder<T> { + private final Key<T> key; + private final DelayedError error; + + BindingBuilder(Key<T> key) { + this.key = key; + this.error = DelayedError.create(binder, "Missing call to toContextSensitiveProvider() for %s", key); + } + + @Override + public ContextSensitiveBindingBuilder<T> annotatedWith(Class<? extends Annotation> annotationType) { + error.cancel(); + return new BindingBuilder<>(Key.get(key.getTypeLiteral(), annotationType)); + } + + @Override + public ContextSensitiveBindingBuilder<T> annotatedWith(Annotation annotation) { + error.cancel(); + return new BindingBuilder<>(Key.get(key.getTypeLiteral(), annotation)); + } + + @Override + public void toContextSensitiveProvider(Class<? extends ContextSensitiveProvider<T>> type) { + toContextSensitiveProvider(Key.get(type)); + } + + @Override + public void toContextSensitiveProvider(TypeLiteral<? extends ContextSensitiveProvider<T>> type) { + toContextSensitiveProvider(Key.get(type)); + } + + @Override + public void toContextSensitiveProvider(Key<? extends ContextSensitiveProvider<T>> type) { + error.cancel(); + + binder.bind(key).toProvider(new ProviderAdapter<>(type)); + binder.bindListener(new BindingMatcher(key), new Trigger(key)); + } + + @Override + public void toContextSensitiveProvider(ContextSensitiveProvider<T> provider) { + error.cancel(); + + binder.bind(key).toProvider(new ProviderAdapter<>(provider)); + binder.bindListener(new BindingMatcher(key), new Trigger(key)); + // Match the behaviour of LinkedBindingBuilder#toProvider(Provider) + binder.requestInjection(provider); + } + } + + /** + * Adapter from {@link ContextSensitiveProvider} to {@link Provider}. + */ + private static class ProviderAdapter<T> implements Provider<T> { + private static final ThreadLocal<InjectionPoint> CURRENT_CONTEXT = new ThreadLocal<>(); + + private final Object equalityKey; + private final @Nullable Key<? extends ContextSensitiveProvider<T>> providerKey; + private Provider<? extends ContextSensitiveProvider<T>> provider; + + ProviderAdapter(Key<? extends ContextSensitiveProvider<T>> providerKey) { + this.equalityKey = providerKey; + this.providerKey = providerKey; + } + + ProviderAdapter(ContextSensitiveProvider<T> provider) { + this.equalityKey = provider; + this.providerKey = null; + this.provider = Providers.of(provider); + } + + @Inject + void inject(Injector injector) { + if (provider == null) { + provider = injector.getProvider(providerKey); + } + } + + static void pushContext(InjectionPoint ip) { + CURRENT_CONTEXT.set(ip); + } + + static void popContext() { + CURRENT_CONTEXT.remove(); + } + + @Override + public T get() { + ContextSensitiveProvider<T> delegate = provider.get(); + InjectionPoint ip = CURRENT_CONTEXT.get(); + if (ip != null) { + return delegate.getInContext(ip); + } else { + return delegate.getInUnknownContext(); + } + } + + // Have to implement equals()/hashCode() here to support binding de-duplication + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof ProviderAdapter)) { + return false; + } + + ProviderAdapter<?> other = (ProviderAdapter<?>)obj; + return equalityKey.equals(other.equalityKey); + } + + @Override + public int hashCode() { + return Objects.hashCode(equalityKey); + } + } + + /** + * {@link Matcher} for {@link Binding}s for specific {@link Key}s. + */ + private static class BindingMatcher extends AbstractMatcher<Binding<?>> { + private final Key<?> key; + + BindingMatcher(Key<?> key) { + this.key = key; + } + + @Override + public boolean matches(Binding<?> binding) { + return key.equals(binding.getKey()); + } + } + + /** + * {@link ProvisionListener} that sets up the current {@link InjectionPoint}. + */ + private static class Trigger implements ProvisionListener { + private final Key<?> key; + + Trigger(Key<?> key) { + this.key = key; + } + + @Override + public <T> void onProvision(ProvisionInvocation<T> provision) { + for (DependencyAndSource dependencyAndSource : provision.getDependencyChain()) { + Dependency<?> dependency = dependencyAndSource.getDependency(); + if (dependency != null && key.equals(dependency.getKey())) { + try { + ProviderAdapter.pushContext(dependency.getInjectionPoint()); + provision.provision(); + } finally { + ProviderAdapter.popContext(); + } + + break; + } + } + } + + // Allow listeners to be de-duplicated + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof Trigger)) { + return false; + } + + Trigger other = (Trigger)obj; + return key.equals(other.key); + } + + @Override + public int hashCode() { + return Objects.hashCode(key); + } + } +} diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBindingBuilder.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBindingBuilder.java new file mode 100644 index 0000000..a558e8d --- /dev/null +++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBindingBuilder.java @@ -0,0 +1,44 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.contextual; + +import com.google.inject.Key; +import com.google.inject.TypeLiteral; + +/** + * See the EDSL examples {@link ContextSensitiveBinder here}. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public interface ContextSensitiveBindingBuilder<T> { + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + void toContextSensitiveProvider(Class<? extends ContextSensitiveProvider<T>> type); + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + void toContextSensitiveProvider(TypeLiteral<? extends ContextSensitiveProvider<T>> type); + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + void toContextSensitiveProvider(Key<? extends ContextSensitiveProvider<T>> type); + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + void toContextSensitiveProvider(ContextSensitiveProvider<T> provider); +} diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProvider.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProvider.java new file mode 100644 index 0000000..7d368f1 --- /dev/null +++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProvider.java @@ -0,0 +1,55 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.contextual; + +import com.google.inject.Provider; +import com.google.inject.spi.InjectionPoint; + +/** + * Like a {@link Provider}, but with knowledge of the target {@link InjectionPoint}. + * + * <p> + * This interface, along with {@link ContextSensitiveBinder}, is useful for injecting custom logger types, among other + * things. However, context-sensitive injections can make maintenance and debugging more difficult. + * </p> + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public interface ContextSensitiveProvider<T> { + /** + * Provide an instance of {@code T} for the given context. + * + * @param injectionPoint The {@link InjectionPoint} for this provision. + * @return An instance of {@code T}. + */ + T getInContext(InjectionPoint injectionPoint); + + /** + * Provide an instance of {@code T} for an unknown context. + * <p> + * The {@link InjectionPoint} may not be known in all cases, for example if a {@code Provider<T>} is used instead + * of + * a bare {@code T}. This method will be called in those cases. + * </p> + * <p> + * One reasonable implementation is to return a generically applicable instance, such as an anonymous logger. + * Another valid implementation is to throw an unchecked exception; in that case, {@code Provider<T>} injections + * will fail. + * </p> + * + * @return An instance of {@code T} + * @throws RuntimeException If injection without a context is not supported. + */ + T getInUnknownContext(); +} |