/**************************************************************************** * Sangria * * Copyright (C) 2014 Tavian Barnes * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * ****************************************************************************/ package com.tavianator.sangria.contextual; import java.lang.annotation.Annotation; import java.util.*; import javax.inject.Inject; import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.Binding; import com.google.inject.ConfigurationException; 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.BindingTargetVisitor; import com.google.inject.spi.Dependency; import com.google.inject.spi.DependencyAndSource; import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.spi.ProvisionListener; import com.tavianator.sangria.core.DelayedError; import com.tavianator.sangria.core.UniqueAnnotations; /** * 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 bindingKey; private final DelayedError error; BindingBuilder(Key bindingKey) { this.bindingKey = bindingKey; this.error = DelayedError.create(binder, "Missing call to toContextSensitiveProvider() for %s", bindingKey); } @Override public ContextSensitiveBindingBuilder annotatedWith(Class annotationType) { error.cancel(); return new BindingBuilder<>(Key.get(bindingKey.getTypeLiteral(), annotationType)); } @Override public ContextSensitiveBindingBuilder annotatedWith(Annotation annotation) { error.cancel(); return new BindingBuilder<>(Key.get(bindingKey.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> key) { error.cancel(); binder.bind(bindingKey).toProvider(new ProviderKeyAdapter<>(key, makeLinkedKey(key))); binder.bindListener(new BindingMatcher(bindingKey), new Trigger(bindingKey)); } private Key makeLinkedKey(Key key) { Key linkedKey = Key.get(key.getTypeLiteral(), UniqueAnnotations.create()); binder.bind(linkedKey) .to(key); return linkedKey; } @Override public void toContextSensitiveProvider(ContextSensitiveProvider provider) { error.cancel(); binder.bind(bindingKey).toProvider(new ProviderInstanceAdapter<>(provider)); binder.bindListener(new BindingMatcher(bindingKey), new Trigger(bindingKey)); // Match the behaviour of LinkedBindingBuilder#toProvider(Provider) binder.requestInjection(provider); } } /** * Adapter from {@link ContextSensitiveProvider} to {@link Provider}. */ private static abstract class ProviderAdapter implements ProviderWithExtensionVisitor { private static final ThreadLocal CURRENT_CONTEXT = new ThreadLocal<>(); static void pushContext(InjectionPoint ip) { CURRENT_CONTEXT.set(ip); } static void popContext() { CURRENT_CONTEXT.remove(); } @Override public T get() { InjectionPoint ip = CURRENT_CONTEXT.get(); if (ip != null) { return delegate().getInContext(ip); } else { return delegate().getInUnknownContext(); } } abstract ContextSensitiveProvider delegate(); // Have to implement equals()/hashCode() to support binding de-duplication @Override public abstract boolean equals(Object obj); @Override public abstract int hashCode(); } private static class ProviderKeyAdapter extends ProviderAdapter implements ContextSensitiveProviderKeyBinding { private final Key> providerKey; private final Key> linkedKey; private Provider> provider; ProviderKeyAdapter( Key> providerKey, Key> linkedKey) { this.providerKey = providerKey; this.linkedKey = linkedKey; } @Inject void inject(Injector injector) { provider = injector.getProvider(linkedKey); } @Override ContextSensitiveProvider delegate() { return provider.get(); } @SuppressWarnings("unchecked") // The real type of B must be T @Override public V acceptExtensionVisitor(BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof ContextSensitiveBindingVisitor) { return ((ContextSensitiveBindingVisitor)visitor).visit(this); } else { return visitor.visit(binding); } } @Override public Key> getContextSensitiveProviderKey() { return providerKey; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (!(obj instanceof ProviderKeyAdapter)) { return false; } ProviderKeyAdapter other = (ProviderKeyAdapter)obj; return providerKey.equals(other.providerKey); } @Override public int hashCode() { return providerKey.hashCode(); } } private static class ProviderInstanceAdapter extends ProviderAdapter implements ContextSensitiveProviderInstanceBinding { private final ContextSensitiveProvider instance; private Set injectionPoints; ProviderInstanceAdapter(ContextSensitiveProvider instance) { this.instance = instance; Set injectionPoints; try { injectionPoints = InjectionPoint.forInstanceMethodsAndFields(instance.getClass()); } catch (ConfigurationException e) { // We can ignore the error, the earlier requestInjection(instance) call will have reported it injectionPoints = e.getPartialValue(); } this.injectionPoints = injectionPoints; } @Override ContextSensitiveProvider delegate() { return instance; } @SuppressWarnings("unchecked") // The real type of B must be T @Override public V acceptExtensionVisitor(BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof ContextSensitiveBindingVisitor) { return ((ContextSensitiveBindingVisitor)visitor).visit(this); } else { return visitor.visit(binding); } } @Override public ContextSensitiveProvider getContextSensitiveProviderInstance() { return instance; } @Override public Set getInjectionPoints() { return injectionPoints; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (!(obj instanceof ProviderInstanceAdapter)) { return false; } ProviderInstanceAdapter other = (ProviderInstanceAdapter)obj; return instance.equals(other.instance); } @Override public int hashCode() { return instance.hashCode(); } } /** * {@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 key.hashCode(); } } }