Added support for binding to annotations instead of names.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@142 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/ConstantConversionException.java b/src/com/google/inject/ConstantConversionException.java
index ff170b5..c90def4 100644
--- a/src/com/google/inject/ConstantConversionException.java
+++ b/src/com/google/inject/ConstantConversionException.java
@@ -37,15 +37,19 @@
static String createMessage(String value, Key<?> key, Member member,
String reason) {
+ String annotationMessage = key.hasAnnotationType()
+ ? " annotated with " + key.getAnnotationName()
+ : "";
+
return member == null
? "Error converting '" + value + "' to "
+ key.getRawType().getSimpleName()
- + " while getting dependency named '" + key.getName()
- + "'. Reason: " + reason
+ + " while getting binding value" + annotationMessage
+ + ". Reason: " + reason
: "Error converting '" + value + "' to "
+ key.getRawType().getSimpleName() + " while injecting "
- + member.getName() + " with dependency named '" + key.getName()
- + "' in " + member.getDeclaringClass().getSimpleName()
+ + member.getName() + " with binding value" + annotationMessage
+ + " required by " + member.getDeclaringClass().getSimpleName()
+ ". Reason: " + reason;
}
}
diff --git a/src/com/google/inject/ConstructorInjector.java b/src/com/google/inject/ConstructorInjector.java
index d36ea31..6e3b0ea 100644
--- a/src/com/google/inject/ConstructorInjector.java
+++ b/src/com/google/inject/ConstructorInjector.java
@@ -16,9 +16,6 @@
package com.google.inject;
-import com.google.inject.util.SurrogateAnnotations;
-import com.google.inject.util.DuplicateAnnotationException;
-
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -34,9 +31,6 @@
final ContainerImpl.ParameterInjector<?>[] parameterInjectors;
final ConstructionProxy<T> constructionProxy;
- /** Annotation on the constructor. */
- Inject inject;
-
ConstructorInjector(ContainerImpl container, Class<T> implementation) {
this.implementation = implementation;
Constructor<T> constructor = findConstructorIn(container, implementation);
@@ -59,13 +53,12 @@
ContainerImpl.ParameterInjector<?>[] createParameterInjector(
ContainerImpl container, Constructor<T> constructor) {
try {
- return inject == null
+ return constructor.getParameterTypes().length == 0
? null // default constructor.
: container.getParametersInjectors(
constructor,
constructor.getParameterAnnotations(),
- constructor.getGenericParameterTypes(),
- inject.value()
+ constructor.getGenericParameterTypes()
);
}
catch (ContainerImpl.MissingDependencyException e) {
@@ -77,27 +70,17 @@
private Constructor<T> findConstructorIn(ContainerImpl container,
Class<T> implementation) {
Constructor<T> found = null;
- @SuppressWarnings("unchecked") // why doesn't it return the right thing?
+ @SuppressWarnings("unchecked")
Constructor<T>[] constructors
= (Constructor<T>[]) implementation.getDeclaredConstructors();
for (Constructor<T> constructor : constructors) {
- Inject inject = null;
- try {
- inject = SurrogateAnnotations.findAnnotation(Inject.class, constructor);
- } catch (DuplicateAnnotationException e) {
- container.errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS,
- Inject.class.getSimpleName(), constructor, e.getFirst(),
- e.getSecond());
- }
-
- if (inject != null) {
+ if (constructor.getAnnotation(Inject.class) != null) {
if (found != null) {
container.errorHandler.handle(
ErrorMessages.TOO_MANY_CONSTRUCTORS, implementation);
return ContainerImpl.invalidConstructor();
}
found = constructor;
- this.inject = inject;
}
}
if (found != null) {
diff --git a/src/com/google/inject/Container.java b/src/com/google/inject/Container.java
index ac3302a..a7113ff 100644
--- a/src/com/google/inject/Container.java
+++ b/src/com/google/inject/Container.java
@@ -18,51 +18,11 @@
import java.util.List;
import java.util.Map;
+import java.lang.annotation.Annotation;
/**
* Injects dependencies into constructors, methods and fields annotated with
- * @{@link Inject}.
- *
- * <p>When injecting a method or constructor, you can additionally annotate its
- * parameters with @{@link Inject} and specify a dependency name. When a
- * parameter has no annotation, the container uses the name from the method or
- * constructor's @{@link Inject} annotation respectively.
- *
- * <p>For example:
- *
- * <pre>
- * class Foo {
- *
- * // Inject the int constant named "i".
- * @Inject("i") int i;
- *
- * // Inject the default implementation of Bar and the String constant
- * // named "s".
- * @Inject Foo(Bar bar, @Inject("s") String s) {
- * ...
- * }
- *
- * // Inject the default implementation of Baz and the Bob implementation
- * // named "foo".
- * @Inject void initialize(Baz baz, @Inject("foo") Bob bob) {
- * ...
- * }
- *
- * // Inject the default implementation of Tee.
- * @Inject void setTee(Tee tee) {
- * ...
- * }
- * }
- * </pre>
- *
- * <p>To get an instance of {@code Foo}:
- *
- * <pre>
- * Container c = ...;
- * Key<Foo> fooKey = Key.get(Foo.class);
- * Factory<Foo> fooFactory = c.getFactory(fooKey);
- * Foo foo = fooFactory.get();
- * </pre>
+ * {@code @}{@link Inject}. Provides access to {@link Binding}s.
*
* @author crazybob@google.com (Bob Lee)
* @see ContainerBuilder
@@ -120,22 +80,50 @@
<T> T getInstance(Key<T> key);
/**
- * Gets an instance from the factory bound to the given type and name.
+ * Gets an instance from the factory bound to the given type and annotation.
*/
- <T> T getInstance(TypeLiteral<T> type, String name);
+ <T> T getInstance(TypeLiteral<T> type,
+ Annotation annotation);
/**
- * Gets an instance from the factory bound to the given type and name.
+ * Gets an instance from the factory bound to the given type and annotation.
*/
- <T> T getInstance(Class<T> type, String name);
+ <T> T getInstance(Class<T> type,
+ Annotation annotation);
/**
- * Gets the factory bound to the given type and name.
+ * Gets the factory bound to the given type and annotation.
*/
- <T> Factory<T> getFactory(Class<T> type, String name);
+ <T> Factory<T> getFactory(Class<T> type,
+ Annotation annotation);
/**
- * Gets the factory bound to the given type and name.
+ * Gets the factory bound to the given type and annotation.
*/
- <T> Factory<T> getFactory(TypeLiteral<T> type, String name);
+ <T> Factory<T> getFactory(TypeLiteral<T> type,
+ Annotation annotation);
+
+ /**
+ * Gets an instance from the factory bound to the given type and annotation.
+ */
+ <T> T getInstance(TypeLiteral<T> type,
+ Class<? extends Annotation> annotationType);
+
+ /**
+ * Gets an instance from the factory bound to the given type and annotation.
+ */
+ <T> T getInstance(Class<T> type,
+ Class<? extends Annotation> annotationType);
+
+ /**
+ * Gets the factory bound to the given type and annotation.
+ */
+ <T> Factory<T> getFactory(Class<T> type,
+ Class<? extends Annotation> annotationType);
+
+ /**
+ * Gets the factory bound to the given type and annotation.
+ */
+ <T> Factory<T> getFactory(TypeLiteral<T> type,
+ Class<? extends Annotation> annotationType);
}
diff --git a/src/com/google/inject/ContainerBuilder.java b/src/com/google/inject/ContainerBuilder.java
index 0aca683..7dbff74 100644
--- a/src/com/google/inject/ContainerBuilder.java
+++ b/src/com/google/inject/ContainerBuilder.java
@@ -17,6 +17,7 @@
package com.google.inject;
import com.google.inject.ContainerImpl.Injector;
+import com.google.inject.Key.AnnotationStrategy;
import static com.google.inject.Scopes.CONTAINER;
import static com.google.inject.Scopes.CONTAINER_NAME;
import static com.google.inject.Scopes.DEFAULT;
@@ -24,13 +25,10 @@
import com.google.inject.matcher.Matcher;
import com.google.inject.spi.Message;
import com.google.inject.spi.SourceConsumer;
-import com.google.inject.util.Objects;
import static com.google.inject.util.Objects.nonNull;
import com.google.inject.util.Stopwatch;
import com.google.inject.util.ToStringBuilder;
-
-import org.aopalliance.intercept.MethodInterceptor;
-
+import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -40,6 +38,7 @@
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
+import org.aopalliance.intercept.MethodInterceptor;
/**
* Builds a dependency injection {@link Container}. Binds {@link Key}s to
@@ -197,15 +196,33 @@
*/
public ConstantBindingBuilder bind(String name) {
ensureNotCreated();
- return bind(name, source());
+ return bind(source(), Key.strategyFor(new NamedImpl(name)));
+ }
+
+ /**
+ * Binds a constant to the given annotation.
+ */
+ public ConstantBindingBuilder bind(Annotation annotation) {
+ ensureNotCreated();
+ return bind(source(), Key.strategyFor(annotation));
+ }
+
+ /**
+ * Binds a constant to the given annotation type.
+ */
+ public ConstantBindingBuilder bind(
+ Class<? extends Annotation> annotationType) {
+ ensureNotCreated();
+ return bind(source(), Key.strategyFor(annotationType));
}
/**
* Binds a constant to the given name from the given source.
*/
- private ConstantBindingBuilder bind(String name, Object source) {
+ private ConstantBindingBuilder bind(Object source,
+ AnnotationStrategy annotationStrategy) {
ConstantBindingBuilder builder =
- new ConstantBindingBuilder(Objects.nonNull(name, "name")).from(source);
+ new ConstantBindingBuilder(annotationStrategy).from(source);
constantBindingBuilders.add(builder);
return builder;
}
@@ -219,7 +236,7 @@
for (Map.Entry<String, String> entry : properties.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
- bind(key, source).to(value);
+ bind(source, Key.strategyFor(new NamedImpl(key))).to(value);
}
}
@@ -232,7 +249,7 @@
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
- bind(key, source).to(value);
+ bind(source, Key.strategyFor(new NamedImpl(key))).to(value);
}
}
@@ -495,13 +512,34 @@
}
/**
- * Sets the name of this binding.
+ * Binds to injection points annotated with {@code @Named(name)}.
*/
public BindingBuilder<T> named(String name) {
- if (!this.key.hasDefaultName()) {
- errorHandler.handle(ErrorMessages.NAME_ALREADY_SET);
+ return annotatedWith(new NamedImpl(nonNull(name, "name")));
+ }
+
+ /**
+ * Specifies the annotation type for this binding.
+ */
+ public BindingBuilder<T> annotatedWith(
+ Class<? extends Annotation> annotationType) {
+ if (this.key.hasAnnotationType()) {
+ errorHandler.handle(ErrorMessages.ANNOTATION_ALREADY_SPECIFIED);
+ } else {
+ this.key = Key.get(this.key.getType(), annotationType);
}
- this.key = this.key.named(name);
+ return this;
+ }
+
+ /**
+ * Specifies an annotation for this binding.
+ */
+ public BindingBuilder<T> annotatedWith(Annotation annotation) {
+ if (this.key.hasAnnotationType()) {
+ errorHandler.handle(ErrorMessages.ANNOTATION_ALREADY_SPECIFIED);
+ } else {
+ this.key = Key.get(this.key.getType(), annotation);
+ }
return this;
}
@@ -704,20 +742,22 @@
}
private static class BindingInfo<T> {
+
final Class<T> type;
final T value;
- final String name;
+ final AnnotationStrategy annotationStrategy;
final Object source;
- BindingInfo(Class<T> type, T value, String name, Object source) {
+ BindingInfo(Class<T> type, T value,
+ AnnotationStrategy annotationStrategy, Object source) {
this.type = type;
this.value = value;
- this.name = name;
+ this.annotationStrategy = annotationStrategy;
this.source = source;
}
Binding<T> createBinding(ContainerImpl container) {
- Key<T> key = Key.get(type, name);
+ Key<T> key = Key.get(type, annotationStrategy);
ConstantFactory<T> factory = new ConstantFactory<T>(value);
return Binding.newInstance(container, key, source, factory);
}
@@ -729,11 +769,11 @@
public class ConstantBindingBuilder {
BindingInfo<?> bindingInfo;
- final String name;
+ final AnnotationStrategy annotationStrategy;
Object source = ContainerBuilder.UNKNOWN_SOURCE;
- ConstantBindingBuilder(String name) {
- this.name = name;
+ ConstantBindingBuilder(AnnotationStrategy annotationStrategy) {
+ this.annotationStrategy = annotationStrategy;
}
boolean hasValue() {
@@ -826,8 +866,8 @@
if (this.bindingInfo != null) {
addError(source, ErrorMessages.CONSTANT_VALUE_ALREADY_SET);
} else {
- // TODO: we're sure name and source will have been set already, right?
- this.bindingInfo = new BindingInfo<T>(type, value, name, source);
+ this.bindingInfo
+ = new BindingInfo<T>(type, value, annotationStrategy, source);
}
}
diff --git a/src/com/google/inject/ContainerImpl.java b/src/com/google/inject/ContainerImpl.java
index 7f49d68..00fde7e 100644
--- a/src/com/google/inject/ContainerImpl.java
+++ b/src/com/google/inject/ContainerImpl.java
@@ -116,10 +116,10 @@
return bindingsMultimap.getAll(type);
}
- <T> List<String> getNamesOfBindingsTo(TypeLiteral<T> type) {
+ <T> List<String> getNamesOfBindingAnnotations(TypeLiteral<T> type) {
List<String> names = new ArrayList<String>();
for (Binding<T> binding : findBindingsByType(type)) {
- names.add(binding.getKey().getName());
+ names.add(binding.getKey().getAnnotationName());
}
return names;
}
@@ -160,8 +160,7 @@
= ((ParameterizedType) factoryType).getActualTypeArguments()[0];
try {
- final Factory<?> factory
- = getFactory(Key.get(entryType, key.getName()));
+ final Factory<?> factory = getFactory(key.ofType(entryType));
return new InternalFactory<T>() {
@SuppressWarnings("unchecked")
public T get(InternalContext context) {
@@ -171,7 +170,7 @@
}
catch (ConfigurationException e) {
ErrorMessages.handleMissingBinding(errorHandler, member, key,
- getNamesOfBindingsTo(key.getType()));
+ getNamesOfBindingAnnotations(key.getType()));
return invalidFactory();
}
}
@@ -181,7 +180,7 @@
= PRIMITIVE_COUNTERPARTS.get(rawType);
if (primitiveCounterpart != null) {
Binding<?> counterpartBinding
- = getBinding(Key.get(primitiveCounterpart, key.getName()));
+ = getBinding(key.ofType(primitiveCounterpart));
if (counterpartBinding != null) {
return (InternalFactory<? extends T>)
counterpartBinding.getInternalFactory();
@@ -189,8 +188,8 @@
}
// Can we convert from a String constant?
- Binding<String> stringBinding
- = getBinding(Key.get(String.class, key.getName()));
+ Key<String> stringKey = key.ofType(String.class);
+ Binding<String> stringBinding = getBinding(stringKey);
if (stringBinding != null && stringBinding.isConstant()) {
// We don't need do pass in an InternalContext because we know this is
// a ConstantFactory which will not use it.
@@ -313,9 +312,9 @@
List<Injector> injectors) {
addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
new InjectorFactory<Method>() {
- public Injector create(ContainerImpl container, Method method,
- String name) throws MissingDependencyException {
- return new MethodInjector(container, method, name);
+ public Injector create(ContainerImpl container, Method method)
+ throws MissingDependencyException {
+ return new MethodInjector(container, method);
}
});
}
@@ -324,9 +323,9 @@
List<Injector> injectors) {
addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
new InjectorFactory<Field>() {
- public Injector create(ContainerImpl container, Field field,
- String name) throws MissingDependencyException {
- return new FieldInjector(container, field, name);
+ public Injector create(ContainerImpl container, Field field)
+ throws MissingDependencyException {
+ return new FieldInjector(container, field);
}
});
}
@@ -347,10 +346,10 @@
if (inject != null) {
try {
- injectors.add(injectorFactory.create(this, member, inject.value()));
+ injectors.add(injectorFactory.create(this, member));
}
catch (MissingDependencyException e) {
- if (inject.required()) {
+ if (!inject.optional()) {
// TODO: Report errors for more than one parameter per member.
e.handle(errorHandler);
}
@@ -375,7 +374,7 @@
}
interface InjectorFactory<M extends Member & AnnotatedElement> {
- Injector create(ContainerImpl container, M member, String name)
+ Injector create(ContainerImpl container, M member)
throws MissingDependencyException;
}
@@ -415,14 +414,15 @@
final InternalFactory<?> factory;
final ExternalContext<?> externalContext;
- public FieldInjector(ContainerImpl container, Field field, String name)
+ public FieldInjector(ContainerImpl container, Field field)
throws MissingDependencyException {
this.field = field;
// Ewwwww...
field.setAccessible(true);
- Key<?> key = Key.get(field.getGenericType(), name);
+ Key<?> key = Key.get(
+ field.getGenericType(), field, field.getAnnotations(), errorHandler);
factory = container.getFactory(field, key);
if (factory == null) {
throw new MissingDependencyException(key, field);
@@ -435,7 +435,8 @@
ExternalContext<?> previous = context.getExternalContext();
context.setExternalContext(externalContext);
try {
- field.set(o, factory.get(context));
+ Object value = factory.get(context);
+ field.set(o, value);
}
catch (IllegalAccessException e) {
throw new AssertionError(e);
@@ -456,17 +457,8 @@
*/
<M extends AccessibleObject & Member>
ParameterInjector<?>[] getParametersInjectors(M member,
- Annotation[][] annotations, Type[] parameterTypes, String defaultName)
+ Annotation[][] annotations, Type[] parameterTypes)
throws MissingDependencyException {
- boolean defaultNameOverridden = !defaultName.equals(Key.DEFAULT_NAME);
-
- // We only carry over the name from the member level annotation to the
- // parameters if there's only one parameter.
- if (parameterTypes.length != 1 && defaultNameOverridden) {
- errorHandler.handle(
- ErrorMessages.NAME_ON_MEMBER_WITH_MULTIPLE_PARAMS, member);
- }
-
ParameterInjector<?>[] parameterInjectors
= new ParameterInjector<?>[parameterTypes.length];
Iterator<Annotation[]> annotationsIterator
@@ -474,29 +466,8 @@
int index = 0;
for (Type parameterType : parameterTypes) {
Annotation[] parameterAnnotations = annotationsIterator.next();
- Inject inject = null;
- try {
- inject = SurrogateAnnotations.findAnnotation(Inject.class,
- parameterAnnotations);
- } catch (DuplicateAnnotationException e) {
- errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS,
- Inject.class.getSimpleName(), member, e.getFirst(),
- e.getSecond());
- }
-
- String name;
- if (defaultNameOverridden) {
- name = defaultName;
- if (inject != null) {
- errorHandler.handle(
- ErrorMessages.NAME_ON_MEMBER_AND_PARAMETER, member);
- }
- }
- else {
- name = inject == null ? defaultName : inject.value();
- }
-
- Key<?> key = Key.get(parameterType, name);
+ Key<?> key = Key.get(
+ parameterType, member, parameterAnnotations, errorHandler);
parameterInjectors[index] = createParameterInjector(key, member, index);
index++;
}
@@ -521,14 +492,14 @@
final FastMethod fastMethod;
final ParameterInjector<?>[] parameterInjectors;
- public MethodInjector(ContainerImpl container, Method method, String name)
+ public MethodInjector(ContainerImpl container, Method method)
throws MissingDependencyException {
FastClass fastClass = GuiceFastClass.create(method.getDeclaringClass());
this.fastMethod = fastClass.getMethod(method);
Type[] parameterTypes = method.getGenericParameterTypes();
parameterInjectors = parameterTypes.length > 0
? container.getParametersInjectors(
- method, method.getParameterAnnotations(), parameterTypes, name)
+ method, method.getParameterAnnotations(), parameterTypes)
: null;
}
@@ -632,20 +603,44 @@
});
}
- public <T> T getInstance(TypeLiteral<T> type, String name) {
- return getFactory(Key.get(type, name)).get();
+ public <T> T getInstance(TypeLiteral<T> type,
+ Annotation annotation) {
+ return getFactory(Key.get(type, annotation)).get();
}
- public <T> T getInstance(Class<T> type, String name) {
- return getFactory(Key.get(type, name)).get();
+ public <T> T getInstance(Class<T> type,
+ Annotation annotation) {
+ return getFactory(Key.get(type, annotation)).get();
}
- public <T> Factory<T> getFactory(Class<T> type, String name) {
- return getFactory(Key.get(type, name));
+ public <T> Factory<T> getFactory(Class<T> type,
+ Annotation annotation) {
+ return getFactory(Key.get(type, annotation));
}
- public <T> Factory<T> getFactory(TypeLiteral<T> type, String name) {
- return getFactory(Key.get(type, name));
+ public <T> Factory<T> getFactory(TypeLiteral<T> type,
+ Annotation annotation) {
+ return getFactory(Key.get(type, annotation));
+ }
+
+ public <T> T getInstance(TypeLiteral<T> type,
+ Class<? extends Annotation> annotationType) {
+ return getFactory(Key.get(type, annotationType)).get();
+ }
+
+ public <T> T getInstance(Class<T> type,
+ Class<? extends Annotation> annotationType) {
+ return getFactory(Key.get(type, annotationType)).get();
+ }
+
+ public <T> Factory<T> getFactory(Class<T> type,
+ Class<? extends Annotation> annotationType) {
+ return getFactory(Key.get(type, annotationType));
+ }
+
+ public <T> Factory<T> getFactory(TypeLiteral<T> type,
+ Class<? extends Annotation> annotationType) {
+ return getFactory(Key.get(type, annotationType));
}
public <T> T getInstance(TypeLiteral<T> type) {
@@ -756,7 +751,7 @@
void handle(ErrorHandler errorHandler) {
ErrorMessages.handleMissingBinding(errorHandler, member, key,
- getNamesOfBindingsTo(key.getType()));
+ getNamesOfBindingAnnotations(key.getType()));
}
}
diff --git a/src/com/google/inject/ErrorMessages.java b/src/com/google/inject/ErrorMessages.java
index 90ddd7c..6454472 100644
--- a/src/com/google/inject/ErrorMessages.java
+++ b/src/com/google/inject/ErrorMessages.java
@@ -36,8 +36,8 @@
+ " type were found.";
private static final String MISSING_BINDING_BUT_OTHERS_EXIST =
- "Binding to %s not found, but %s requires it. Names of other"
- + " bindings to that type: %s";
+ "Binding to %s not found, but %s requires it. Annotations on other"
+ + " bindings to that type include: %s";
static void handleMissingBinding(ErrorHandler errorHandler, Member member,
Key<?> key, List<String> otherNames) {
@@ -76,7 +76,8 @@
static final String CANNOT_INJECT_INTERFACE = "Injecting into interfaces is"
+ " not supported. Please use a concrete type instead of %s.";
- static final String NAME_ALREADY_SET = "Binding name is set more than once.";
+ static final String ANNOTATION_ALREADY_SPECIFIED =
+ "More than one annotation type is specified for this binding.";
static final String IMPLEMENTATION_ALREADY_SET = "Implementation is set more"
+ " than once.";
@@ -90,7 +91,7 @@
+ " implementation with with an annotation which is annotated with"
+ " @Scoped.";
- static final String DUPLICATE_ANNOTATIONS = "Duplicate @%s annotations found"
+ static final String DUPLICATE_ANNOTATIONS = "Duplicate binding annotations found"
+ " on %s: %s and %s";
static final String CONSTANT_VALUE_ALREADY_SET = "Constant value is set more"
@@ -147,9 +148,9 @@
},
new Converter<Key>(Key.class) {
public String toString(Key k) {
- return k.hasDefaultName()
- ? k.getType().toString()
- : k.getType() + " named '" + k.getName() + "'";
+ return k.hasAnnotationType()
+ ? k.getType() + " annotated with " + k.getAnnotationName()
+ : k.getType().toString();
}
}
);
diff --git a/src/com/google/inject/ForBinding.java b/src/com/google/inject/ForBinding.java
new file mode 100644
index 0000000..637d541
--- /dev/null
+++ b/src/com/google/inject/ForBinding.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (C) 2006 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;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotates annotations which are used for binding.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@Target({ ElementType.ANNOTATION_TYPE })
+@Retention(RUNTIME)
+public @interface ForBinding {}
diff --git a/src/com/google/inject/Inject.java b/src/com/google/inject/Inject.java
index 10e4f7c..7fa96c9 100644
--- a/src/com/google/inject/Inject.java
+++ b/src/com/google/inject/Inject.java
@@ -20,31 +20,28 @@
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
/**
- * <p>Annotates members and parameters which should have their value[s]
- * injected. If applied to another annotation type, that annotation type
- * can act as a surrogate and save you from having to repeat the dependency
- * name over and over.
+ * <p>Annotates members which should have their value[s] injected.
+ *
+ * <p>Also applies to other annotations which can be used with {@link Key}s and
+ * at injection points.
*
* @author crazybob@google.com (Bob Lee)
*/
-@Target({ METHOD, CONSTRUCTOR, FIELD, PARAMETER, ANNOTATION_TYPE })
+@Target({ METHOD, CONSTRUCTOR, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Inject {
/**
- * Dependency name. Defaults to {@link Key#DEFAULT_NAME}.
+ * Indicates whether injection at the target is optional or not. The default
+ * is {@code false}. Can be used on methods and fields. If a method has
+ * multiple parameters and one parameter binding is missing, the method
+ * won't be invoked at all. Not applicable to constructors or other
+ * annotations.
*/
- String value() default Key.DEFAULT_NAME;
-
- /**
- * Whether or not injection is required. Applicable only to methods and fields
- * (not constructors or parameters).
- */
- boolean required() default true;
+ boolean optional() default false;
}
diff --git a/src/com/google/inject/Key.java b/src/com/google/inject/Key.java
index bc40e75..afcd968 100644
--- a/src/com/google/inject/Key.java
+++ b/src/com/google/inject/Key.java
@@ -17,31 +17,34 @@
package com.google.inject;
import static com.google.inject.util.Objects.nonNull;
+import com.google.inject.util.ToStringBuilder;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
import java.lang.reflect.Type;
/**
- * Binding key consisting of a type and a name. Matches the type and name
- * ({@link Inject#value()}) at a point of injection.
+ * Binding key consisting of an injection type and an optional annotation.
+ * Matches the type and annotation at a point of injection.
*
- * <p>For example, {@code new Key<List<String>>("cities") {}} will match:
+ * <p>For example, {@code Key.get(Service.class, Transactional.class) {}} will
+ * match:
*
* <pre>
- * {@literal @}Inject("cities")
- * public void setList(List<String> cities) {
+ * {@literal @}Inject
+ * public void setService({@literal @}Transactional Service cities) {
* ...
* }
* </pre>
*
+ * <p>{@code Key} supports generic types via subclassing just like {@link
+ * TypeLiteral}.
+ *
* @author crazybob@google.com (Bob Lee)
*/
public abstract class Key<T> {
- /**
- * Default binding name.
- */
- public static final String DEFAULT_NAME = "default";
+ final AnnotationStrategy annotationStrategy;
- final String name;
final TypeLiteral<T> typeLiteral;
final int hashCode;
@@ -52,58 +55,98 @@
* parameter in the anonymous class's type hierarchy so we can reconstitute it
* at runtime despite erasure.
*
- * <p>Example usage for a binding of type {@code Foo} named "bar":
- * {@code new Key<Foo>("bar") {}}.
+ * <p>Example usage for a binding of type {@code Foo} annotated with
+ * {@code @Bar}:
+ *
+ * <p>{@code new Key<Foo>(Bar.class) {}}.
*/
@SuppressWarnings("unchecked")
- protected Key(String name) {
- this.name = nonNull(name, "name");
+ protected Key(Class<? extends Annotation> annotationType) {
+ this.annotationStrategy = strategyFor(annotationType);
this.typeLiteral
= (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
this.hashCode = computeHashCode();
}
/**
- * Convenience method. Delegates to {@link #Key(String)} with
- * {@link #DEFAULT_NAME}.
+ * Constructs a new key. Derives the type from this class's type parameter.
+ *
+ * <p>Clients create an empty anonymous subclass. Doing so embeds the type
+ * parameter in the anonymous class's type hierarchy so we can reconstitute it
+ * at runtime despite erasure.
+ *
+ * <p>Example usage for a binding of type {@code Foo} annotated with
+ * {@code @Bar}:
+ *
+ * <p>{@code new Key<Foo>(new Bar()) {}}.
*/
+ @SuppressWarnings("unchecked")
+ protected Key(Annotation annotation) {
+ this.annotationStrategy = strategyFor(annotation);
+ this.typeLiteral
+ = (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
+ this.hashCode = computeHashCode();
+ }
+
+ /**
+ * Constructs a new key. Derives the type from this class's type parameter.
+ *
+ * <p>Clients create an empty anonymous subclass. Doing so embeds the type
+ * parameter in the anonymous class's type hierarchy so we can reconstitute it
+ * at runtime despite erasure.
+ *
+ * <p>Example usage for a binding of type {@code Foo} annotated with
+ * {@code @Named("bar")}:
+ *
+ * <p>{@code new Key<Foo>("bar") {}}.
+ */
+ @SuppressWarnings("unchecked")
+ protected Key(String name) {
+ this.annotationStrategy = strategyFor(new NamedImpl(name));
+ this.typeLiteral
+ = (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
+ this.hashCode = computeHashCode();
+ }
+
+ /**
+ * Constructs a new key. Derives the type from this class's type parameter.
+ *
+ * <p>Clients create an empty anonymous subclass. Doing so embeds the type
+ * parameter in the anonymous class's type hierarchy so we can reconstitute it
+ * at runtime despite erasure.
+ *
+ * <p>Example usage for a binding of type {@code Foo}:
+ *
+ * <p>{@code new Key<Foo>() {}}.
+ */
+ @SuppressWarnings("unchecked")
protected Key() {
- this(DEFAULT_NAME);
+ this.annotationStrategy = NULL_STRATEGY;
+ this.typeLiteral
+ = (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
+ this.hashCode = computeHashCode();
}
/**
* Unsafe. Constructs a key from a manually specified type.
*/
@SuppressWarnings("unchecked")
- private Key(Type type, String name) {
- this.name = nonNull(name, "name");
+ private Key(Type type, AnnotationStrategy annotationStrategy) {
+ this.annotationStrategy = annotationStrategy;
this.typeLiteral = (TypeLiteral<T>) TypeLiteral.get(type);
this.hashCode = computeHashCode();
}
/** Constructs a key from a manually specified type. */
- private Key(TypeLiteral<T> typeLiteral, String name) {
- this.name = nonNull(name, "name");
+ private Key(TypeLiteral<T> typeLiteral,
+ AnnotationStrategy annotationStrategy) {
+ this.annotationStrategy = annotationStrategy;
this.typeLiteral = typeLiteral;
this.hashCode = computeHashCode();
}
private int computeHashCode() {
- return typeLiteral.hashCode() * 31 + name.hashCode();
- }
-
- /**
- * Returns {@code true} if this key has the default name.
- */
- public boolean hasDefaultName() {
- return DEFAULT_NAME.equals(this.name);
- }
-
- /**
- * Returns a new key with the same type as this key and the given name,
- */
- Key<T> named(String name) {
- return new SimpleKey<T>(this.typeLiteral, name);
+ return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode();
}
/**
@@ -114,10 +157,23 @@
}
/**
- * Gets the binding name.
+ * Gets the annotation type.
*/
- public String getName() {
- return name;
+ public Class<? extends Annotation> getAnnotationType() {
+ return annotationStrategy.getAnnotationType();
+ }
+
+ boolean hasAnnotationType() {
+ return annotationStrategy.getAnnotationType() != null;
+ }
+
+ String getAnnotationName() {
+ Annotation annotation = annotationStrategy.getAnnotation();
+ if (annotation != null) {
+ return annotation.toString();
+ }
+
+ return annotationStrategy.getAnnotationType().toString();
}
public int hashCode() {
@@ -136,64 +192,299 @@
return false;
}
Key<?> other = (Key<?>) o;
- return name.equals(other.name) && typeLiteral.equals(other.typeLiteral);
+ return annotationStrategy.equals(other.annotationStrategy)
+ && typeLiteral.equals(other.typeLiteral);
}
public String toString() {
- return Key.class.getSimpleName()
- + "[type=" + typeLiteral + ", name='" + name + "']";
+ return new ToStringBuilder(Key.class)
+ .add("type", typeLiteral)
+ .add("annotation", annotationStrategy)
+ .toString();
}
/**
- * Gets a key for a {@code Class}. Defaults name to {@link #DEFAULT_NAME}.
+ * Gets a key for an injection type and an annotation strategy.
+ */
+ static <T> Key<T> get(Class<T> type,
+ AnnotationStrategy annotationStrategy) {
+ return new SimpleKey<T>(type, annotationStrategy);
+ }
+
+ /**
+ * Gets a key for an injection type.
*/
public static <T> Key<T> get(Class<T> type) {
- return new SimpleKey<T>(type, DEFAULT_NAME);
+ return new SimpleKey<T>(type, NULL_STRATEGY);
}
/**
- * Gets a key for a {@code Class} and a name.
+ * Gets a key for an injection type and an annotation type.
+ */
+ public static <T> Key<T> get(Class<T> type,
+ Class<? extends Annotation> annotationType) {
+ return new SimpleKey<T>(type, strategyFor(annotationType));
+ }
+
+ /**
+ * Gets a key for an injection type and the annotation {@code Named(name)}.
*/
public static <T> Key<T> get(Class<T> type, String name) {
- return new SimpleKey<T>(type, name);
+ return new SimpleKey<T>(type, strategyFor(new NamedImpl(name)));
}
/**
- * Gets a key for a type. Defaults name to {@link #DEFAULT_NAME}.
+ * Gets a key for an injection type and an annotation.
+ */
+ public static <T> Key<T> get(Class<T> type, Annotation annotation) {
+ return new SimpleKey<T>(type, strategyFor(annotation));
+ }
+
+ /**
+ * Gets a key for an injection type.
*/
public static Key<?> get(Type type) {
- return new SimpleKey<Object>(type, DEFAULT_NAME);
+ return new SimpleKey<Object>(type, NULL_STRATEGY);
}
/**
- * Gets a key for a type and a name.
+ * Gets a key for an injection type and an annotation type.
+ */
+ public static Key<?> get(Type type,
+ Class<? extends Annotation> annotationType) {
+ return new SimpleKey<Object>(type, strategyFor(annotationType));
+ }
+
+ /**
+ * Gets a key for an injection type and an annotation.
+ */
+ public static Key<?> get(Type type, Annotation annotation) {
+ return new SimpleKey<Object>(type, strategyFor(annotation));
+ }
+
+ /**
+ * Gets a key for an injection type and the annotation {@code Named(name)}.
*/
public static Key<?> get(Type type, String name) {
- return new SimpleKey<Object>(type, name);
+ return new SimpleKey<Object>(type, strategyFor(new NamedImpl(name)));
}
/**
- * Gets a key for a type. Defaults name to {@link #DEFAULT_NAME}.
+ * Gets a key for an injection type.
*/
public static <T> Key<T> get(TypeLiteral<T> typeLiteral) {
- return new SimpleKey<T>(typeLiteral, DEFAULT_NAME);
+ return new SimpleKey<T>(typeLiteral, NULL_STRATEGY);
}
/**
- * Gets key for a type and a name.
+ * Gets a key for an injection type and an annotation type.
+ */
+ public static <T> Key<T> get(TypeLiteral<T> typeLiteral,
+ Class<? extends Annotation> annotationType) {
+ return new SimpleKey<T>(typeLiteral, strategyFor(annotationType));
+ }
+
+ /**
+ * Gets a key for an injection type and an annotation.
+ */
+ public static <T> Key<T> get(TypeLiteral<T> typeLiteral,
+ Annotation annotation) {
+ return new SimpleKey<T>(typeLiteral, strategyFor(annotation));
+ }
+
+ /**
+ * Gets a key for an injection type and the annotation {@code Named(name)}.
*/
public static <T> Key<T> get(TypeLiteral<T> typeLiteral, String name) {
- return new SimpleKey<T>(typeLiteral, name);
+ return new SimpleKey<T>(typeLiteral, strategyFor(new NamedImpl(name)));
+ }
+
+ /**
+ * Gets a key for the given type, member and annotations.
+ */
+ static Key<?> get(Type type, Member member, Annotation[] annotations,
+ ErrorHandler errorHandler) {
+ Annotation found = null;
+ for (Annotation annotation : annotations) {
+ if (annotation.annotationType().getAnnotation(ForBinding.class) != null) {
+ if (found == null) {
+ found = annotation;
+ } else {
+ errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS, member,
+ found, annotation);
+ }
+ }
+ }
+ Key<?> key = found == null ? Key.get(type) : Key.get(type, found);
+ return key;
+ }
+
+ /**
+ * Returns a new key of the specified type with the same annotation as this
+ * key.
+ */
+ public <T> Key<T> ofType(Class<T> type) {
+ return new SimpleKey<T>(type, annotationStrategy);
+ }
+
+ /**
+ * Returns a new key of the specified type with the same annotation as this
+ * key.
+ */
+ public Key<?> ofType(Type type) {
+ return new SimpleKey<Object>(type, annotationStrategy);
+ }
+
+ /**
+ * Returns a new key of the specified type with the same annotation as this
+ * key.
+ */
+ public <T> Key<T> ofType(TypeLiteral<T> type) {
+ return new SimpleKey<T>(type, annotationStrategy);
}
private static class SimpleKey<T> extends Key<T> {
- private SimpleKey(Type type, String name) {
- super(type, name);
+ private SimpleKey(Type type, AnnotationStrategy annotationStrategy) {
+ super(type, annotationStrategy);
}
- private SimpleKey(TypeLiteral<T> typeLiteral, String name) {
- super(typeLiteral, name);
+ private SimpleKey(TypeLiteral<T> typeLiteral,
+ AnnotationStrategy annotationStrategy) {
+ super(typeLiteral, annotationStrategy);
+ }
+ }
+
+ interface AnnotationStrategy {
+
+ Annotation getAnnotation();
+ Class<? extends Annotation> getAnnotationType();
+ }
+
+ static final AnnotationStrategy NULL_STRATEGY = new AnnotationStrategy() {
+
+ public Annotation getAnnotation() {
+ return null;
+ }
+
+ public Class<? extends Annotation> getAnnotationType() {
+ return null;
+ }
+
+ public boolean equals(Object o) {
+ return o == NULL_STRATEGY;
+ }
+
+ public int hashCode() {
+ return 0;
+ }
+
+ public String toString() {
+ return "[none]";
+ }
+ };
+
+ /**
+ * Returns {@code true} if the given annotation type has no attributes.
+ */
+ static boolean isMarker(Class<? extends Annotation> annotationType) {
+ return annotationType.getDeclaredMethods().length == 0;
+ }
+
+ /**
+ * Gets the strategy for an annotation.
+ */
+ static AnnotationStrategy strategyFor(Annotation annotation) {
+ nonNull(annotation, "annotation");
+ return isMarker(annotation.annotationType())
+ ? new AnnotationTypeStrategy(annotation.annotationType(), annotation)
+ : new AnnotationInstanceStrategy(annotation);
+ }
+
+ /**
+ * Gets the strategy for an annotation type.
+ */
+ static AnnotationStrategy strategyFor(
+ Class<? extends Annotation> annotationType) {
+ nonNull(annotationType, "annotation type");
+ if (!isMarker(annotationType)) {
+ throw new IllegalArgumentException(annotationType.getName()
+ + " is not a marker annotation, i.e. it has attributes. Please"
+ + " use an Annotation instance or a marker annotation instead.");
+ }
+ return new AnnotationTypeStrategy(annotationType, null);
+ }
+
+ static class AnnotationInstanceStrategy implements AnnotationStrategy {
+
+ final Annotation annotation;
+
+ AnnotationInstanceStrategy(Annotation annotation) {
+ this.annotation = nonNull(annotation, "annotation");
+ }
+
+ public Annotation getAnnotation() {
+ return annotation;
+ }
+
+ public Class<? extends Annotation> getAnnotationType() {
+ return annotation.annotationType();
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof AnnotationInstanceStrategy)) {
+ return false;
+ }
+
+ AnnotationInstanceStrategy other = (AnnotationInstanceStrategy) o;
+ return annotation.equals(other.annotation);
+ }
+
+ public int hashCode() {
+ return annotation.hashCode();
+ }
+
+ public String toString() {
+ return annotation.toString();
+ }
+ }
+
+ static class AnnotationTypeStrategy implements AnnotationStrategy {
+
+ final Class<? extends Annotation> annotationType;
+
+ // Keep the instance around if we have it so the client can request it.
+ final Annotation annotation;
+
+ AnnotationTypeStrategy(Class<? extends Annotation> annotationType,
+ Annotation annotation) {
+ this.annotationType = nonNull(annotationType, "annotation type");
+ this.annotation = annotation;
+ }
+
+ public Annotation getAnnotation() {
+ return annotation;
+ }
+
+ public Class<? extends Annotation> getAnnotationType() {
+ return annotationType;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof AnnotationTypeStrategy)) {
+ return false;
+ }
+
+ AnnotationTypeStrategy other = (AnnotationTypeStrategy) o;
+ return annotationType.equals(other.annotationType);
+ }
+
+ public int hashCode() {
+ return annotationType.hashCode();
+ }
+
+ public String toString() {
+ return annotationType.toString();
}
}
}
diff --git a/src/com/google/inject/Named.java b/src/com/google/inject/Named.java
new file mode 100644
index 0000000..0a0bd8d
--- /dev/null
+++ b/src/com/google/inject/Named.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (C) 2006 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;
+
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotates named things.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@Retention(RUNTIME)
+@ForBinding
+public @interface Named {
+ String value();
+}
diff --git a/src/com/google/inject/NamedImpl.java b/src/com/google/inject/NamedImpl.java
new file mode 100644
index 0000000..c8be943
--- /dev/null
+++ b/src/com/google/inject/NamedImpl.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (C) 2006 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;
+
+import java.lang.annotation.Annotation;
+import com.google.inject.util.Objects;
+
+class NamedImpl implements Named {
+
+ final String value;
+
+ public NamedImpl(String value) {
+ this.value = Objects.nonNull(value, "name");
+ }
+
+ public String value() {
+ return this.value;
+ }
+
+ public int hashCode() {
+ // This is specified in java.lang.Annotation.
+ return 127 * "value".hashCode() ^ value.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof Named)) {
+ return false;
+ }
+
+ Named other = (Named) o;
+ return value.equals(other.value());
+ }
+
+ public String toString() {
+ return "@Named(\"" + value + "\")";
+ }
+
+ public Class<? extends Annotation> annotationType() {
+ return Named.class;
+ }
+}
diff --git a/src/com/google/inject/util/Objects.java b/src/com/google/inject/util/Objects.java
index 15fbf3f..59b3d25 100644
--- a/src/com/google/inject/util/Objects.java
+++ b/src/com/google/inject/util/Objects.java
@@ -36,4 +36,19 @@
}
return t;
}
+
+ /**
+ * {@code null}-aware equals.
+ */
+ public static boolean equal(Object a, Object b) {
+ if (a == b) {
+ return true;
+ }
+
+ if (a == null || b == null) {
+ return false;
+ }
+
+ return a.equals(b);
+ }
}