Added support for @DefaultImplementation and @DefaulProvider.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@251 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/BindingBuilderImpl.java b/src/com/google/inject/BindingBuilderImpl.java
index e760af5..7299d59 100644
--- a/src/com/google/inject/BindingBuilderImpl.java
+++ b/src/com/google/inject/BindingBuilderImpl.java
@@ -245,40 +245,6 @@
return this.scope == Scopes.SINGLETON;
}
- /**
- * Delegates to a custom factory which is also bound in the injector.
- */
- private static class BoundProviderFactory<T>
- implements InternalFactory<T>, CreationListener {
-
- final Key<? extends Provider<? extends T>> providerKey;
- final Object source;
- private InternalFactory<? extends Provider<? extends T>> providerFactory;
-
- public BoundProviderFactory(
- Key<? extends Provider<? extends T>> providerKey,
- Object source) {
- this.providerKey = providerKey;
- this.source = source;
- }
-
- public void notify(final InjectorImpl injector) {
- injector.withDefaultSource(source, new Runnable() {
- public void run() {
- providerFactory = injector.getInternalFactory(null, providerKey);
- }
- });
- }
-
- public String toString() {
- return providerKey.toString();
- }
-
- public T get(InternalContext context) {
- return providerFactory.get(context).get();
- }
- }
-
void registerInstanceForInjection(final Object o) {
binder.creationListeners.add(new CreationListener() {
public void notify(InjectorImpl injector) {
diff --git a/src/com/google/inject/BoundProviderFactory.java b/src/com/google/inject/BoundProviderFactory.java
new file mode 100644
index 0000000..aa89f98
--- /dev/null
+++ b/src/com/google/inject/BoundProviderFactory.java
@@ -0,0 +1,62 @@
+/**
+ * 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 com.google.inject.BinderImpl.CreationListener;
+
+/**
+ * Delegates to a custom factory which is also bound in the injector.
+ */
+class BoundProviderFactory<T>
+ implements InternalFactory<T>, CreationListener {
+
+ final Key<? extends Provider<? extends T>> providerKey;
+ final Object source;
+ private InternalFactory<? extends Provider<? extends T>> providerFactory;
+
+ BoundProviderFactory(
+ Key<? extends Provider<? extends T>> providerKey,
+ Object source) {
+ this.providerKey = providerKey;
+ this.source = source;
+ }
+
+ BoundProviderFactory(
+ Key<? extends Provider<? extends T>> providerKey,
+ InternalFactory<? extends Provider<? extends T>> providerFactory,
+ Object source) {
+ this.providerKey = providerKey;
+ this.providerFactory = providerFactory;
+ this.source = source;
+ }
+
+ public void notify(final InjectorImpl injector) {
+ injector.withDefaultSource(source, new Runnable() {
+ public void run() {
+ providerFactory = injector.getInternalFactory(null, providerKey);
+ }
+ });
+ }
+
+ public String toString() {
+ return providerKey.toString();
+ }
+
+ public T get(InternalContext context) {
+ return providerFactory.get(context).get();
+ }
+}
diff --git a/src/com/google/inject/DefaultImplementation.java b/src/com/google/inject/DefaultImplementation.java
index 7191bf7..31bea9c 100644
--- a/src/com/google/inject/DefaultImplementation.java
+++ b/src/com/google/inject/DefaultImplementation.java
@@ -16,11 +16,18 @@
package com.google.inject;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
/**
* A pointer to the default implementation of a type.
*
* @author crazybob@google.com (Bob Lee)
*/
+@Retention(RUNTIME)
+@Target(TYPE)
public @interface DefaultImplementation {
/**
diff --git a/src/com/google/inject/DefaultProvider.java b/src/com/google/inject/DefaultProvider.java
index 7128ed2..7d72b5f 100644
--- a/src/com/google/inject/DefaultProvider.java
+++ b/src/com/google/inject/DefaultProvider.java
@@ -16,15 +16,22 @@
package com.google.inject;
+import static java.lang.annotation.ElementType.TYPE;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
/**
- * A pointer to the default provider implementation for a type.
+ * A pointer to the default provider for a type.
*
* @author crazybob@google.com (Bob Lee)
*/
+@Retention(RUNTIME)
+@Target(TYPE)
public @interface DefaultProvider {
/**
* The implementation type.
*/
- Class<?> value();
+ Class<? extends Provider<?>> value();
}
diff --git a/src/com/google/inject/ErrorMessages.java b/src/com/google/inject/ErrorMessages.java
index 6c6e229..1bfd9e0 100644
--- a/src/com/google/inject/ErrorMessages.java
+++ b/src/com/google/inject/ErrorMessages.java
@@ -52,6 +52,17 @@
}
}
+ static final String SUBTYPE_NOT_PROVIDED
+ = "%s doesn't provide instances of %s.";
+
+ static final String NOT_A_SUBTYPE = "%s doesn't extend %s.";
+
+ static final String RECURSIVE_IMPLEMENTATION_TYPE = "@DefaultImplementation"
+ + " points to the same class it annotates.";
+
+ static final String RECURSIVE_PROVIDER_TYPE = "@DefaultProvider"
+ + " points to the same class it annotates.";
+
static final String ERROR_INJECTING_MEMBERS = "An error of type %s occurred"
+ " while injecting members of %s. Error message: %s";
@@ -109,8 +120,9 @@
static final String LINK_DESTINATION_NOT_FOUND = "Binding to %s not found.";
- static final String CANNOT_INJECT_INTERFACE = "Injecting into interfaces is"
- + " not supported. Please use a concrete type instead of %s.";
+ static final String CANNOT_INJECT_ABSTRACT_TYPE =
+ "Injecting into abstract types is not supported. Please use a concrete"
+ + " type instead of %s.";
static final String ANNOTATION_ALREADY_SPECIFIED =
"More than one annotation type is specified for this binding.";
@@ -142,7 +154,7 @@
+ " instance%n bound to %s%n for member at %s";
static final String NULL_PROVIDED = "Null value returned by custom provider"
- + " at %s";
+ + " bound at %s";
static Object convert(Object o) {
for (Converter<?> converter : converters) {
diff --git a/src/com/google/inject/Guice.java b/src/com/google/inject/Guice.java
index f06e5e3..f221605 100644
--- a/src/com/google/inject/Guice.java
+++ b/src/com/google/inject/Guice.java
@@ -27,6 +27,20 @@
private Guice() {}
/**
+ * Creates an injector with no explicit bindings.
+ */
+ public static Injector createInjector() {
+ try {
+ return createInjector(new Module() {
+ public void configure(Binder binder) {}
+ });
+ }
+ catch (CreationException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
* Creates an injector for the given set of modules.
*/
public static Injector createInjector(Module... modules)
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index 2f0ef89..cc04d95 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -261,11 +261,9 @@
}
}
- // Don't try to inject primitives, arrays, enums, interfaces or abstract
- // classes.
+ // Don't try to inject primitives, arrays, or enums.
int modifiers = rawType.getModifiers();
- if (rawType.isArray() || rawType.isEnum()
- || Modifier.isAbstract(modifiers) || rawType.isPrimitive()) {
+ if (rawType.isArray() || rawType.isEnum() || rawType.isPrimitive()) {
// Look for a factory bound to a key without annotation attributes if
// necessary.
if (key.hasAttributes()) {
@@ -299,14 +297,15 @@
};
try {
// note: intelliJ thinks this cast is superfluous, but is it?
- return (InternalFactory<? extends T>) getImplicitBinding(rawType);
+ return (InternalFactory<? extends T>) getImplicitBinding(member,
+ rawType);
}
finally {
this.errorHandler = previous;
}
}
// note: intelliJ thinks this cast is superfluous, but is it?
- return (InternalFactory<? extends T>) getImplicitBinding(rawType);
+ return (InternalFactory<? extends T>) getImplicitBinding(member, rawType);
}
private <T> InternalFactory<T> handleConstantConversionError(
@@ -594,7 +593,7 @@
protected ConstructorInjector<?> create(Class<?> implementation) {
if (implementation.isInterface()) {
errorHandler.handle(defaultSource,
- ErrorMessages.CANNOT_INJECT_INTERFACE, implementation);
+ ErrorMessages.CANNOT_INJECT_ABSTRACT_TYPE, implementation);
return ConstructorInjector.invalidConstructor();
}
@@ -873,7 +872,72 @@
* was not made. Uses synchronization here so it's not necessary in the
* factory itself. Returns {@code null} if the type isn't injectable.
*/
- <T> InternalFactory<? extends T> getImplicitBinding(Class<T> type) {
+ <T> InternalFactory<? extends T> getImplicitBinding(Member member,
+ final Class<T> type) {
+ // Look for @DefaultImplementation.
+ DefaultImplementation defaultImplementation =
+ type.getAnnotation(DefaultImplementation.class);
+ if (defaultImplementation != null) {
+ Class<?> implementationType = defaultImplementation.value();
+
+ // Make sure it's not the same type. TODO: Can we check for deeper loops?
+ if (implementationType == type) {
+ errorHandler.handle(StackTraceElements.forType(type),
+ ErrorMessages.RECURSIVE_IMPLEMENTATION_TYPE, type);
+ return invalidFactory();
+ }
+
+ // Make sure implementationType extends type.
+ if (!type.isAssignableFrom(implementationType)) {
+ errorHandler.handle(StackTraceElements.forType(type),
+ ErrorMessages.NOT_A_SUBTYPE, implementationType, type);
+ return invalidFactory();
+ }
+
+ return (InternalFactory<T>) getInternalFactory(
+ member, Key.get(implementationType));
+ }
+
+ // Look for @DefaultProvider.
+ DefaultProvider defaultProvider = type.getAnnotation(DefaultProvider.class);
+ if (defaultProvider != null) {
+ final Class<? extends Provider<?>> providerType = defaultProvider.value();
+
+ // Make sure it's not the same type. TODO: Can we check for deeper loops?
+ if (providerType == type) {
+ errorHandler.handle(StackTraceElements.forType(type),
+ ErrorMessages.RECURSIVE_PROVIDER_TYPE, type);
+ return invalidFactory();
+ }
+
+ // TODO: Make sure the provided type extends type. We at least check
+ // the type at runtime below.
+
+ InternalFactory<? extends Provider<?>> providerFactory
+ = getInternalFactory(member, Key.get(providerType));
+ Key<? extends Provider<?>> providerKey = Key.get(providerType);
+ return (InternalFactory<T>) new BoundProviderFactory(
+ providerKey, providerFactory, StackTraceElements.forType(type)) {
+ public Object get(InternalContext context) {
+ Object o = super.get(context);
+ try {
+ return type.cast(o);
+ } catch (ClassCastException e) {
+ errorHandler.handle(StackTraceElements.forType(type),
+ ErrorMessages.SUBTYPE_NOT_PROVIDED, providerType, type);
+ throw new AssertionError();
+ }
+ }
+ };
+ }
+
+ // TODO: Method interceptors could actually enable us to implement
+ // abstract types. Should we remove this restriction?
+ if (Modifier.isAbstract(type.getModifiers())) {
+ return null;
+ }
+
+ // Inject the class itself.
synchronized (implicitBindings) {
@SuppressWarnings("unchecked")
InternalFactory<T> factory =
@@ -919,11 +983,11 @@
static class ImplicitBinding<T> implements InternalFactory<T> {
- final Class<T> type;
+ final Class<T> implementation;
ConstructorInjector<T> constructorInjector;
- ImplicitBinding(Class<T> type) {
- this.type = type;
+ ImplicitBinding(Class<T> implementation) {
+ this.implementation = implementation;
}
void setConstructorInjector(
@@ -932,7 +996,7 @@
}
public T get(InternalContext context) {
- return constructorInjector.construct(context, type);
+ return constructorInjector.construct(context, implementation);
}
}