blob: 185d647acfdc36ef7ac8c215ee6c8861bef0de70 [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.multibindings;
import com.google.common.base.Objects;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.spi.InjectionPoint;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A mutable annotation implementation with non-conforming {@link #equals} and {@link #hashCode}
* methods.
*
* <p>Annotation equality is defined based on the binding it is used in, letting Guice spot and
* remove duplicate bindings without false conflicts. However, as the binding is mutable, so is
* the annotation.
*
* @author chrispurcell@google.com (Chris Purcell)
*/
class RealElement implements Element {
private static final AtomicInteger nextUniqueId = new AtomicInteger(1);
/**
* Adds a new binding to a multibindings collection, returning its {@link BindingBuilder}.
*
* @param binder the current Guice binder
* @param type whether the collection is a set or a map
* @param elementType the type of element stored in the collection
* @param setName the string used internally to identify the collection
*/
static <T> BindingBuilder<T> addBinding(
Binder binder, Element.Type type, TypeLiteral<T> elementType, String setName) {
RealElement annotation = new RealElement(setName, type, null);
LinkedBindingBuilder<T> delegate = binder
.skipSources(RealElement.class)
.bind(Key.get(elementType, annotation));
return new BindingBuilder<T>(annotation, delegate);
}
/**
* Adds a new map value binding to a multibindings collection, returning its
* {@link BindingBuilder}.
*
* @param binder the current Guice binder
* @param mapKey the key used to fetch the element from the map
* @param elementType the type of element stored in the collection
* @param setName the string used internally to identify the collection
*/
static <T> BindingBuilder<T> addMapBinding(
Binder binder, Object mapKey, TypeLiteral<T> elementType, String setName) {
RealElement annotation = new RealElement(setName, Element.Type.MAPBINDER, mapKey);
LinkedBindingBuilder<T> delegate = binder
.skipSources(RealElement.class)
.bind(Key.get(elementType, annotation));
return new BindingBuilder<T>(annotation, delegate);
}
private final int uniqueId;
private final String setName;
private final Element.Type type;
private final Object mapKey;
private TargetType targetType = TargetType.UNTARGETTED;
private Object target = null;
private Object scope = Scopes.NO_SCOPE;
private RealElement(String setName, Element.Type type, Object mapKey) {
this.uniqueId = nextUniqueId.incrementAndGet();
this.setName = setName;
this.type = type;
this.mapKey = mapKey;
}
public String setName() {
return setName;
}
public int uniqueId() {
return uniqueId;
}
public Element.Type type() {
return type;
}
public Class<? extends Annotation> annotationType() {
return Element.class;
}
@Override public String toString() {
return String.format("@%s(setName=%s, uniqueId=%d, type=%s)",
annotationType().getName(), setName, uniqueId, type);
}
/**
* Returns true if the given object is a {@link RealElement} associated with a binding that
* is considered a duplicate of the one associated with this object. Specifically, that means all
* the following must hold:
* <ul>
* <li>the bindings are from the same collection (as determined by {@link #setName} and
* {@link #type})
* <li>the bindings are in the same scope
* <li>the target types ("instance", "linked key", etc.) match
* <li>the targets themselves (the instance, the linked key, etc.) are equal
* </ul>
*
* <p>Note that this means the definition of equality can change if a bound instance changes.
*
* <p>This <b>does not</b> match the definition of {@link Annotation#equals}. However, as
* these annotations will only ever be used within Guice, and {@link Element} itself is package
* private and will never be used as an annotation, this should not cause problems.
*/
@Override public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
RealElement other = (RealElement) obj;
return (setName.equals(other.setName)
&& type.equals(other.type)
&& Objects.equal(mapKey, other.mapKey)
&& scope.equals(other.scope)
&& targetType.equals(other.targetType)
&& Objects.equal(target, other.target));
}
/**
* Returns the hash code of this annotation. This depends on the binding the annotation is in.
*
* <p>This <b>does not</b> match the definition of {@link Annotation#hashCode}. However, as
* these annotations will only ever be used within Guice, and {@link Element} itself is package
* private and will never be used as an annotation, this should not cause problems.
*/
@Override public int hashCode() {
return Objects.hashCode(setName, type, mapKey, scope, targetType, target);
}
private enum TargetType {
INSTANCE, PROVIDER_INSTANCE, PROVIDER_KEY, LINKED_KEY, UNTARGETTED, CONSTRUCTOR
}
private static final Object EAGER_SINGLETON = new Object();
static class BindingBuilder<T> implements LinkedBindingBuilder<T> {
private final RealElement annotation;
private final LinkedBindingBuilder<T> delegate;
BindingBuilder(RealElement annotation, LinkedBindingBuilder<T> delegate) {
this.annotation = annotation;
this.delegate = delegate;
}
RealElement getAnnotation() {
return annotation;
}
public void in(Class<? extends Annotation> scopeAnnotation) {
delegate.in(scopeAnnotation);
annotation.scope = scopeAnnotation;
}
public void in(Scope scope) {
delegate.in(scope);
annotation.scope = scope;
}
public void asEagerSingleton() {
delegate.asEagerSingleton();
annotation.scope = EAGER_SINGLETON;
}
public ScopedBindingBuilder to(Class<? extends T> implementation) {
return to(Key.get(implementation));
}
public ScopedBindingBuilder to(TypeLiteral<? extends T> implementation) {
return to(Key.get(implementation));
}
public ScopedBindingBuilder to(Key<? extends T> targetKey) {
delegate.to(targetKey);
annotation.targetType = TargetType.LINKED_KEY;
annotation.target = targetKey;
return this;
}
public void toInstance(T instance) {
delegate.toInstance(instance);
annotation.scope = EAGER_SINGLETON;
annotation.targetType = TargetType.INSTANCE;
annotation.target = instance;
}
public ScopedBindingBuilder toProvider(Provider<? extends T> provider) {
delegate.toProvider(provider);
annotation.targetType = TargetType.PROVIDER_INSTANCE;
annotation.target = provider;
return this;
}
public ScopedBindingBuilder toProvider(
Class<? extends javax.inject.Provider<? extends T>> providerType) {
return toProvider(Key.get(providerType));
}
public ScopedBindingBuilder toProvider(
TypeLiteral<? extends javax.inject.Provider<? extends T>> providerType) {
return toProvider(Key.get(providerType));
}
public ScopedBindingBuilder toProvider(
Key<? extends javax.inject.Provider<? extends T>> providerKey) {
delegate.toProvider(providerKey);
annotation.targetType = TargetType.PROVIDER_KEY;
annotation.target = providerKey;
return this;
}
public <S extends T> ScopedBindingBuilder toConstructor(Constructor<S> constructor) {
return toConstructor(constructor, TypeLiteral.get(constructor.getDeclaringClass()));
}
public <S extends T> ScopedBindingBuilder toConstructor(
Constructor<S> constructor, TypeLiteral<? extends S> type) {
delegate.toConstructor(constructor, type);
annotation.targetType = TargetType.CONSTRUCTOR;
annotation.target = InjectionPoint.forConstructor(constructor, type);
return this;
}
}
}