Error handling. Now if InjectionListeners or InjectableTypeListeners fail, we include those errors in our pretty error report.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@910 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/ConstructorInjector.java b/src/com/google/inject/ConstructorInjector.java
index 7176474..13ff143 100644
--- a/src/com/google/inject/ConstructorInjector.java
+++ b/src/com/google/inject/ConstructorInjector.java
@@ -95,7 +95,12 @@
}
for (InjectionListener<? super T> injectionListener : injectionListeners) {
- injectionListener.afterInjection(t); // TODO: handle user exceptions here
+ try {
+ injectionListener.afterInjection(t);
+ } catch (RuntimeException e) {
+ throw errors.withSource(constructionProxy.getInjectionPoint())
+ .errorNotifyingInjectionListener(injectionListener, injectableType, e).toException();
+ }
}
return t;
diff --git a/src/com/google/inject/ConstructorInjectorStore.java b/src/com/google/inject/ConstructorInjectorStore.java
index ff78e6d..147362c 100644
--- a/src/com/google/inject/ConstructorInjectorStore.java
+++ b/src/com/google/inject/ConstructorInjectorStore.java
@@ -64,8 +64,16 @@
private <T> ConstructorInjector<T> createConstructor(TypeLiteral<T> type, Errors errors)
throws ErrorsException {
+ int numErrorsBefore = errors.size();
- InjectionPoint injectionPoint = InjectionPoint.forConstructorOf(type);
+ InjectionPoint injectionPoint;
+ try {
+ injectionPoint = InjectionPoint.forConstructorOf(type);
+ } catch (ConfigurationException e) {
+ errors.merge(e.getErrorMessages());
+ throw errors.toException();
+ }
+
ImmutableList<SingleParameterInjector<?>> constructorParameterInjectors
= injector.getParametersInjectors(injectionPoint.getDependencies(), errors);
ImmutableList<SingleMemberInjector> memberInjectors = injector.injectors.get(type, errors);
@@ -83,8 +91,11 @@
for (InjectableTypeListenerBinding typeListener : injectableTypeListenerBindings) {
if (typeListener.getTypeMatcher().matches(type)) {
- // TODO: wrap this user code in a better try/catch block
- typeListener.getListener().hear(injectableType, encounter);
+ try {
+ typeListener.getListener().hear(injectableType, encounter);
+ } catch (RuntimeException e) {
+ errors.errorNotifyingTypeListener(typeListener, injectableType, e);
+ }
}
}
@@ -96,6 +107,8 @@
injectionPoint, type, injectableMembers, proxyFactory.getInterceptors());
}
+ errors.throwIfNewErrors(numErrorsBefore);
+
return new ConstructorInjector<T>(proxyFactory.create(), constructorParameterInjectors,
memberInjectors, encounter.getInjectionListeners(), injectableType);
}
@@ -193,5 +206,9 @@
return methodInterceptors;
}
/*end[AOP]*/
+
+ @Override public String toString() {
+ return type.toString();
+ }
}
}
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index a17fb40..db644e8 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -284,7 +284,7 @@
return new ConvertedConstantBindingImpl<T>(this, key, converted, stringBinding);
} catch (ErrorsException e) {
throw e;
- } catch (Exception e) {
+ } catch (RuntimeException e) {
throw errors.conversionError(stringValue, source, type, matchingConverter, e)
.toException();
}
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index ed781d5..130c087 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -24,6 +24,9 @@
import com.google.inject.Scope;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Dependency;
+import com.google.inject.spi.InjectableType;
+import com.google.inject.spi.InjectableTypeListenerBinding;
+import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.Message;
import java.io.PrintWriter;
@@ -125,8 +128,8 @@
}
public Errors conversionError(String stringValue, Object source,
- TypeLiteral<?> type, MatcherAndConverter matchingConverter, Exception cause) {
- return addMessage(cause, "Error converting '%s' (bound at %s) to %s%n"
+ TypeLiteral<?> type, MatcherAndConverter matchingConverter, RuntimeException cause) {
+ return errorInUserCode(cause, "Error converting '%s' (bound at %s) to %s%n"
+ " using %s.%n"
+ " Reason: %s",
stringValue, convert(source), type, matchingConverter, cause);
@@ -248,15 +251,29 @@
}
public Errors errorInjectingMethod(Throwable cause) {
- return errorInUserCode("Error injecting method, %s", cause);
+ return errorInUserCode(cause, "Error injecting method, %s", cause);
+ }
+
+ public Errors errorNotifyingTypeListener(InjectableTypeListenerBinding listener,
+ InjectableType<?> injectableType, Throwable cause) {
+ return errorInUserCode(cause,
+ "Error notifying InjectableType.Listener %s (bound at %s) of %s.%n"
+ + " Reason: %s",
+ listener.getListener(), convert(listener.getSource()), injectableType, cause);
}
public Errors errorInjectingConstructor(Throwable cause) {
- return errorInUserCode("Error injecting constructor, %s", cause);
+ return errorInUserCode(cause, "Error injecting constructor, %s", cause);
}
public Errors errorInProvider(RuntimeException runtimeException) {
- return errorInUserCode("Error in custom provider, %s", runtimeException);
+ return errorInUserCode(runtimeException, "Error in custom provider, %s", runtimeException);
+ }
+
+ public Errors errorNotifyingInjectionListener(
+ InjectionListener listener, InjectableType<?> injectableType, RuntimeException cause) {
+ return errorInUserCode(cause, "Error notifying InjectionListener %s of %s.%n"
+ + " Reason: %s", listener, injectableType, cause);
}
public void exposedButNotBound(Key<?> key) {
@@ -275,13 +292,13 @@
}
}
- public Errors errorInUserCode(String message, Throwable cause) {
+ public Errors errorInUserCode(Throwable cause, String messageFormat, Object... arguments) {
Collection<Message> messages = getMessagesFromThrowable(cause);
if (!messages.isEmpty()) {
return merge(messages);
} else {
- return addMessage(cause, message, cause);
+ return addMessage(cause, messageFormat, arguments);
}
}
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index 3999405..d9d61d2 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -30,6 +30,7 @@
import com.google.inject.spi.ModuleWriterTest;
import com.google.inject.spi.ProviderMethodsTest;
import com.google.inject.spi.SpiBindingsTest;
+import com.google.inject.spi.BindingTargetVisitorTest;
import com.google.inject.util.ProvidersTest;
import com.google.inject.util.TypesTest;
import junit.framework.Test;
@@ -51,18 +52,18 @@
suite.addTestSuite(BoundInstanceInjectionTest.class);
suite.addTestSuite(BoundProviderTest.class);
suite.addTestSuite(CircularDependencyTest.class);
- suite.addTestSuite(TypeConversionTest.class);
- // suite.addTestSuite(ErrorHandlingTest.class); not a testcase
+ // ErrorHandlingTest.class is not a testcase
suite.addTestSuite(EagerSingletonTest.class);
suite.addTestSuite(GenericInjectionTest.class);
suite.addTestSuite(ImplicitBindingTest.class);
suite.addTestSuite(InjectableTypeListenerTest.class);
- suite.addTestSuite(InjectionPointTest.class);
suite.addTestSuite(InjectorTest.class);
+ // IntegrationTest is AOP-only
suite.addTestSuite(KeyTest.class);
suite.addTestSuite(LoggerInjectionTest.class);
- suite.addTestSuite(ModuleTest.class);
+ // MethodInterceptionTest is AOP-only
suite.addTestSuite(ModulesTest.class);
+ suite.addTestSuite(ModuleTest.class);
suite.addTestSuite(NullableInjectionPointTest.class);
suite.addTestSuite(OptionalBindingTest.class);
suite.addTestSuite(OverrideModuleTest.class);
@@ -70,29 +71,22 @@
suite.addTestSuite(PrivateModuleTest.class);
suite.addTestSuite(ProviderInjectionTest.class);
suite.addTestSuite(ProvisionExceptionTest.class);
+ // ProxyFactoryTest is AOP-only
suite.addTestSuite(ReflectionTest.class);
+ suite.addTestSuite(RequestInjectionTest.class);
suite.addTestSuite(ScopesTest.class);
suite.addTestSuite(SerializationTest.class);
- suite.addTestSuite(RequestInjectionTest.class);
suite.addTestSuite(SuperclassTest.class);
+ suite.addTestSuite(TypeConversionTest.class);
suite.addTestSuite(TypeLiteralInjectionTest.class);
suite.addTestSuite(TypeLiteralTest.class);
suite.addTestSuite(TypeLiteralTypeResolutionTest.class);
- // spi
- suite.addTestSuite(ElementsTest.class);
- suite.addTestSuite(HasDependenciesTest.class);
- suite.addTestSuite(ProviderMethodsTest.class);
- suite.addTestSuite(ModuleWriterTest.class);
- suite.addTestSuite(ModuleRewriterTest.class);
- suite.addTestSuite(SpiBindingsTest.class);
-
// internal
suite.addTestSuite(FinalizableReferenceQueueTest.class);
+ suite.addTestSuite(Jsr166HashMapTest.class);
suite.addTestSuite(LineNumbersTest.class);
suite.addTest(MapMakerTestSuite.suite());
- suite.addTestSuite(Jsr166HashMapTest.class);
- suite.addTestSuite(TypesTest.class);
suite.addTestSuite(UniqueAnnotationsTest.class);
// matcher
@@ -101,11 +95,22 @@
// names
suite.addTestSuite(NamesTest.class);
+ // spi
+ suite.addTestSuite(BindingTargetVisitorTest.class);
+ suite.addTestSuite(ElementsTest.class);
+ suite.addTestSuite(HasDependenciesTest.class);
+ suite.addTestSuite(InjectionPointTest.class);
+ suite.addTestSuite(ModuleRewriterTest.class);
+ suite.addTestSuite(ModuleWriterTest.class);
+ suite.addTestSuite(ProviderMethodsTest.class);
+ suite.addTestSuite(SpiBindingsTest.class);
+
// tools
// suite.addTestSuite(JmxTest.class); not a testcase
// util
suite.addTestSuite(ProvidersTest.class);
+ suite.addTestSuite(TypesTest.class);
/*if[AOP]*/
suite.addTestSuite(ProxyFactoryTest.class);
diff --git a/test/com/google/inject/InjectableTypeListenerTest.java b/test/com/google/inject/InjectableTypeListenerTest.java
index c5de01f..e178aa8 100644
--- a/test/com/google/inject/InjectableTypeListenerTest.java
+++ b/test/com/google/inject/InjectableTypeListenerTest.java
@@ -25,6 +25,7 @@
import com.google.inject.spi.InjectableType;
import com.google.inject.spi.InjectableType.Encounter;
import com.google.inject.spi.InjectionListener;
+import static com.google.inject.Asserts.assertContains;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
@@ -131,9 +132,129 @@
assertEquals("felibeep", c.beep());
}
- // TODO(jessewilson): injectableType.Listener throws test
+ public void testInjectableTypeListenerThrows() {
+ final InjectableType.Listener clumsyListener = new InjectableType.Listener() {
+ int failures = 0;
- // TODO(jessewilson): injectionlistener throws test
+ public <I> void hear(InjectableType<I> injectableType, Encounter<I> encounter) {
+ throw new ClassCastException("whoops, failure #" + (++failures));
+ }
+
+ @Override public String toString() {
+ return "clumsy";
+ }
+ };
+
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bindListener(any(), clumsyListener);
+ bind(B.class);
+ bind(C.class);
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertContains(expected.getMessage(),
+ "1) Error notifying InjectableType.Listener clumsy (bound at " + getClass().getName(),
+ ".configure(InjectableTypeListenerTest.java:",
+ "of " + B.class.getName(),
+ "Reason: java.lang.ClassCastException: whoops, failure #1",
+ "2) Error notifying InjectableType.Listener clumsy (bound at " + getClass().getName(),
+ ".configure(InjectableTypeListenerTest.java:",
+ "of " + C.class.getName(),
+ "Reason: java.lang.ClassCastException: whoops, failure #2");
+ }
+
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bindListener(any(), clumsyListener);
+ }
+ });
+ try {
+ injector.getProvider(B.class);
+ fail();
+ } catch (ConfigurationException expected) {
+ assertContains(expected.getMessage(),
+ "1) Error notifying InjectableType.Listener clumsy (bound at " + getClass().getName(),
+ ".configure(InjectableTypeListenerTest.java:",
+ "of " + B.class.getName(),
+ "Reason: java.lang.ClassCastException: whoops, failure #3");
+ }
+
+ // getting it again should yield the same exception #3
+ try {
+ injector.getInstance(B.class);
+ fail();
+ } catch (ConfigurationException expected) {
+ assertContains(expected.getMessage(),
+ "1) Error notifying InjectableType.Listener clumsy (bound at " + getClass().getName(),
+ ".configure(InjectableTypeListenerTest.java:",
+ "of " + B.class.getName(),
+ "Reason: java.lang.ClassCastException: whoops, failure #3");
+ }
+
+ // non-constructed types do not participate
+ assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class));
+ }
+
+ public void testInjectionListenerThrows() {
+ final InjectionListener<Object> injectionListener = new InjectionListener<Object>() {
+ int failures = 0;
+
+ public void afterInjection(Object injectee) {
+ throw new ClassCastException("whoops, failure #" + (++failures));
+ }
+
+ @Override public String toString() {
+ return "goofy";
+ }
+ };
+
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bindListener(any(), new InjectableType.Listener() {
+ public <I> void hear(InjectableType<I> injectableType, Encounter<I> encounter) {
+ encounter.register(injectionListener);
+ }
+ });
+ bind(B.class);
+ }
+ });
+
+ try {
+ injector.getInstance(A.class);
+ fail();
+ } catch (ProvisionException e) {
+ assertContains(e.getMessage(),
+ "1) Error notifying InjectionListener goofy of " + A.class.getName(),
+ " Reason: java.lang.ClassCastException: whoops, failure #1");
+ }
+
+ // second time through should be a new cause (#2)
+ try {
+ injector.getInstance(A.class);
+ fail();
+ } catch (ProvisionException e) {
+ assertContains(e.getMessage(),
+ "1) Error notifying InjectionListener goofy of " + A.class.getName(),
+ " Reason: java.lang.ClassCastException: whoops, failure #2");
+ }
+
+ // we should get errors for all types, but only on getInstance()
+ Provider<B> bProvider = injector.getProvider(B.class);
+ try {
+ bProvider.get();
+ fail();
+ } catch (ProvisionException e) {
+ assertContains(e.getMessage(),
+ "1) Error notifying InjectionListener goofy of " + B.class.getName(),
+ " Reason: java.lang.ClassCastException: whoops, failure #3");
+ }
+
+ // non-constructed types do not participate
+ assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class));
+ }
static class A {
@Inject Injector injector;