blob: d15845a4dd46ae23c3fdbdff962fd9cc399e9408 [file] [log] [blame]
/**
* Copyright (C) 2008 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.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.ConfigurationException;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.AnnotatedConstantBindingBuilder;
import com.google.inject.binder.ConstantBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.DefaultBindingTargetVisitor;
import com.google.inject.spi.ElementVisitor;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.Message;
import com.google.inject.util.Providers;
import java.lang.annotation.Annotation;
import java.util.Set;
/**
* Immutable snapshot of a request to bind a value.
*
* @author jessewilson@google.com (Jesse Wilson)
*/
public final class ModuleBinding<T> implements Binding<T> {
private final Key<?> NULL_KEY = Key.get(Void.class);
private static final Target<Object> EMPTY_TARGET = new Target<Object>() {
public <V> V acceptTargetVisitor(BindingTargetVisitor<? super Object, V> visitor) {
return visitor.visitUntargetted();
}
};
private static final Scoping EMPTY_SCOPING = new AbstractScoping() {
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitNoScoping();
}
};
private static final BindingTargetVisitor<Object, Boolean> SUPPORTS_SCOPES
= new DefaultBindingTargetVisitor<Object, Boolean>() {
@Override public Boolean visitInstance(Object instance, Set<InjectionPoint> injectionPoints) {
return false;
}
@Override protected Boolean visitOther() {
return true;
}
};
private final Object source;
private Key<T> key;
@SuppressWarnings("unchecked")
private Target<T> target = (Target<T>) EMPTY_TARGET;
private Scoping scoping = EMPTY_SCOPING;
public ModuleBinding(Object source, Key<T> key) {
this.source = checkNotNull(source, "source");
this.key = checkNotNull(key, "key");
}
public ModuleBinding(Object source) {
@SuppressWarnings("unchecked") // unsafe, but we won't ever return this (Key.get fails)
Key<T> NULL_KEY_OF_T = (Key<T>) NULL_KEY;
this.source = checkNotNull(source);
this.key = NULL_KEY_OF_T;
}
public Object getSource() {
return source;
}
/**
* Returns the scoped provider guice uses to fulfill requests for this
* binding.
*/
public Provider<T> getProvider() {
throw new UnsupportedOperationException();
}
public <V> V acceptVisitor(ElementVisitor<V> visitor) {
return visitor.visitBinding(this);
}
public Key<T> getKey() {
return key;
}
public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
return target.acceptTargetVisitor(visitor);
}
public <V> V acceptScopingVisitor(BindingScopingVisitor<V> visitor) {
return scoping.acceptVisitor(visitor);
}
private boolean keyTypeIsSet() {
return !Void.class.equals(key.getTypeLiteral().getType());
}
@Override public String toString() {
return "bind " + key
+ (target == EMPTY_TARGET ? "" : (" to " + target))
+ (scoping == EMPTY_SCOPING ? "" : (" in " + scoping));
}
private static abstract class AbstractScoping implements Scoping {
public Scope getScope() {
return null;
}
public Class<? extends Annotation> getScopeAnnotation() {
return null;
}
}
public RegularBuilder regularBuilder(Binder binder) {
return new RegularBuilder(binder);
}
/**
* Write access to the internal state of this element. Not for use by the public API.
*/
public class RegularBuilder implements AnnotatedBindingBuilder<T> {
private final Binder binder;
RegularBuilder(Binder binder) {
this.binder = binder.skipSources(RegularBuilder.class);
}
public LinkedBindingBuilder<T> annotatedWith(
Class<? extends Annotation> annotationType) {
checkNotNull(annotationType, "annotationType");
checkNotAnnotated();
key = Key.get(key.getTypeLiteral(), annotationType);
return this;
}
public LinkedBindingBuilder<T> annotatedWith(Annotation annotation) {
checkNotNull(annotation, "annotation");
checkNotAnnotated();
key = Key.get(key.getTypeLiteral(), annotation);
return this;
}
public ScopedBindingBuilder to(final Class<? extends T> implementation) {
return to(Key.get(implementation));
}
public ScopedBindingBuilder to(
final TypeLiteral<? extends T> implementation) {
return to(Key.get(implementation));
}
public ScopedBindingBuilder to(final Key<? extends T> targetKey) {
checkNotNull(targetKey, "targetKey");
checkNotTargetted();
target = new Target<T>() {
public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
return visitor.visitKey(targetKey);
}
@Override public String toString() {
return String.valueOf(targetKey);
}
};
return this;
}
public void toInstance(final T instance) {
checkNotTargetted();
if (instance == null) {
binder.addError(BINDING_TO_NULL);
// we finish the binding to prevent additional errors
toProvider(Providers.<T>of(null));
return;
}
// lookup the injection points, adding any errors to the binder's errors list
Set<InjectionPoint> injectionPoints;
try {
injectionPoints = InjectionPoint.forInstanceMethodsAndFields(instance.getClass());
} catch (ConfigurationException e) {
for (Message message : e.getErrorMessages()) {
binder.addError(message);
}
injectionPoints = e.getPartialValue();
}
target = new InstanceTarget<T>(instance, injectionPoints);
}
public ScopedBindingBuilder toProvider(final Provider<? extends T> provider) {
checkNotNull(provider, "provider");
checkNotTargetted();
// lookup the injection points, adding any errors to the binder's errors list
Set<InjectionPoint> injectionPoints;
try {
injectionPoints = InjectionPoint.forInstanceMethodsAndFields(provider.getClass());
} catch (ConfigurationException e) {
for (Message message : e.getErrorMessages()) {
binder.addError(message);
}
injectionPoints = e.getPartialValue();
}
final Set<InjectionPoint> injectionPointsFinal = injectionPoints;
target = new Target<T>() {
public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
return visitor.visitProvider(provider, injectionPointsFinal);
}
};
return this;
}
public ScopedBindingBuilder toProvider(
Class<? extends Provider<? extends T>> providerType) {
return toProvider(Key.get(providerType));
}
public ScopedBindingBuilder toProvider(
final Key<? extends Provider<? extends T>> providerKey) {
checkNotNull(providerKey, "providerKey");
checkNotTargetted();
target = new Target<T>() {
public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
return visitor.visitProviderKey(providerKey);
}
};
return this;
}
public void in(final Class<? extends Annotation> scopeAnnotation) {
checkNotNull(scopeAnnotation, "scopeAnnotation");
checkNotScoped();
scoping = new AbstractScoping() {
@Override public Class<? extends Annotation> getScopeAnnotation() {
return scopeAnnotation;
}
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitScopeAnnotation(scopeAnnotation);
}
@Override public String toString() {
return scopeAnnotation.getName();
}
};
}
public void in(final Scope scope) {
checkNotNull(scope, "scope");
checkNotScoped();
scoping = new AbstractScoping() {
@Override public Scope getScope() {
return scope;
}
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitScope(scope);
}
@Override public String toString() {
return String.valueOf(scope);
}
};
}
public void asEagerSingleton() {
checkNotScoped();
scoping = new AbstractScoping() {
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitEagerSingleton();
}
@Override public String toString() {
return "eager singleton";
}
};
}
static final String IMPLEMENTATION_ALREADY_SET
= "Implementation is set more than once.";
static final String SINGLE_INSTANCE_AND_SCOPE = "Setting the scope is not"
+ " permitted when binding to a single instance.";
static final String SCOPE_ALREADY_SET = "Scope is set more than once.";
static final String ANNOTATION_ALREADY_SPECIFIED = "More than one annotation"
+ " is specified for this binding.";
private void checkNotTargetted() {
if (target != EMPTY_TARGET) {
binder.addError(IMPLEMENTATION_ALREADY_SET);
}
}
private void checkNotAnnotated() {
if (ModuleBinding.this.key.getAnnotationType() != null) {
binder.addError(ANNOTATION_ALREADY_SPECIFIED);
}
}
private void checkNotScoped() {
@SuppressWarnings("unchecked") BindingTargetVisitor<T,Boolean> supportsScopesOfT
= (BindingTargetVisitor<T,Boolean>) SUPPORTS_SCOPES;
// Scoping isn't allowed when we have only one instance.
if (!target.acceptTargetVisitor(supportsScopesOfT)) {
binder.addError(SINGLE_INSTANCE_AND_SCOPE);
return;
}
if (scoping != EMPTY_SCOPING) {
binder.addError(SCOPE_ALREADY_SET);
}
}
@Override public String toString() {
String type = key.getAnnotationType() == null
? "AnnotatedBindingBuilder<"
: "LinkedBindingBuilder<";
return type + key.getTypeLiteral() + ">";
}
}
public ConstantBuilder constantBuilder(Binder binder) {
return new ConstantBuilder(binder);
}
/**
* Package-private write access to the internal state of this element.
*/
class ConstantBuilder
implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder {
private final Binder binder;
ConstantBuilder(Binder binder) {
this.binder = binder.skipSources(ConstantBuilder.class);
}
public ConstantBindingBuilder annotatedWith(final Class<? extends Annotation> annotationType) {
checkNotNull(annotationType, "annotationType");
if (key.getAnnotationType() != null) {
binder.addError(ANNOTATION_ALREADY_SPECIFIED);
} else {
key = Key.get(key.getTypeLiteral(), annotationType);
}
return this;
}
public ConstantBindingBuilder annotatedWith(final Annotation annotation) {
checkNotNull(annotation, "annotation");
if (key.getAnnotationType() != null) {
binder.addError(ANNOTATION_ALREADY_SPECIFIED);
} else {
key = Key.get(key.getTypeLiteral(), annotation);
}
return this;
}
public void to(final String value) {
to(String.class, value);
}
public void to(final int value) {
to(Integer.class, value);
}
public void to(final long value) {
to(Long.class, value);
}
public void to(final boolean value) {
to(Boolean.class, value);
}
public void to(final double value) {
to(Double.class, value);
}
public void to(final float value) {
to(Float.class, value);
}
public void to(final short value) {
to(Short.class, value);
}
public void to(final char value) {
to(Character.class, value);
}
public void to(final Class<?> value) {
to(Class.class, value);
}
public <E extends Enum<E>> void to(final E value) {
to(value.getDeclaringClass(), value);
}
private void to(Class<?> type, Object instance) {
// this type will define T, so these assignments are safe
@SuppressWarnings("unchecked")
Class<T> typeAsClassT = (Class<T>) type;
@SuppressWarnings("unchecked")
T instanceAsT = (T) instance;
if (keyTypeIsSet()) {
binder.addError(CONSTANT_VALUE_ALREADY_SET);
return;
}
if (key.getAnnotation() != null) {
key = Key.get(typeAsClassT, key.getAnnotation());
} else if (key.getAnnotationType() != null) {
key = Key.get(typeAsClassT, key.getAnnotationType());
} else {
key = Key.get(typeAsClassT);
}
ModuleBinding.this.target = new InstanceTarget<T>(instanceAsT,
ImmutableSet.<InjectionPoint>of());
if (instanceAsT == null) {
binder.addError(BINDING_TO_NULL);
}
}
@Override public String toString() {
return key.getAnnotationType() == null
? "AnnotatedConstantBindingBuilder"
: "ConstantBindingBuilder";
}
}
/** A binding target, which provides instances from a specific key. */
private interface Target<T> {
<V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor);
}
/** Immutable snapshot of a binding scope. */
private interface Scoping {
<V> V acceptVisitor(BindingScopingVisitor<V> visitor);
}
static class InstanceTarget<T> implements Target<T> {
private final T instance;
private final ImmutableSet<InjectionPoint> injectionPoints;
public InstanceTarget(T instance, Set<InjectionPoint> injectionPoints) {
this.instance = instance;
this.injectionPoints = ImmutableSet.copyOf(injectionPoints);
}
public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
return visitor.visitInstance(instance, injectionPoints);
}
}
static final String BINDING_TO_NULL = "Binding to null instances is not allowed. "
+ "Use toProvider(Providers.of(null)) if this is your intended behaviour.";
static final String CONSTANT_VALUE_ALREADY_SET = "Constant value is set more"
+ " than once.";
static final String ANNOTATION_ALREADY_SPECIFIED = "More than one annotation"
+ " is specified for this binding.";
}