update ThrowingProvider to support an @ThrowingProvides annotation, much like @Provides. update the internals of the class to give more explicit error messaging & use bind.addError instead of throwing exceptions. added lots & lots of tests.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@1312 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java
index 90255b9..e171db2 100644
--- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java
+++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java
@@ -18,6 +18,7 @@
import com.google.inject.Binder;
import com.google.inject.Key;
+import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.ScopedBindingBuilder;
@@ -34,15 +35,31 @@
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
+import java.util.Arrays;
import java.util.Set;
/**
- * <p>Builds a binding for a {@link ThrowingProvider} using a fluent API:
+ * <p>Builds a binding for a {@link ThrowingProvider}.
+ *
+ * <p>You can use a fluent API and custom providers:
* <pre><code>ThrowingProviderBinder.create(binder())
* .bind(RemoteProvider.class, Customer.class)
* .to(RemoteCustomerProvider.class)
* .in(RequestScope.class);
* </code></pre>
+ * or, you can use throwing provider methods:
+ * <pre><code>class MyModule extends AbstractModule {
+ * configure() {
+ * ThrowingProviderBinder.install(this, binder());
+ * }
+ *
+ * {@literal @}ThrowingProvides(RemoteProvider.class)
+ * {@literal @}RequestScope
+ * Customer provideCustomer(FlakyCustomerCreator creator) throws RemoteException {
+ * return creator.getCustomerOrThrow();
+ * }
+ * }
+ * </code></pre>
*
* @author jmourits@google.com (Jerome Mourits)
* @author jessewilson@google.com (Jesse Wilson)
@@ -55,8 +72,14 @@
this.binder = binder;
}
- public static ThrowingProviderBinder create(Binder binder) {
- return new ThrowingProviderBinder(binder);
+ public static ThrowingProviderBinder create(Binder binder) {
+ return new ThrowingProviderBinder(binder.skipSources(
+ ThrowingProviderBinder.class,
+ ThrowingProviderBinder.SecondaryBinder.class));
+ }
+
+ public static void install(Module module, Binder binder) {
+ binder.install(ThrowingProviderMethodsModule.forModule(module));
}
public <P extends ThrowingProvider> SecondaryBinder<P>
@@ -69,13 +92,23 @@
private final Type valueType;
private Class<? extends Annotation> annotationType;
private Annotation annotation;
- private final Class<?> exceptionType;
+ private final Class<? extends Exception> exceptionType;
+ private final boolean valid;
public SecondaryBinder(Class<P> interfaceType, Type valueType) {
this.interfaceType = checkNotNull(interfaceType, "interfaceType");
this.valueType = checkNotNull(valueType, "valueType");
- checkInterface();
- this.exceptionType = getExceptionType(interfaceType);
+ if(checkInterface()) {
+ this.exceptionType = getExceptionType(interfaceType);
+ valid = true;
+ } else {
+ valid = false;
+ this.exceptionType = null;
+ }
+ }
+
+ Class<? extends Exception> getExceptionType() {
+ return exceptionType;
}
public SecondaryBinder<P> annotatedWith(Class<? extends Annotation> annotationType) {
@@ -103,32 +136,47 @@
public ScopedBindingBuilder to(Class<? extends P> targetType) {
return to(Key.get(targetType));
}
+
+ ScopedBindingBuilder toProviderMethod(ThrowingProviderMethod<?> target) {
+ Key<ThrowingProviderMethod> targetKey =
+ Key.get(ThrowingProviderMethod.class, UniqueAnnotations.create());
+ binder.bind(targetKey).toInstance(target);
+
+ return toInternal(targetKey);
+ }
- public ScopedBindingBuilder to(final Key<? extends P> targetKey) {
+ public ScopedBindingBuilder to(Key<? extends P> targetKey) {
checkNotNull(targetKey, "targetKey");
+ return toInternal(targetKey);
+ }
+
+ private ScopedBindingBuilder toInternal(final Key<? extends ThrowingProvider> targetKey) {
final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create());
- final Key<P> key = createKey();
+ final Key<P> key = createKey();
final Provider<Result> resultProvider = binder.getProvider(resultKey);
- final Provider<? extends P> targetProvider = binder.getProvider(targetKey);
+ final Provider<? extends ThrowingProvider> targetProvider = binder.getProvider(targetKey);
- binder.bind(key).toProvider(new ProviderWithDependencies<P>() {
- private final P instance = interfaceType.cast(Proxy.newProxyInstance(
- interfaceType.getClassLoader(), new Class<?>[] { interfaceType },
- new InvocationHandler() {
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- return resultProvider.get().getOrThrow();
- }
- }));
-
- public P get() {
- return instance;
- }
-
- public Set<Dependency<?>> getDependencies() {
- return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey));
- }
- });
+ // don't bother binding the proxy type if this is in an invalid state.
+ if(valid) {
+ binder.bind(key).toProvider(new ProviderWithDependencies<P>() {
+ private final P instance = interfaceType.cast(Proxy.newProxyInstance(
+ interfaceType.getClassLoader(), new Class<?>[] { interfaceType },
+ new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ return resultProvider.get().getOrThrow();
+ }
+ }));
+
+ public P get() {
+ return instance;
+ }
+
+ public Set<Dependency<?>> getDependencies() {
+ return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey));
+ }
+ });
+ }
return binder.bind(resultKey).toProvider(new ProviderWithDependencies<Result>() {
public Result get() {
@@ -157,58 +205,94 @@
* {@code interfaceType}.
*/
@SuppressWarnings({"unchecked"})
- private <P extends ThrowingProvider> Class<?> getExceptionType(Class<P> interfaceType) {
+ private Class<? extends Exception> getExceptionType(Class<P> interfaceType) {
ParameterizedType genericUnreliableProvider
= (ParameterizedType) interfaceType.getGenericInterfaces()[0];
return (Class<? extends Exception>) genericUnreliableProvider.getActualTypeArguments()[1];
}
- private void checkInterface() {
- String errorMessage = "%s is not a compliant interface "
- + "- see the Javadoc for ThrowingProvider";
-
- checkArgument(interfaceType.isInterface(), errorMessage, interfaceType.getName());
- checkArgument(interfaceType.getGenericInterfaces().length == 1, errorMessage,
- interfaceType.getName());
- checkArgument(interfaceType.getInterfaces()[0] == ThrowingProvider.class,
- errorMessage, interfaceType.getName());
+ private boolean checkInterface() {
+ if(!checkArgument(interfaceType.isInterface(),
+ "%s must be an interface", interfaceType.getName())) {
+ return false;
+ }
+ if(!checkArgument(interfaceType.getGenericInterfaces().length == 1,
+ "%s must extend ThrowingProvider (and only ThrowingProvider)",
+ interfaceType)) {
+ return false;
+ }
+ if(!checkArgument(interfaceType.getInterfaces()[0] == ThrowingProvider.class,
+ "%s must extend ThrowingProvider (and only ThrowingProvider)",
+ interfaceType)) {
+ return false;
+ }
// Ensure that T is parameterized and unconstrained.
ParameterizedType genericThrowingProvider
= (ParameterizedType) interfaceType.getGenericInterfaces()[0];
if (interfaceType.getTypeParameters().length == 1) {
- checkArgument(interfaceType.getTypeParameters().length == 1, errorMessage,
- interfaceType.getName());
String returnTypeName = interfaceType.getTypeParameters()[0].getName();
Type returnType = genericThrowingProvider.getActualTypeArguments()[0];
- checkArgument(returnType instanceof TypeVariable, errorMessage, interfaceType.getName());
- checkArgument(returnTypeName.equals(((TypeVariable) returnType).getName()),
- errorMessage, interfaceType.getName());
+ if(!checkArgument(returnType instanceof TypeVariable,
+ "%s does not properly extend ThrowingProvider, the first type parameter of ThrowingProvider (%s) is not a generic type",
+ interfaceType, returnType)) {
+ return false;
+ }
+ if(!checkArgument(returnTypeName.equals(((TypeVariable) returnType).getName()),
+ "The generic type (%s) of %s does not match the generic type of ThrowingProvider (%s)",
+ returnTypeName, interfaceType, ((TypeVariable)returnType).getName())) {
+ return false;
+ }
} else {
- checkArgument(interfaceType.getTypeParameters().length == 0,
- errorMessage, interfaceType.getName());
- checkArgument(genericThrowingProvider.getActualTypeArguments()[0].equals(valueType),
- errorMessage, interfaceType.getName());
+ if(!checkArgument(interfaceType.getTypeParameters().length == 0,
+ "%s has more than one generic type parameter: %s",
+ interfaceType, Arrays.asList(interfaceType.getTypeParameters()))) {
+ return false;
+ }
+ if(!checkArgument(genericThrowingProvider.getActualTypeArguments()[0].equals(valueType),
+ "%s expects the value type to be %s, but it was %s",
+ interfaceType, genericThrowingProvider.getActualTypeArguments()[0], valueType)) {
+ return false;
+ }
}
Type exceptionType = genericThrowingProvider.getActualTypeArguments()[1];
- checkArgument(exceptionType instanceof Class, errorMessage, interfaceType.getName());
+ if(!checkArgument(exceptionType instanceof Class,
+ "%s has the wrong Exception generic type (%s) when extending ThrowingProvider",
+ interfaceType, exceptionType)) {
+ return false;
+ }
if (interfaceType.getDeclaredMethods().length == 1) {
Method method = interfaceType.getDeclaredMethods()[0];
- checkArgument(method.getName().equals("get"), errorMessage, interfaceType.getName());
- checkArgument(method.getParameterTypes().length == 0,
- errorMessage, interfaceType.getName());
+ if(!checkArgument(method.getName().equals("get"),
+ "%s may not declare any new methods, but declared %s",
+ interfaceType, method)) {
+ return false;
+ }
+ if(!checkArgument(method.getParameterTypes().length == 0,
+ "%s may not declare any new methods, but declared %s",
+ interfaceType, method.toGenericString())) {
+ return false;
+ }
} else {
- checkArgument(interfaceType.getDeclaredMethods().length == 0,
- errorMessage, interfaceType.getName());
+ if(!checkArgument(interfaceType.getDeclaredMethods().length == 0,
+ "%s may not declare any new methods, but declared %s",
+ interfaceType, Arrays.asList(interfaceType.getDeclaredMethods()))) {
+ return false;
+ }
}
+
+ return true;
}
- private void checkArgument(boolean condition,
+ private boolean checkArgument(boolean condition,
String messageFormat, Object... args) {
if (!condition) {
- throw new IllegalArgumentException(String.format(messageFormat, args));
+ binder.addError(messageFormat, args);
+ return false;
+ } else {
+ return true;
}
}
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderMethod.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderMethod.java
new file mode 100644
index 0000000..0f20949
--- /dev/null
+++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderMethod.java
@@ -0,0 +1,156 @@
+/**
+ * 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.throwingproviders;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+import com.google.inject.Binder;
+import com.google.inject.Exposed;
+import com.google.inject.Key;
+import com.google.inject.PrivateBinder;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.ScopedBindingBuilder;
+import com.google.inject.internal.util.ImmutableSet;
+import com.google.inject.internal.util.StackTraceElements;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.HasDependencies;
+import com.google.inject.throwingproviders.ThrowingProviderBinder.SecondaryBinder;
+
+/**
+ * A provider that invokes a method and returns its result.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ */
+class ThrowingProviderMethod<T> implements ThrowingProvider<T, Exception>, HasDependencies {
+ private final Key<T> key;
+ private final Class<? extends Annotation> scopeAnnotation;
+ private final Object instance;
+ private final Method method;
+ private final ImmutableSet<Dependency<?>> dependencies;
+ private final List<Provider<?>> parameterProviders;
+ private final boolean exposed;
+ private final Class<? extends ThrowingProvider> throwingProvider;
+ private final List<TypeLiteral<?>> exceptionTypes;
+
+ ThrowingProviderMethod(
+ Key<T> key,
+ Method method,
+ Object instance,
+ ImmutableSet<Dependency<?>> dependencies,
+ List<Provider<?>> parameterProviders,
+ Class<? extends Annotation> scopeAnnotation,
+ Class<? extends ThrowingProvider> throwingProvider,
+ List<TypeLiteral<?>> exceptionTypes) {
+ this.key = key;
+ this.scopeAnnotation = scopeAnnotation;
+ this.instance = instance;
+ this.dependencies = dependencies;
+ this.method = method;
+ this.parameterProviders = parameterProviders;
+ this.exposed = method.isAnnotationPresent(Exposed.class);
+ this.throwingProvider = throwingProvider;
+ this.exceptionTypes = exceptionTypes;
+
+ method.setAccessible(true);
+ }
+
+ public Key<T> getKey() {
+ return key;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public void configure(Binder binder) {
+ binder = binder.withSource(method);
+
+ SecondaryBinder<?> sbinder =
+ ThrowingProviderBinder.create(binder)
+ .bind(throwingProvider, key.getTypeLiteral().getType());
+ if(key.getAnnotation() != null) {
+ sbinder = sbinder.annotatedWith(key.getAnnotation());
+ } else if(key.getAnnotationType() != null) {
+ sbinder = sbinder.annotatedWith(key.getAnnotationType());
+ }
+ ScopedBindingBuilder sbbuilder = sbinder.toProviderMethod(this);
+ if(scopeAnnotation != null) {
+ sbbuilder.in(scopeAnnotation);
+ }
+
+ if (exposed) {
+ // the cast is safe 'cause the only binder we have implements PrivateBinder. If there's a
+ // misplaced @Exposed, calling this will add an error to the binder's error queue
+ ((PrivateBinder) binder).expose(key);
+ }
+
+ // Validate the exceptions in the method match the exceptions
+ // in the ThrowingProvider.
+ for(TypeLiteral<?> exType : exceptionTypes) {
+ // Ignore runtime exceptions.
+ if(RuntimeException.class.isAssignableFrom(exType.getRawType())) {
+ continue;
+ }
+
+ if(sbinder.getExceptionType() != null) {
+ if (!sbinder.getExceptionType().isAssignableFrom(exType.getRawType())) {
+ binder.addError(
+ "%s is not compatible with the exception (%s) declared in the ThrowingProvider interface (%s)",
+ exType.getRawType(), sbinder.getExceptionType(), throwingProvider);
+ }
+ }
+ }
+ }
+
+ public T get() throws Exception {
+ Object[] parameters = new Object[parameterProviders.size()];
+ for (int i = 0; i < parameters.length; i++) {
+ parameters[i] = parameterProviders.get(i).get();
+ }
+
+ try {
+ // We know this cast is safe becase T is the method's return type.
+ @SuppressWarnings({ "unchecked", "UnnecessaryLocalVariable" })
+ T result = (T) method.invoke(instance, parameters);
+ return result;
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ Throwable t = e.getCause();
+ if(t instanceof Exception) {
+ throw (Exception)t;
+ } else if(t instanceof Error) {
+ throw (Error)t;
+ } else {
+ throw new IllegalStateException(t);
+ }
+ }
+ }
+
+ public Set<Dependency<?>> getDependencies() {
+ return dependencies;
+ }
+
+ @Override public String toString() {
+ return "@ThrowingProvides " + StackTraceElements.forMember(method).toString();
+ }
+}
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderMethodsModule.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderMethodsModule.java
new file mode 100644
index 0000000..528782a
--- /dev/null
+++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderMethodsModule.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright (C) 2010 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.throwingproviders;
+
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.internal.Annotations;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.UniqueAnnotations;
+import com.google.inject.internal.util.ImmutableSet;
+import com.google.inject.internal.util.Lists;
+import static com.google.inject.internal.util.Preconditions.checkNotNull;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.Message;
+import com.google.inject.util.Modules;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Creates bindings to methods annotated with {@literal @}{@link ThrowingProvides}. Use the scope
+ * and binding annotations on the provider method to configure the binding.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ */
+final class ThrowingProviderMethodsModule implements Module {
+ private final Object delegate;
+ private final TypeLiteral<?> typeLiteral;
+
+ private ThrowingProviderMethodsModule(Object delegate) {
+ this.delegate = checkNotNull(delegate, "delegate");
+ this.typeLiteral = TypeLiteral.get(this.delegate.getClass());
+ }
+
+ /**
+ * Returns a module which creates bindings for provider methods from the given module.
+ */
+ static Module forModule(Module module) {
+ // avoid infinite recursion, since installing a module always installs itself
+ if (module instanceof ThrowingProviderMethodsModule) {
+ return Modules.EMPTY_MODULE;
+ }
+
+ return new ThrowingProviderMethodsModule(module);
+ }
+
+ public synchronized void configure(Binder binder) {
+ for (ThrowingProviderMethod<?> throwingProviderMethod : getProviderMethods(binder)) {
+ throwingProviderMethod.configure(binder);
+ }
+ }
+
+ List<ThrowingProviderMethod<?>> getProviderMethods(Binder binder) {
+ List<ThrowingProviderMethod<?>> result = Lists.newArrayList();
+ for (Class<?> c = delegate.getClass(); c != Object.class; c = c.getSuperclass()) {
+ for (Method method : c.getDeclaredMethods()) {
+ ThrowingProvides throwingProvides =
+ (ThrowingProvides)method.getAnnotation(ThrowingProvides.class);
+ if(throwingProvides != null) {
+ result.add(createProviderMethod(binder, method, throwingProvides.value()));
+ }
+ }
+ }
+ return result;
+ }
+
+ <T> ThrowingProviderMethod<T> createProviderMethod(Binder binder, final Method method,
+ Class<? extends ThrowingProvider> throwingProvider) {
+ binder = binder.withSource(method);
+ Errors errors = new Errors(method);
+
+ // prepare the parameter providers
+ List<Dependency<?>> dependencies = Lists.newArrayList();
+ List<Provider<?>> parameterProviders = Lists.newArrayList();
+ List<TypeLiteral<?>> parameterTypes = typeLiteral.getParameterTypes(method);
+ Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+ for (int i = 0; i < parameterTypes.size(); i++) {
+ Key<?> key = getKey(errors, parameterTypes.get(i), method, parameterAnnotations[i]);
+ if(key.equals(Key.get(Logger.class))) {
+ // If it was a Logger, change the key to be unique & bind it to a
+ // provider that provides a logger with a proper name.
+ // This solves issue 482 (returning a new anonymous logger on every call exhausts memory)
+ Key<Logger> loggerKey = Key.get(Logger.class, UniqueAnnotations.create());
+ binder.bind(loggerKey).toProvider(new LogProvider(method));
+ key = loggerKey;
+ }
+ dependencies.add(Dependency.get(key));
+ parameterProviders.add(binder.getProvider(key));
+ }
+
+ @SuppressWarnings("unchecked") // Define T as the method's return type.
+ TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method);
+ List<TypeLiteral<?>> exceptionTypes = typeLiteral.getExceptionTypes(method);
+
+ Key<T> key = getKey(errors, returnType, method, method.getAnnotations());
+ Class<? extends Annotation> scopeAnnotation
+ = Annotations.findScopeAnnotation(errors, method.getAnnotations());
+
+ for (Message message : errors.getMessages()) {
+ binder.addError(message);
+ }
+
+ return new ThrowingProviderMethod<T>(key, method, delegate, ImmutableSet.copyOf(dependencies),
+ parameterProviders, scopeAnnotation, throwingProvider, exceptionTypes);
+ }
+
+ <T> Key<T> getKey(Errors errors, TypeLiteral<T> type, Member member, Annotation[] annotations) {
+ Annotation bindingAnnotation = Annotations.findBindingAnnotation(errors, member, annotations);
+ return bindingAnnotation == null ? Key.get(type) : Key.get(type, bindingAnnotation);
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof ThrowingProviderMethodsModule
+ && ((ThrowingProviderMethodsModule) o).delegate == delegate;
+ }
+
+ @Override public int hashCode() {
+ return delegate.hashCode();
+ }
+
+ /** A provider that returns a logger based on the method name. */
+ private static final class LogProvider implements Provider<Logger> {
+ private final String name;
+
+ public LogProvider(Method method) {
+ this.name = method.getDeclaringClass().getName() + "." + method.getName();
+ }
+
+ public Logger get() {
+ return Logger.getLogger(name);
+ }
+ }
+}
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProvides.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProvides.java
new file mode 100644
index 0000000..d680202
--- /dev/null
+++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProvides.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2010 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.throwingproviders;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.METHOD;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates methods of a {@link Module} to create a provider method binding that can throw
+ * exceptions. The method's return type is bound to it's returned value. Guice will pass
+ * dependencies to the method as parameters.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ * @since 3.0
+ */
+@Documented @Target(METHOD) @Retention(RUNTIME)
+public @interface ThrowingProvides {
+
+ /**
+ * The interface that provides this value, a subinterface of ThrowingProvider.
+ */
+ Class<? extends ThrowingProvider> value();
+
+}
diff --git a/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderBinderTest.java b/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderBinderTest.java
index a882ab0..6bb1f88 100644
--- a/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderBinderTest.java
+++ b/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderBinderTest.java
@@ -17,7 +17,6 @@
package com.google.inject.throwingproviders;
import com.google.inject.AbstractModule;
-import static com.google.inject.Asserts.assertContains;
import com.google.inject.CreationException;
import com.google.inject.Guice;
@@ -25,17 +24,24 @@
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
+import com.google.inject.internal.MoreTypes;
import com.google.inject.internal.util.Function;
+import com.google.inject.internal.util.ImmutableList;
import com.google.inject.internal.util.ImmutableSet;
import com.google.inject.internal.util.Iterables;
+import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
+import com.google.inject.spi.Message;
+import java.io.IOException;
+import java.rmi.AccessException;
import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.TooManyListenersException;
import junit.framework.TestCase;
@@ -49,7 +55,7 @@
= new TypeLiteral<RemoteProvider<String>>() { };
private final MockRemoteProvider<String> mockRemoteProvider = new MockRemoteProvider<String>();
private final TestScope testScope = new TestScope();
- private Injector injector = Guice.createInjector(new AbstractModule() {
+ private Injector bindInjector = Guice.createInjector(new AbstractModule() {
protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(RemoteProvider.class, String.class)
@@ -57,10 +63,31 @@
.in(testScope);
}
});
+ private Injector providesInjector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ bindScope(TestScope.Scoped.class, testScope);
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ @TestScope.Scoped
+ String throwOrGet() throws RemoteException {
+ return mockRemoteProvider.get();
+ }
+ });
- public void testExceptionsThrown() {
+ public void testExceptionsThrown_Bind() {
+ tExceptionsThrown(bindInjector);
+ }
+
+ public void testExceptionsThrown_Provides() {
+ tExceptionsThrown(providesInjector);
+ }
+
+ private void tExceptionsThrown(Injector injector) {
RemoteProvider<String> remoteProvider =
- injector.getInstance(Key.get(remoteProviderOfString));
+ injector.getInstance(Key.get(remoteProviderOfString));
mockRemoteProvider.throwOnNextGet("kaboom!");
try {
@@ -71,9 +98,17 @@
}
}
- public void testValuesScoped() throws RemoteException {
+ public void testValuesScoped_Bind() throws RemoteException {
+ tValuesScoped(bindInjector);
+ }
+
+ public void testValuesScoped_Provides() throws RemoteException {
+ tValuesScoped(providesInjector);
+ }
+
+ private void tValuesScoped(Injector injector) throws RemoteException {
RemoteProvider<String> remoteProvider =
- injector.getInstance(Key.get(remoteProviderOfString));
+ injector.getInstance(Key.get(remoteProviderOfString));
mockRemoteProvider.setNextToReturn("A");
assertEquals("A", remoteProvider.get());
@@ -85,7 +120,15 @@
assertEquals("B", remoteProvider.get());
}
- public void testExceptionsScoped() {
+ public void testExceptionsScoped_Bind() {
+ tExceptionsScoped(bindInjector);
+ }
+
+ public void testExceptionsScoped_Provides() {
+ tExceptionsScoped(providesInjector);
+ }
+
+ private void tExceptionsScoped(Injector injector) {
RemoteProvider<String> remoteProvider =
injector.getInstance(Key.get(remoteProviderOfString));
@@ -106,13 +149,10 @@
}
}
- public void testAnnotations() throws RemoteException {
+ public void testAnnotations_Bind() throws RemoteException {
final MockRemoteProvider<String> mockRemoteProviderA = new MockRemoteProvider<String>();
- mockRemoteProviderA.setNextToReturn("A");
final MockRemoteProvider<String> mockRemoteProviderB = new MockRemoteProvider<String>();
- mockRemoteProviderB.setNextToReturn("B");
-
- injector = Guice.createInjector(new AbstractModule() {
+ bindInjector = Guice.createInjector(new AbstractModule() {
protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(RemoteProvider.class, String.class)
@@ -124,19 +164,55 @@
.to(mockRemoteProviderB);
}
});
-
+ tAnnotations(bindInjector, mockRemoteProviderA, mockRemoteProviderB);
+ }
+
+ public void testAnnotations_Provides() throws RemoteException {
+ final MockRemoteProvider<String> mockRemoteProviderA = new MockRemoteProvider<String>();
+ final MockRemoteProvider<String> mockRemoteProviderB = new MockRemoteProvider<String>();
+ providesInjector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ @Named("a")
+ String throwOrGet() throws RemoteException {
+ return mockRemoteProviderA.get();
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ String throwOrGet2() throws RemoteException {
+ return mockRemoteProviderB.get();
+ }
+ });
+ tAnnotations(providesInjector, mockRemoteProviderA, mockRemoteProviderB);
+ }
+
+ private void tAnnotations(Injector injector, MockRemoteProvider<String> mockA,
+ MockRemoteProvider<String> mockB) throws RemoteException {
+ mockA.setNextToReturn("A");
+ mockB.setNextToReturn("B");
assertEquals("A",
injector.getInstance(Key.get(remoteProviderOfString, Names.named("a"))).get());
assertEquals("B",
injector.getInstance(Key.get(remoteProviderOfString)).get());
-
}
- public void testUndeclaredExceptions() throws RemoteException {
+ public void testUndeclaredExceptions_Bind() throws RemoteException {
+ tUndeclaredExceptions(bindInjector);
+ }
+
+ public void testUndeclaredExceptions_Provides() throws RemoteException {
+ tUndeclaredExceptions(providesInjector);
+ }
+
+ private void tUndeclaredExceptions(Injector injector) throws RemoteException {
RemoteProvider<String> remoteProvider =
injector.getInstance(Key.get(remoteProviderOfString));
-
mockRemoteProvider.throwOnNextGet(new IndexOutOfBoundsException("A"));
try {
remoteProvider.get();
@@ -159,7 +235,7 @@
final SubMockRemoteProvider aProvider = new SubMockRemoteProvider();
aProvider.setNextToReturn("A");
- injector = Guice.createInjector(new AbstractModule() {
+ bindInjector = Guice.createInjector(new AbstractModule() {
protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(RemoteProvider.class, String.class)
@@ -168,14 +244,14 @@
});
assertEquals("A",
- injector.getInstance(Key.get(remoteProviderOfString)).get());
+ bindInjector.getInstance(Key.get(remoteProviderOfString)).get());
}
static class SubMockRemoteProvider extends MockRemoteProvider<String> { }
- public void testBindingToNonInterfaceType() throws RemoteException {
+ public void testBindingToNonInterfaceType_Bind() throws RemoteException {
try {
- injector = Guice.createInjector(new AbstractModule() {
+ Guice.createInjector(new AbstractModule() {
protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(MockRemoteProvider.class, String.class)
@@ -184,13 +260,34 @@
});
fail();
} catch (CreationException expected) {
- assertContains(expected.getMessage(), "is not a compliant interface");
+ assertEquals(MockRemoteProvider.class.getName() + " must be an interface",
+ Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
}
}
- public void testBindingToSubSubInterface() throws RemoteException {
+ public void testBindingToNonInterfaceType_Provides() throws RemoteException {
try {
- injector = Guice.createInjector(new AbstractModule() {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(MockRemoteProvider.class)
+ String foo() {
+ return null;
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertEquals(MockRemoteProvider.class.getName() + " must be an interface",
+ Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testBindingToSubSubInterface_Bind() throws RemoteException {
+ try {
+ bindInjector = Guice.createInjector(new AbstractModule() {
protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(SubRemoteProvider.class, String.class);
@@ -198,15 +295,36 @@
});
fail();
} catch (CreationException expected) {
- assertContains(expected.getMessage(), "is not a compliant interface");
+ assertEquals(SubRemoteProvider.class.getName() + " must extend ThrowingProvider (and only ThrowingProvider)",
+ Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
}
}
+
+ public void testBindingToSubSubInterface_Provides() throws RemoteException {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(SubRemoteProvider.class)
+ String foo() {
+ return null;
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertEquals(SubRemoteProvider.class.getName() + " must extend ThrowingProvider (and only ThrowingProvider)",
+ Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
+ }
+ }
interface SubRemoteProvider extends RemoteProvider<String> { }
- public void testBindingToInterfaceWithExtraMethod() throws RemoteException {
+ public void testBindingToInterfaceWithExtraMethod_Bind() throws RemoteException {
try {
- injector = Guice.createInjector(new AbstractModule() {
+ bindInjector = Guice.createInjector(new AbstractModule() {
protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(RemoteProviderWithExtraMethod.class, String.class);
@@ -214,12 +332,35 @@
});
fail();
} catch (CreationException expected) {
- assertContains(expected.getMessage(), "is not a compliant interface");
+ assertEquals(RemoteProviderWithExtraMethod.class.getName() + " may not declare any new methods, but declared "
+ + RemoteProviderWithExtraMethod.class.getDeclaredMethods()[0].toGenericString(),
+ Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
}
}
- public void testDependencies() {
- injector = Guice.createInjector(new AbstractModule() {
+ public void testBindingToInterfaceWithExtraMethod_Provides() throws RemoteException {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProviderWithExtraMethod.class)
+ String foo() {
+ return null;
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertEquals(RemoteProviderWithExtraMethod.class.getName() + " may not declare any new methods, but declared "
+ + RemoteProviderWithExtraMethod.class.getDeclaredMethods()[0].toGenericString(),
+ Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testDependencies_Bind() {
+ bindInjector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(String.class).toInstance("Foo");
bind(Integer.class).toInstance(5);
@@ -232,15 +373,15 @@
});
HasDependencies hasDependencies =
- (HasDependencies)injector.getBinding(Key.get(remoteProviderOfString));
+ (HasDependencies)bindInjector.getBinding(Key.get(remoteProviderOfString));
hasDependencies =
- (HasDependencies)injector.getBinding(
+ (HasDependencies)bindInjector.getBinding(
Iterables.getOnlyElement(hasDependencies.getDependencies()).getKey());
// Make sure that that is dependent on DependentRemoteProvider.
assertEquals(Dependency.get(Key.get(DependentRemoteProvider.class)),
Iterables.getOnlyElement(hasDependencies.getDependencies()));
// And make sure DependentRemoteProvider has the proper dependencies.
- hasDependencies = (HasDependencies)injector.getBinding(DependentRemoteProvider.class);
+ hasDependencies = (HasDependencies)bindInjector.getBinding(DependentRemoteProvider.class);
Set<Key<?>> dependencyKeys = ImmutableSet.copyOf(
Iterables.transform(hasDependencies.getDependencies(),
new Function<Dependency<?>, Key<?>>() {
@@ -251,6 +392,43 @@
assertEquals(ImmutableSet.<Key<?>>of(Key.get(String.class), Key.get(Integer.class),
Key.get(Long.class), Key.get(Double.class)), dependencyKeys);
}
+
+ public void testDependencies_Provides() {
+ providesInjector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(String.class).toInstance("Foo");
+ bind(Integer.class).toInstance(5);
+ bind(Double.class).toInstance(5d);
+ bind(Long.class).toInstance(5L);
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ String foo(String s, Integer i, Double d, Long l) {
+ return null;
+ }
+ });
+
+ HasDependencies hasDependencies =
+ (HasDependencies)providesInjector.getBinding(Key.get(remoteProviderOfString));
+ // RemoteProvider<String> is dependent on the provider method..
+ hasDependencies =
+ (HasDependencies)providesInjector.getBinding(
+ Iterables.getOnlyElement(hasDependencies.getDependencies()).getKey());
+ // And the provider method has our real dependencies..
+ hasDependencies = (HasDependencies)providesInjector.getBinding(
+ Iterables.getOnlyElement(hasDependencies.getDependencies()).getKey());
+ Set<Key<?>> dependencyKeys = ImmutableSet.copyOf(
+ Iterables.transform(hasDependencies.getDependencies(),
+ new Function<Dependency<?>, Key<?>>() {
+ public Key<?> apply(Dependency<?> from) {
+ return from.getKey();
+ }
+ }));
+ assertEquals(ImmutableSet.<Key<?>>of(Key.get(String.class), Key.get(Integer.class),
+ Key.get(Long.class), Key.get(Double.class)), dependencyKeys);
+ }
interface RemoteProviderWithExtraMethod<T> extends ThrowingProvider<T, RemoteException> {
T get(T defaultValue) throws RemoteException;
@@ -300,8 +478,8 @@
}
}
- public void testBindingToInterfaceWithBoundValueType() throws RemoteException {
- injector = Guice.createInjector(new AbstractModule() {
+ public void testBindingToInterfaceWithBoundValueType_Bind() throws RemoteException {
+ bindInjector = Guice.createInjector(new AbstractModule() {
protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(StringRemoteProvider.class, String.class)
@@ -313,13 +491,29 @@
}
});
- assertEquals("A", injector.getInstance(StringRemoteProvider.class).get());
+ assertEquals("A", bindInjector.getInstance(StringRemoteProvider.class).get());
+ }
+
+ public void testBindingToInterfaceWithBoundValueType_Provides() throws RemoteException {
+ providesInjector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(StringRemoteProvider.class)
+ String foo() throws RemoteException {
+ return "A";
+ }
+ });
+
+ assertEquals("A", providesInjector.getInstance(StringRemoteProvider.class).get());
}
interface StringRemoteProvider extends ThrowingProvider<String, RemoteException> { }
- public void testBindingToInterfaceWithGeneric() throws RemoteException {
- injector = Guice.createInjector(new AbstractModule() {
+ public void testBindingToInterfaceWithGeneric_Bind() throws RemoteException {
+ bindInjector = Guice.createInjector(new AbstractModule() {
protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(RemoteProvider.class, new TypeLiteral<List<String>>() { }.getType())
@@ -333,6 +527,288 @@
Key<RemoteProvider<List<String>>> key
= Key.get(new TypeLiteral<RemoteProvider<List<String>>>() { });
- assertEquals(Arrays.asList("A", "B"), injector.getInstance(key).get());
+ assertEquals(Arrays.asList("A", "B"), bindInjector.getInstance(key).get());
+ }
+
+ public void testBindingToInterfaceWithGeneric_Provides() throws RemoteException {
+ providesInjector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ List<String> foo() throws RemoteException {
+ return Arrays.asList("A", "B");
+ }
+ });
+
+ Key<RemoteProvider<List<String>>> key
+ = Key.get(new TypeLiteral<RemoteProvider<List<String>>>() { });
+ assertEquals(Arrays.asList("A", "B"), providesInjector.getInstance(key).get());
+ }
+
+ public void testProviderMethodWithWrongException() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ String foo() throws InterruptedException {
+ return null;
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ assertEquals(InterruptedException.class.getName() + " is not compatible with the exception ("
+ + RemoteException.class.getName() + ") declared in the ThrowingProvider interface ("
+ + RemoteProvider.class.getName() + ")",
+ Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testProviderMethodWithSubclassOfExceptionIsOk() {
+ providesInjector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ String foo() throws AccessException {
+ throw new AccessException("boo!");
+ }
+ });
+
+ RemoteProvider<String> remoteProvider =
+ providesInjector.getInstance(Key.get(remoteProviderOfString));
+
+ try {
+ remoteProvider.get();
+ fail();
+ } catch (RemoteException expected) {
+ assertTrue(expected instanceof AccessException);
+ assertEquals("boo!", expected.getMessage());
+ }
+ }
+
+ public void testProviderMethodWithSuperclassFails() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ String foo() throws IOException {
+ return null;
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ assertEquals(IOException.class.getName() + " is not compatible with the exception ("
+ + RemoteException.class.getName() + ") declared in the ThrowingProvider interface ("
+ + RemoteProvider.class.getName() + ")",
+ Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testProviderMethodWithRuntimeExceptionsIsOk() throws RemoteException {
+ providesInjector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ String foo() throws RuntimeException {
+ throw new RuntimeException("boo!");
+ }
+ });
+
+ RemoteProvider<String> remoteProvider =
+ providesInjector.getInstance(Key.get(remoteProviderOfString));
+
+ try {
+ remoteProvider.get();
+ fail();
+ } catch (RuntimeException expected) {
+ assertEquals("boo!", expected.getCause().getMessage());
+ }
+ }
+
+ public void testProviderMethodWithManyExceptions() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(RemoteProvider.class)
+ String foo() throws InterruptedException, RuntimeException, RemoteException,
+ AccessException, TooManyListenersException {
+ return null;
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ // The only two that should fail are Interrupted & TooManyListeners.. the rest are OK.
+ List<Message> errors = ImmutableList.copyOf(ce.getErrorMessages());
+ assertEquals(InterruptedException.class.getName() + " is not compatible with the exception ("
+ + RemoteException.class.getName() + ") declared in the ThrowingProvider interface ("
+ + RemoteProvider.class.getName() + ")",
+ errors.get(0).getMessage());
+ assertEquals(TooManyListenersException.class.getName() + " is not compatible with the exception ("
+ + RemoteException.class.getName() + ") declared in the ThrowingProvider interface ("
+ + RemoteProvider.class.getName() + ")",
+ errors.get(1).getMessage());
+ assertEquals(2, errors.size());
+ }
+ }
+
+ public void testMoreTypeParameters() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(TooManyTypeParameters.class)
+ String foo() {
+ return null;
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ assertEquals(TooManyTypeParameters.class.getName() + " has more than one generic type parameter: [T, P]",
+ Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testWrongThrowingProviderType() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(WrongThrowingProviderType.class)
+ String foo() {
+ return null;
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ assertEquals(WrongThrowingProviderType.class.getName()
+ + " does not properly extend ThrowingProvider, the first type parameter of ThrowingProvider "
+ + "(java.lang.String) is not a generic type",
+ Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testOneMethodThatIsntGet() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(OneNoneGetMethod.class)
+ String foo() {
+ return null;
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ assertEquals(OneNoneGetMethod.class.getName()
+ + " may not declare any new methods, but declared " + MoreTypes.toString(OneNoneGetMethod.class.getDeclaredMethods()[0]),
+ Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testManyMethods() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(ManyMethods.class)
+ String foo() {
+ return null;
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ assertEquals(ManyMethods.class.getName()
+ + " may not declare any new methods, but declared " + Arrays.asList(ManyMethods.class.getDeclaredMethods()),
+ Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testIncorrectPredefinedType_Bind() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.create(binder())
+ .bind(StringRemoteProvider.class, Integer.class)
+ .to(new StringRemoteProvider() {
+ public String get() throws RemoteException {
+ return "A";
+ }
+ });
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ assertEquals(StringRemoteProvider.class.getName()
+ + " expects the value type to be java.lang.String, but it was java.lang.Integer",
+ Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
+ }
+ }
+
+ public void testIncorrectPredefinedType_Provides() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ ThrowingProviderBinder.install(this, binder());
+ }
+
+ @SuppressWarnings("unused")
+ @ThrowingProvides(StringRemoteProvider.class)
+ Integer foo() {
+ return null;
+ }
+ });
+ fail();
+ } catch(CreationException ce) {
+ assertEquals(StringRemoteProvider.class.getName()
+ + " expects the value type to be java.lang.String, but it was java.lang.Integer",
+ Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
+ }
+ }
+
+ private static interface TooManyTypeParameters<T, P> extends ThrowingProvider<T, Exception> {
+ }
+
+ private static interface WrongThrowingProviderType<T> extends ThrowingProvider<String, Exception> {
+ }
+
+ private static interface OneNoneGetMethod<T> extends ThrowingProvider<T, Exception> {
+ T bar();
+ }
+
+ private static interface ManyMethods<T> extends ThrowingProvider<T, Exception> {
+ T bar();
+ String baz();
}
}