All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.multibindings.OptionalBinder Maven / Gradle / Ivy

There is a newer version: 4.2.3
Show newest version
/**
 * Copyright (C) 2014 Google Inc.
 *
 * 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.google.inject.multibindings;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.multibindings.Multibinder.checkConfiguration;
import static com.google.inject.util.Types.newParameterizedType;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderLookup;
import com.google.inject.spi.ProviderWithDependencies;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;

import javax.inject.Qualifier;


/**
 * An API to bind optional values, optionally with a default value.
 * OptionalBinder fulfills two roles: 
    *
  1. It allows a framework to define an injection point that may or * may not be bound by users. *
  2. It allows a framework to supply a default value that can be changed * by users. *
* *

When an OptionalBinder is added, it will always supply the bindings: * {@code Optional} and {@code Optional>}. If * {@link #setBinding} or {@link #setDefault} are called, it will also * bind {@code T}. * *

{@code setDefault} is intended for use by frameworks that need a default * value. User code can call {@code setBinding} to override the default. * Warning: Even if setBinding is called, the default binding * will still exist in the object graph. If it is a singleton, it will be * instantiated in {@code Stage.PRODUCTION}. * *

If setDefault or setBinding are linked to Providers, the Provider may return * {@code null}. If it does, the Optional bindings will be absent. Binding * setBinding to a Provider that returns null will not cause OptionalBinder * to fall back to the setDefault binding. * *

If neither setDefault nor setBinding are called, it will try to link to a * user-supplied binding of the same type. If no binding exists, the optionals * will be absent. Otherwise, if a user-supplied binding of that type exists, * or if setBinding or setDefault are called, the optionals will return present * if they are bound to a non-null value. * *

Values are resolved at injection time. If a value is bound to a * provider, that provider's get method will be called each time the optional * is injected (unless the binding is also scoped, or an optional of provider is * injected). * *

Annotations are used to create different optionals of the same key/value * type. Each distinct annotation gets its own independent binding. * *


 * public class FrameworkModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Renamer.class);
 *   }
 * }
* *

With this module, an {@link Optional}{@code } can now be * injected. With no other bindings, the optional will be absent. * Users can specify bindings in one of two ways: * *

Option 1: *


 * public class UserRenamerModule extends AbstractModule {
 *   protected void configure() {
 *     bind(Renamer.class).to(ReplacingRenamer.class);
 *   }
 * }
* *

or Option 2: *


 * public class UserRenamerModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Renamer.class)
 *         .setBinding().to(ReplacingRenamer.class);
 *   }
 * }
* With both options, the {@code Optional} will be present and supply the * ReplacingRenamer. * *

Default values can be supplied using: *


 * public class FrameworkModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
 *         .setDefault().toInstance(DEFAULT_LOOKUP_URL);
 *   }
 * }
* With the above module, code can inject an {@code @LookupUrl String} and it * will supply the DEFAULT_LOOKUP_URL. A user can change this value by binding *

 * public class UserLookupModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
 *         .setBinding().toInstance(CUSTOM_LOOKUP_URL);
 *   }
 * }
* ... which will override the default value. * *

If one module uses setDefault the only way to override the default is to use setBinding. * It is an error for a user to specify the binding without using OptionalBinder if * setDefault or setBinding are called. For example, *


 * public class FrameworkModule extends AbstractModule {
 *   protected void configure() {
 *     OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
 *         .setDefault().toInstance(DEFAULT_LOOKUP_URL);
 *   }
 * }
 * public class UserLookupModule extends AbstractModule {
 *   protected void configure() {
 *     bind(Key.get(String.class, LookupUrl.class)).toInstance(CUSTOM_LOOKUP_URL);
 *   } 
 * }
* ... would generate an error, because both the framework and the user are trying to bind * {@code @LookupUrl String}. * * @author [email protected] (Sam Berlin) * @since 4.0 */ public abstract class OptionalBinder { /* Reflectively capture java 8's Optional types so we can bind them if we're running in java8. */ private static final Class JAVA_OPTIONAL_CLASS; private static final Method JAVA_EMPTY_METHOD; private static final Method JAVA_OF_NULLABLE_METHOD; static { Class optional = null; Method empty = null; Method ofNullable = null; boolean useJavaOptional = false; try { optional = Class.forName("java.util.Optional"); empty = optional.getDeclaredMethod("empty"); ofNullable = optional.getDeclaredMethod("ofNullable", Object.class); useJavaOptional = true; } catch (ClassNotFoundException ignored) { } catch (NoSuchMethodException ignored) { } catch (SecurityException ignored) { } JAVA_OPTIONAL_CLASS = useJavaOptional ? optional : null; JAVA_EMPTY_METHOD = useJavaOptional ? empty : null; JAVA_OF_NULLABLE_METHOD = useJavaOptional ? ofNullable : null; } private OptionalBinder() {} public static OptionalBinder newOptionalBinder(Binder binder, Class type) { return newRealOptionalBinder(binder, Key.get(type)); } public static OptionalBinder newOptionalBinder(Binder binder, TypeLiteral type) { return newRealOptionalBinder(binder, Key.get(type)); } public static OptionalBinder newOptionalBinder(Binder binder, Key type) { return newRealOptionalBinder(binder, type); } static RealOptionalBinder newRealOptionalBinder(Binder binder, Key type) { binder = binder.skipSources(OptionalBinder.class, RealOptionalBinder.class); RealOptionalBinder optionalBinder = new RealOptionalBinder(binder, type); binder.install(optionalBinder); return optionalBinder; } @SuppressWarnings("unchecked") static TypeLiteral> optionalOf( TypeLiteral type) { return (TypeLiteral>) TypeLiteral.get( Types.newParameterizedType(Optional.class, type.getType())); } static TypeLiteral javaOptionalOf( TypeLiteral type) { checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found"); return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS, type.getType())); } @SuppressWarnings("unchecked") static TypeLiteral>> optionalOfJavaxProvider( TypeLiteral type) { return (TypeLiteral>>) TypeLiteral.get( Types.newParameterizedType(Optional.class, newParameterizedType(javax.inject.Provider.class, type.getType()))); } static TypeLiteral javaOptionalOfJavaxProvider( TypeLiteral type) { checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found"); return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS, newParameterizedType(javax.inject.Provider.class, type.getType()))); } @SuppressWarnings("unchecked") static TypeLiteral>> optionalOfProvider(TypeLiteral type) { return (TypeLiteral>>) TypeLiteral.get(Types.newParameterizedType( Optional.class, newParameterizedType(Provider.class, type.getType()))); } static TypeLiteral javaOptionalOfProvider(TypeLiteral type) { checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found"); return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS, newParameterizedType(Provider.class, type.getType()))); } @SuppressWarnings("unchecked") static Key> providerOf(Key key) { Type providerT = Types.providerOf(key.getTypeLiteral().getType()); return (Key>) key.ofType(providerT); } /** * Returns a binding builder used to set the default value that will be injected. * The binding set by this method will be ignored if {@link #setBinding} is called. * *

It is an error to call this method without also calling one of the {@code to} * methods on the returned binding builder. */ public abstract LinkedBindingBuilder setDefault(); /** * Returns a binding builder used to set the actual value that will be injected. * This overrides any binding set by {@link #setDefault}. * *

It is an error to call this method without also calling one of the {@code to} * methods on the returned binding builder. */ public abstract LinkedBindingBuilder setBinding(); enum Source { DEFAULT, ACTUAL } @Retention(RUNTIME) @Qualifier @interface Default { String value(); } @Retention(RUNTIME) @Qualifier @interface Actual { String value(); } /** * The actual OptionalBinder plays several roles. It implements Module to hide that * fact from the public API, and installs the various bindings that are exposed to the user. */ static final class RealOptionalBinder extends OptionalBinder implements Module { private final Key typeKey; private final Key> optionalKey; private final Key>> optionalJavaxProviderKey; private final Key>> optionalProviderKey; private final Provider>> optionalProviderT; private final Key defaultKey; private final Key actualKey; private final Key javaOptionalKey; private final Key javaOptionalJavaxProviderKey; private final Key javaOptionalProviderKey; /** the target injector's binder. non-null until initialization, null afterwards */ private Binder binder; /** the default binding, for the SPI. */ private Binding defaultBinding; /** the actual binding, for the SPI */ private Binding actualBinding; /** the dependencies -- initialized with defaults & overridden when tooled. */ private Set> dependencies; /** the dependencies -- initialized with defaults & overridden when tooled. */ private Set> providerDependencies; private RealOptionalBinder(Binder binder, Key typeKey) { this.binder = binder; this.typeKey = checkNotNull(typeKey); TypeLiteral literal = typeKey.getTypeLiteral(); this.optionalKey = typeKey.ofType(optionalOf(literal)); this.optionalJavaxProviderKey = typeKey.ofType(optionalOfJavaxProvider(literal)); this.optionalProviderKey = typeKey.ofType(optionalOfProvider(literal)); this.optionalProviderT = binder.getProvider(optionalProviderKey); String name = RealElement.nameOf(typeKey); this.defaultKey = Key.get(typeKey.getTypeLiteral(), new DefaultImpl(name)); this.actualKey = Key.get(typeKey.getTypeLiteral(), new ActualImpl(name)); // Until the injector initializes us, we don't know what our dependencies are, // so initialize to the whole Injector (like Multibinder, and MapBinder indirectly). this.dependencies = ImmutableSet.>of(Dependency.get(Key.get(Injector.class))); this.providerDependencies = ImmutableSet.>of(Dependency.get(Key.get(Injector.class))); if (JAVA_OPTIONAL_CLASS != null) { this.javaOptionalKey = typeKey.ofType(javaOptionalOf(literal)); this.javaOptionalJavaxProviderKey = typeKey.ofType(javaOptionalOfJavaxProvider(literal)); this.javaOptionalProviderKey = typeKey.ofType(javaOptionalOfProvider(literal)); } else { this.javaOptionalKey = null; this.javaOptionalJavaxProviderKey = null; this.javaOptionalProviderKey = null; } } /** * Adds a binding for T. Multiple calls to this are safe, and will be collapsed as duplicate * bindings. */ private void addDirectTypeBinding(Binder binder) { binder.bind(typeKey).toProvider(new RealDirectTypeProvider()); } Key getKeyForDefaultBinding() { checkConfiguration(!isInitialized(), "already initialized"); addDirectTypeBinding(binder); return defaultKey; } @Override public LinkedBindingBuilder setDefault() { return binder.bind(getKeyForDefaultBinding()); } Key getKeyForActualBinding() { checkConfiguration(!isInitialized(), "already initialized"); addDirectTypeBinding(binder); return actualKey; } @Override public LinkedBindingBuilder setBinding() { return binder.bind(getKeyForActualBinding()); } @Override public void configure(Binder binder) { checkConfiguration(!isInitialized(), "OptionalBinder was already initialized"); binder.bind(optionalProviderKey).toProvider(new RealOptionalProviderProvider()); // Optional is immutable, so it's safe to expose Optional> as // Optional> (since Guice provider implements javax Provider). @SuppressWarnings({"unchecked", "cast"}) Key massagedOptionalProviderKey = (Key) optionalProviderKey; binder.bind(optionalJavaxProviderKey).to(massagedOptionalProviderKey); binder.bind(optionalKey).toProvider(new RealOptionalKeyProvider()); // Bind the java-8 types if we know them. bindJava8Optional(binder); } @SuppressWarnings("unchecked") private void bindJava8Optional(Binder binder) { if (JAVA_OPTIONAL_CLASS != null) { binder.bind(javaOptionalKey).toProvider(new JavaOptionalProvider()); binder.bind(javaOptionalProviderKey).toProvider(new JavaOptionalProviderProvider()); // for the javax version we reuse the guice version since they're type-compatible. binder.bind(javaOptionalJavaxProviderKey).to(javaOptionalProviderKey); } } @SuppressWarnings("rawtypes") final class JavaOptionalProvider extends RealOptionalBinderProviderWithDependencies implements ProviderWithExtensionVisitor, OptionalBinderBinding { private JavaOptionalProvider() { super(typeKey); } @Override public Object get() { Optional> optional = optionalProviderT.get(); try { if (optional.isPresent()) { return JAVA_OF_NULLABLE_METHOD.invoke(JAVA_OPTIONAL_CLASS, optional.get().get()); } else { return JAVA_EMPTY_METHOD.invoke(JAVA_OPTIONAL_CLASS); } } catch (IllegalAccessException e) { throw new SecurityException(e); } catch (IllegalArgumentException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw Throwables.propagate(e.getCause()); } } @Override public Set> getDependencies() { return dependencies; } @SuppressWarnings("unchecked") @Override public Object acceptExtensionVisitor(BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor) visitor).visit(this); } else { return visitor.visit(binding); } } @Override public boolean containsElement(Element element) { return RealOptionalBinder.this.containsElement(element); } @Override public Binding getActualBinding() { return RealOptionalBinder.this.getActualBinding(); } @Override public Binding getDefaultBinding() { return RealOptionalBinder.this.getDefaultBinding(); } @Override public Key getKey() { return javaOptionalKey; } } @SuppressWarnings("rawtypes") final class JavaOptionalProviderProvider extends RealOptionalBinderProviderWithDependencies { private JavaOptionalProviderProvider() { super(typeKey); } @Override public Object get() { Optional> optional = optionalProviderT.get(); try { if (optional.isPresent()) { return JAVA_OF_NULLABLE_METHOD.invoke(JAVA_OPTIONAL_CLASS, optional.get()); } else { return JAVA_EMPTY_METHOD.invoke(JAVA_OPTIONAL_CLASS); } } catch (IllegalAccessException e) { throw new SecurityException(e); } catch (IllegalArgumentException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw Throwables.propagate(e.getCause()); } } @Override public Set> getDependencies() { return providerDependencies; } } final class RealDirectTypeProvider extends RealOptionalBinderProviderWithDependencies { private RealDirectTypeProvider() { super(typeKey); } @Override public T get() { Optional> optional = optionalProviderT.get(); if (optional.isPresent()) { return optional.get().get(); } // Let Guice handle blowing up if the injection point doesn't have @Nullable // (If it does have @Nullable, that's fine. This would only happen if // setBinding/setDefault themselves were bound to 'null'). return null; } @Override public Set> getDependencies() { return dependencies; } } final class RealOptionalProviderProvider extends RealOptionalBinderProviderWithDependencies>> { private Optional> optional; private RealOptionalProviderProvider() { super(typeKey); } @Toolable @Inject void initialize(Injector injector) { RealOptionalBinder.this.binder = null; actualBinding = injector.getExistingBinding(actualKey); defaultBinding = injector.getExistingBinding(defaultKey); Binding userBinding = injector.getExistingBinding(typeKey); Binding binding = null; if (actualBinding != null) { // TODO(sameb): Consider exposing an option that will allow // ACTUAL to fallback to DEFAULT if ACTUAL's provider returns null. // Right now, an ACTUAL binding can convert from present -> absent // if it's bound to a provider that returns null. binding = actualBinding; } else if (defaultBinding != null) { binding = defaultBinding; } else if (userBinding != null) { // If neither the actual or default is set, then we fallback // to the value bound to the type itself and consider that the // "actual binding" for the SPI. binding = userBinding; actualBinding = userBinding; } if (binding != null) { optional = Optional.of(binding.getProvider()); RealOptionalBinder.this.dependencies = ImmutableSet.>of(Dependency.get(binding.getKey())); RealOptionalBinder.this.providerDependencies = ImmutableSet.>of(Dependency.get(providerOf(binding.getKey()))); } else { optional = Optional.absent(); RealOptionalBinder.this.dependencies = ImmutableSet.of(); RealOptionalBinder.this.providerDependencies = ImmutableSet.of(); } } @Override public Optional> get() { return optional; } @Override public Set> getDependencies() { return providerDependencies; } } final class RealOptionalKeyProvider extends RealOptionalBinderProviderWithDependencies> implements ProviderWithExtensionVisitor>, OptionalBinderBinding>, Provider> { private RealOptionalKeyProvider() { super(typeKey); } @Override public Optional get() { Optional> optional = optionalProviderT.get(); if (optional.isPresent()) { return Optional.fromNullable(optional.get().get()); } else { return Optional.absent(); } } @Override public Set> getDependencies() { return dependencies; } @SuppressWarnings("unchecked") @Override public R acceptExtensionVisitor(BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor, R>) visitor).visit(this); } else { return visitor.visit(binding); } } @Override public Key> getKey() { return optionalKey; } @Override public Binding getActualBinding() { return RealOptionalBinder.this.getActualBinding(); } @Override public Binding getDefaultBinding() { return RealOptionalBinder.this.getDefaultBinding(); } @Override public boolean containsElement(Element element) { return RealOptionalBinder.this.containsElement(element); } } private Binding getActualBinding() { if (isInitialized()) { return actualBinding; } else { throw new UnsupportedOperationException( "getActualBinding() not supported from Elements.getElements, requires an Injector."); } } private Binding getDefaultBinding() { if (isInitialized()) { return defaultBinding; } else { throw new UnsupportedOperationException( "getDefaultBinding() not supported from Elements.getElements, requires an Injector."); } } private boolean containsElement(Element element) { Key elementKey; if (element instanceof Binding) { elementKey = ((Binding) element).getKey(); } else if (element instanceof ProviderLookup) { elementKey = ((ProviderLookup) element).getKey(); } else { return false; // cannot match; } return elementKey.equals(optionalKey) || elementKey.equals(optionalProviderKey) || elementKey.equals(optionalJavaxProviderKey) || elementKey.equals(defaultKey) || elementKey.equals(actualKey) || matchesJ8Keys(elementKey) || matchesTypeKey(element, elementKey); } private boolean matchesJ8Keys(Key elementKey) { if (JAVA_OPTIONAL_CLASS != null) { return elementKey.equals(javaOptionalKey) || elementKey.equals(javaOptionalProviderKey) || elementKey.equals(javaOptionalJavaxProviderKey); } return false; } /** Returns true if the key & element indicate they were bound by this OptionalBinder. */ private boolean matchesTypeKey(Element element, Key elementKey) { // Just doing .equals(typeKey) isn't enough, because the user can bind that themselves. return elementKey.equals(typeKey) && element instanceof ProviderInstanceBinding && (((ProviderInstanceBinding) element) .getUserSuppliedProvider() instanceof RealOptionalBinderProviderWithDependencies); } private boolean isInitialized() { return binder == null; } @Override public boolean equals(Object o) { return o instanceof RealOptionalBinder && ((RealOptionalBinder) o).typeKey.equals(typeKey); } @Override public int hashCode() { return typeKey.hashCode(); } /** * A base class for ProviderWithDependencies that need equality based on a specific object. */ private abstract static class RealOptionalBinderProviderWithDependencies implements ProviderWithDependencies { private final Object equality; public RealOptionalBinderProviderWithDependencies(Object equality) { this.equality = equality; } @Override public boolean equals(Object obj) { return obj != null && this.getClass() == obj.getClass() && equality.equals(((RealOptionalBinderProviderWithDependencies) obj).equality); } @Override public int hashCode() { return equality.hashCode(); } } } static class DefaultImpl extends BaseAnnotation implements Default { public DefaultImpl(String value) { super(Default.class, value); } } static class ActualImpl extends BaseAnnotation implements Actual { public ActualImpl(String value) { super(Actual.class, value); } } abstract static class BaseAnnotation implements Serializable, Annotation { private final String value; private final Class clazz; BaseAnnotation(Class clazz, String value) { this.clazz = checkNotNull(clazz, "clazz"); this.value = checkNotNull(value, "value"); } public String value() { return this.value; } @Override public int hashCode() { // This is specified in java.lang.Annotation. return (127 * "value".hashCode()) ^ value.hashCode(); } @Override public boolean equals(Object o) { // We check against each annotation type instead of BaseAnnotation // so that we can compare against generated annotation implementations. if (o instanceof Actual && clazz == Actual.class) { Actual other = (Actual) o; return value.equals(other.value()); } else if (o instanceof Default && clazz == Default.class) { Default other = (Default) o; return value.equals(other.value()); } return false; } @Override public String toString() { return "@" + clazz.getName() + (value.isEmpty() ? "" : "(value=" + value + ")"); } @Override public Class annotationType() { return clazz; } private static final long serialVersionUID = 0; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy