// =================================================================================================
// 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 com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
/**
* Provider that has a default value which can be overridden.
*
* The intended use of this class is:
* <pre>
* Default installer:
* bind(DefaultProvider.makeDefaultKey(Runnable.class, "mykey").toInstance(defaultRunnable);
* DefaultProvider.bindOrElse(Runnable.class, "mykey", binder());
*
* Custom override:
* bind(DefaultProvider.makeCustomKey(Runnable.class, "mykey")).toInstance(myCustomRunnable);
*
* Injection:
* {@literal Inject} Named("myKey") Runnable runnable;
*
* </pre>
*
* @param <T> the type of object this provides
*
* @author William Farner
* @author John Sirois
*/
public class DefaultProvider<T> implements Provider<T> {
private static final String DEFAULT_BINDING_KEY_SUFFIX = "_default";
private static final String CUSTOM_BINDING_KEY_SUFFIX = "_custom";
private final Key<T> defaultProviderKey;
private final Key<T> customProviderKey;
private Injector injector;
public DefaultProvider(Key<T> defaultProviderKey, Key<T> customProviderKey) {
this.defaultProviderKey = Preconditions.checkNotNull(defaultProviderKey);
this.customProviderKey = Preconditions.checkNotNull(customProviderKey);
Preconditions.checkArgument(!defaultProviderKey.equals(customProviderKey));
}
@Inject
public void setInjector(Injector injector) {
this.injector = injector;
}
@Override
public T get() {
Preconditions.checkNotNull(injector);
return injector.getBindings().containsKey(customProviderKey)
? injector.getInstance(customProviderKey)
: injector.getInstance(defaultProviderKey);
}
/**
* Creates a DefaultProvider and installs a new module to {@code binder}, which will serve as
* an indirection layer for swapping the default binding with a custom one.
*
* @param customBinding The custom binding key.
* @param defaultBinding The default binding key.
* @param exposedBinding The exposed binding key.
* @param binder The binder to install bindings to.
* @param <T> The type of binding to make.
*/
public static <T> void bindOrElse(final Key<T> customBinding, final Key<T> defaultBinding,
final Key<T> exposedBinding, Binder binder) {
Preconditions.checkNotNull(customBinding);
Preconditions.checkNotNull(defaultBinding);
Preconditions.checkNotNull(exposedBinding);
Preconditions.checkArgument(!customBinding.equals(defaultBinding)
&& !customBinding.equals(exposedBinding));
binder.install(new AbstractModule() {
@Override protected void configure() {
Provider<T> defaultProvider = new DefaultProvider<T>(defaultBinding, customBinding);
requestInjection(defaultProvider);
bind(exposedBinding).toProvider(defaultProvider);
}
});
}
/**
* Convenience function for creating and installing a DefaultProvider. This will use internal
* suffixes to create names for the custom and default bindings. When bound this way, callers
* should use one of the functions such as {@link #makeDefaultBindingKey(String)} to set default
* and custom bindings.
*
* @param type The type of object to bind.
* @param exposedKey The exposed key.
* @param binder The binder to install to.
* @param <T> The type of binding to make.
*/
public static <T> void bindOrElse(TypeLiteral<T> type, String exposedKey, Binder binder) {
bindOrElse(Key.get(type, Names.named(makeCustomBindingKey(exposedKey))),
Key.get(type, Names.named(makeDefaultBindingKey(exposedKey))),
Key.get(type, Names.named(exposedKey)),
binder);
}
/**
* Convenience method for calls to {@link #bindOrElse(TypeLiteral, String, Binder)}, that are not
* binding a parameterized type.
*
* @param type The class of the object to bind.
* @param exposedKey The exposed key.
* @param binder The binder to install to.
* @param <T> The type of binding to make.
*/
public static <T> void bindOrElse(Class<T> type, String exposedKey, Binder binder) {
bindOrElse(TypeLiteral.get(type), exposedKey, binder);
}
public static String makeDefaultBindingKey(String rootKey) {
return rootKey + DEFAULT_BINDING_KEY_SUFFIX;
}
public static Named makeDefaultBindingName(String rootKey) {
return Names.named(makeDefaultBindingKey(rootKey));
}
public static <T> Key<T> makeDefaultKey(TypeLiteral<T> type, String rootKey) {
return Key.get(type, makeDefaultBindingName(rootKey));
}
public static <T> Key<T> makeDefaultKey(Class<T> type, String rootKey) {
return makeDefaultKey(TypeLiteral.get(type), rootKey);
}
public static String makeCustomBindingKey(String rootKey) {
return rootKey + CUSTOM_BINDING_KEY_SUFFIX;
}
public static Named makeCustomBindingName(String rootKey) {
return Names.named(makeCustomBindingKey(rootKey));
}
public static <T> Key<T> makeCustomKey(Class<T> type, String rootKey) {
return Key.get(type, makeCustomBindingName(rootKey));
}
public static <T> Key<T> makeCustomKey(TypeLiteral<T> type, String rootKey) {
return Key.get(type, makeCustomBindingName(rootKey));
}
}