// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.inject; import java.lang.annotation.Annotation; import javax.inject.Qualifier; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.BindingAnnotation; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.PrivateModule; import com.google.inject.TypeLiteral; import com.google.inject.binder.LinkedBindingBuilder; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; /** * A utility that helps with guice bindings. * * @author John Sirois */ public final class Bindings { private Bindings() { // utility } /** * Equivalent to calling {@code requireBinding(binder, Key.get(required, Names.named(namedKey)))}. */ public static void requireNamedBinding(Binder binder, Class<?> required, String namedKey) { requireBinding(binder, Key.get(Preconditions.checkNotNull(required), Names.named(Preconditions.checkNotNull(namedKey)))); } /** * Equivalent to calling {@code requireBinding(binder, Key.get(required))}. */ public static void requireBinding(Binder binder, Class<?> required) { requireBinding(binder, Key.get(Preconditions.checkNotNull(required))); } /** * Registers {@code required} as non-optional dependency in the {@link com.google.inject.Injector} * associated with {@code binder}. * * @param binder A binder to require bindings against. * @param required The dependency that is required. */ public static void requireBinding(Binder binder, final Key<?> required) { Preconditions.checkNotNull(binder); Preconditions.checkNotNull(required); binder.install(new AbstractModule() { @Override protected void configure() { requireBinding(required); } }); } /** * A convenient version of {@link #exposing(Iterable, com.google.inject.Module)} when you just * want to expose a single binding. */ public static Module exposing(Key<?> key, Module module) { return exposing(ImmutableList.of(key), module); } /** * Creates a module that hides all the given module's bindings and only exposes bindings for * the given key. * * @param keys The keys of the bindings to expose. * @param module The module to hide most bindings for. * @return A limited visibility module. */ public static Module exposing(final Iterable<? extends Key<?>> keys, final Module module) { Preconditions.checkNotNull(keys); Preconditions.checkNotNull(module); return new PrivateModule() { @Override protected void configure() { install(module); for (Key<?> key : keys) { expose(key); } } }; } /** * A guice binding helper that allows for any combination of Class, TypeLiteral or Key binding * without forcing guiced implementation to provide all the overloaded binding methods they would * otherwise have to. * * @param <T> The type this helper can be used to bind implementations for. */ public interface BindHelper<T> { /** * Associates this BindHelper with an Injector instance. * * @param binder The binder for the injector implementations will be bound in. * @return A binding builder that can be used to bind an implementation with. */ LinkedBindingBuilder<T> with(Binder binder); } /** * Creates a BindHelper for the given binding key that can be used to bind a single instance. * * @param key The binding key the returned BindHelper can be use to bind implementations for. * @param <T> The type the returned BindHelper can be used to bind implementations for. * @return A BindHelper that can be used to bind an implementation with. */ public static <T> BindHelper<T> binderFor(final Key<T> key) { return new BindHelper<T>() { public LinkedBindingBuilder<T> with(Binder binder) { return binder.bind(key); } }; } /** * Creates a BindHelper for the given type that can be used to add a binding of to a set. * * @param type The type the returned BindHelper can be use to bind implementations for. * @param <T> The type the returned BindHelper can be used to bind implementations for. * @return A BindHelper that can be used to bind an implementation with. */ public static <T> BindHelper<T> multiBinderFor(final Class<T> type) { return new BindHelper<T>() { public LinkedBindingBuilder<T> with(Binder binder) { return Multibinder.newSetBinder(binder, type).addBinding(); } }; } /** * Checks that the given annotation instance is a {@link BindingAnnotation @BindingAnnotation}. * * @param annotation The annotation instance to check. * @param <T> The type of the binding annotation. * @return The checked binding annotation. * @throws NullPointerException If the given {@code annotation} is null. * @throws IllegalArgumentException If the given {@code annotation} is not a * {@literal @BindingAnnotation}. */ public static <T extends Annotation> T checkBindingAnnotation(T annotation) { Preconditions.checkNotNull(annotation); checkBindingAnnotation(annotation.annotationType()); return annotation; } /** * Checks that the given annotation type is a {@link BindingAnnotation @BindingAnnotation}. * * @param annotationType The annotation type to check. * @param <T> The type of the binding annotation. * @return The checked binding annotation type. * @throws NullPointerException If the given {@code annotationType} is null. * @throws IllegalArgumentException If the given {@code annotationType} is not a * {@literal @BindingAnnotation}. */ public static <T extends Annotation> Class<T> checkBindingAnnotation(Class<T> annotationType) { Preconditions.checkNotNull(annotationType); boolean bindingAnnotation = annotationType.isAnnotationPresent(BindingAnnotation.class); boolean qualifier = annotationType.isAnnotationPresent(Qualifier.class); Preconditions.checkArgument(bindingAnnotation || qualifier, "%s is not a @BindingAnnotation or @Qualifier", annotationType); return annotationType; } /** * A factory for binding {@link Key keys}. */ public interface KeyFactory { /** * Creates plain un-annotated keys. */ KeyFactory PLAIN = new KeyFactory() { @Override public <T> Key<T> create(Class<T> type) { return Key.get(type); } @Override public <T> Key<T> create(TypeLiteral<T> type) { return Key.get(type); } }; /** * Creates a key for the given type. * * @param type The type to create a key for. * @param <T> The keyed type. * @return A key. */ <T> Key<T> create(Class<T> type); /** * Creates a key for the given type. * * @param type The type to create a key for. * @param <T> The keyed type. * @return A key. */ <T> Key<T> create(TypeLiteral<T> type); } /** * Creates a key factory that produces keys for a given annotation instance. * * @param annotation The annotation instance to apply to all keys. * @return A key factory that creates annotated keys. */ public static KeyFactory annotatedKeyFactory(final Annotation annotation) { checkBindingAnnotation(annotation); return new KeyFactory() { @Override public <T> Key<T> create(Class<T> type) { return Key.get(type, annotation); } @Override public <T> Key<T> create(TypeLiteral<T> type) { return Key.get(type, annotation); } }; } /** * Creates a key factory that produces keys for a given annotation type. * * @param annotationType The annotation type to apply to all keys. * @return A key factory that creates annotated keys. */ public static KeyFactory annotatedKeyFactory(final Class<? extends Annotation> annotationType) { checkBindingAnnotation(annotationType); return new KeyFactory() { @Override public <T> Key<T> create(Class<T> type) { return Key.get(type, annotationType); } @Override public <T> Key<T> create(TypeLiteral<T> type) { return Key.get(type, annotationType); } }; } /** * A utility that helps rebind keys. */ public static final class Rebinder { private final Binder binder; private final KeyFactory bindToFactory; /** * Creates a Rebinder that links bindings to keys from the given {@code bindToFactory}. * * @param binder A binder to rebind keys in. * @param bindToFactory A factory for the rebinding key. */ public Rebinder(Binder binder, KeyFactory bindToFactory) { this.binder = Preconditions.checkNotNull(binder); this.bindToFactory = Preconditions.checkNotNull(bindToFactory); } /** * Rebinds the given key to another, linking bindings. * * @param fromKey The source key to rebind. * @return The key that {@code key} was rebound to. */ public <T> Key<T> rebind(Key<T> fromKey) { Key<T> toKey = bindToFactory.create(fromKey.getTypeLiteral()); binder.bind(toKey).to(fromKey); requireBinding(binder, fromKey); return toKey; } } /** * Creates a Rebinder that rebinds keys to the given annotation instance. * * @param binder A binder to rebind keys in. * @param annotation The annotation instance to rebind keys to. * @return A Rebinder targeting the given {@code annotationType}. */ public static Rebinder rebinder(Binder binder, Annotation annotation) { return new Rebinder(binder, annotatedKeyFactory(annotation)); } /** * Creates a Rebinder that rebinds keys to the given annotation type. * * @param binder A binder to rebind keys in. * @param annotationType The annotation type to rebind keys to. * @return A Rebinder targeting the given {@code annotationType}. */ public static Rebinder rebinder(Binder binder, Class<? extends Annotation> annotationType) { return new Rebinder(binder, annotatedKeyFactory(annotationType)); } }