From 1ce3feb50ef042c52233f0703d9fca708b0d5e30 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 1 Apr 2014 18:14:54 -0400 Subject: Add sangria-contextual module. --- .../AnnotatedContextSensitiveBindingBuilder.java | 33 +++ .../sangria/contextual/ContextSensitiveBinder.java | 276 +++++++++++++++++++++ .../contextual/ContextSensitiveBindingBuilder.java | 44 ++++ .../contextual/ContextSensitiveProvider.java | 55 ++++ 4 files changed, 408 insertions(+) create mode 100644 sangria-contextual/src/main/java/com/tavianator/sangria/contextual/AnnotatedContextSensitiveBindingBuilder.java create mode 100644 sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java create mode 100644 sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBindingBuilder.java create mode 100644 sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProvider.java (limited to 'sangria-contextual/src/main/java/com/tavianator/sangria/contextual') 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 * + * * + * 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 extends ContextSensitiveBindingBuilder { + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + ContextSensitiveBindingBuilder annotatedWith(Class annotationType); + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + ContextSensitiveBindingBuilder 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 * + * * + * 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. + * + *

+ * For example, to bind a custom logger provider, you can write this inside {@link AbstractModule#configure()}: + *

+ * + *
+ * ContextSensitiveBinder.create(binder())
+ *         .bind(CustomLogger.class)
+ *         .toContextSensitiveProvider(CustomLoggerProvider.class);
+ * 
+ * + * @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 AnnotatedContextSensitiveBindingBuilder bind(Class type) { + return new BindingBuilder<>(Key.get(type)); + } + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + public AnnotatedContextSensitiveBindingBuilder bind(TypeLiteral type) { + return new BindingBuilder<>(Key.get(type)); + } + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + public ContextSensitiveBindingBuilder bind(Key key) { + return new BindingBuilder<>(key); + } + + /** + * Fluent binding builder implementation. + */ + private class BindingBuilder implements AnnotatedContextSensitiveBindingBuilder { + private final Key key; + private final DelayedError error; + + BindingBuilder(Key key) { + this.key = key; + this.error = DelayedError.create(binder, "Missing call to toContextSensitiveProvider() for %s", key); + } + + @Override + public ContextSensitiveBindingBuilder annotatedWith(Class annotationType) { + error.cancel(); + return new BindingBuilder<>(Key.get(key.getTypeLiteral(), annotationType)); + } + + @Override + public ContextSensitiveBindingBuilder annotatedWith(Annotation annotation) { + error.cancel(); + return new BindingBuilder<>(Key.get(key.getTypeLiteral(), annotation)); + } + + @Override + public void toContextSensitiveProvider(Class> type) { + toContextSensitiveProvider(Key.get(type)); + } + + @Override + public void toContextSensitiveProvider(TypeLiteral> type) { + toContextSensitiveProvider(Key.get(type)); + } + + @Override + public void toContextSensitiveProvider(Key> type) { + error.cancel(); + + binder.bind(key).toProvider(new ProviderAdapter<>(type)); + binder.bindListener(new BindingMatcher(key), new Trigger(key)); + } + + @Override + public void toContextSensitiveProvider(ContextSensitiveProvider 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 implements Provider { + private static final ThreadLocal CURRENT_CONTEXT = new ThreadLocal<>(); + + private final Object equalityKey; + private final @Nullable Key> providerKey; + private Provider> provider; + + ProviderAdapter(Key> providerKey) { + this.equalityKey = providerKey; + this.providerKey = providerKey; + } + + ProviderAdapter(ContextSensitiveProvider 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 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> { + 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 void onProvision(ProvisionInvocation 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 * + * * + * 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 { + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + void toContextSensitiveProvider(Class> type); + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + void toContextSensitiveProvider(TypeLiteral> type); + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + void toContextSensitiveProvider(Key> type); + + /** + * See the EDSL examples {@link ContextSensitiveBinder here}. + */ + void toContextSensitiveProvider(ContextSensitiveProvider 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 * + * * + * 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}. + * + *

+ * 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. + *

+ * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public interface ContextSensitiveProvider { + /** + * 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. + *

+ * The {@link InjectionPoint} may not be known in all cases, for example if a {@code Provider} is used instead + * of + * a bare {@code T}. This method will be called in those cases. + *

+ *

+ * 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} injections + * will fail. + *

+ * + * @return An instance of {@code T} + * @throws RuntimeException If injection without a context is not supported. + */ + T getInUnknownContext(); +} -- cgit v1.2.3