Add ability to not scope exception to RemoteProviderBinder
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=87524840
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethod.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethod.java
index f65b6d2..a76d64c 100644
--- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethod.java
+++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethod.java
@@ -50,6 +50,7 @@
   private final boolean exposed;
   private final Class<? extends CheckedProvider> checkedProvider;
   private final List<TypeLiteral<?>> exceptionTypes;
+  private final boolean scopeExceptions;
 
   CheckedProviderMethod(
       Key<T> key,
@@ -59,7 +60,8 @@
       List<Provider<?>> parameterProviders,
       Class<? extends Annotation> scopeAnnotation,
       Class<? extends CheckedProvider> checkedProvider,
-      List<TypeLiteral<?>> exceptionTypes) {
+      List<TypeLiteral<?>> exceptionTypes,
+      boolean scopeExceptions) {
     this.key = key;
     this.scopeAnnotation = scopeAnnotation;
     this.instance = instance;
@@ -69,6 +71,7 @@
     this.exposed = method.isAnnotationPresent(Exposed.class);
     this.checkedProvider = checkedProvider;
     this.exceptionTypes = exceptionTypes;
+    this.scopeExceptions = scopeExceptions;
 
     method.setAccessible(true);
   }
@@ -77,13 +80,14 @@
     binder = binder.withSource(method);
 
     SecondaryBinder<?, ?> sbinder = 
-      ThrowingProviderBinder.create(binder)
-        .bind(checkedProvider, key.getTypeLiteral());
+        ThrowingProviderBinder.create(binder)
+          .bind(checkedProvider, key.getTypeLiteral());
     if(key.getAnnotation() != null) {
       sbinder = sbinder.annotatedWith(key.getAnnotation());
     } else if(key.getAnnotationType() != null) {
       sbinder = sbinder.annotatedWith(key.getAnnotationType());
-    } 
+    }
+    sbinder.scopeExceptions(scopeExceptions);
     ScopedBindingBuilder sbbuilder = sbinder.toProviderMethod(this);
     if(scopeAnnotation != null) {
       sbbuilder.in(scopeAnnotation);
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethodsModule.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethodsModule.java
index dce2bfa..f88c507 100644
--- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethodsModule.java
+++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethodsModule.java
@@ -79,7 +79,7 @@
       for (Method method : c.getDeclaredMethods()) {
         CheckedProvides checkedProvides = method.getAnnotation(CheckedProvides.class);
         if(checkedProvides != null) {
-          result.add(createProviderMethod(binder, method, checkedProvides.value()));
+          result.add(createProviderMethod(binder, method, checkedProvides));
         }
       }
     }
@@ -87,7 +87,9 @@
   }
 
   <T> CheckedProviderMethod<T> createProviderMethod(Binder binder, final Method method,
-      Class<? extends CheckedProvider> throwingProvider) {
+      CheckedProvides checkedProvides) {
+    @SuppressWarnings("rawtypes")
+    Class<? extends CheckedProvider> throwingProvider = checkedProvides.value();
     binder = binder.withSource(method);
     Errors errors = new Errors(method);
 
@@ -123,7 +125,8 @@
     }
 
     return new CheckedProviderMethod<T>(key, method, delegate, ImmutableSet.copyOf(dependencies),
-        parameterProviders, scopeAnnotation, throwingProvider, exceptionTypes);
+        parameterProviders, scopeAnnotation, throwingProvider, exceptionTypes, 
+        checkedProvides.scopeExceptions()); 
   }
 
   <T> Key<T> getKey(Errors errors, TypeLiteral<T> type, Member member, Annotation[] annotations) {
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProvides.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProvides.java
index b702dcc..c7bfaed 100644
--- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProvides.java
+++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProvides.java
@@ -42,5 +42,10 @@
    * The interface that provides this value, a subinterface of {@link CheckedProvider}.
    */
   Class<? extends CheckedProvider> value();
-  
+
+  /**
+   * Whether exceptions should be put into the Guice scope.
+   * Default behavior is that exceptions are scoped. 
+   */
+  boolean scopeExceptions() default true;  
 }
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java
index 637099a..de33d2c 100644
--- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java
+++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java
@@ -85,6 +85,12 @@
  */
 public class ThrowingProviderBinder {
 
+  private static final TypeLiteral<CheckedProvider<?>> CHECKED_PROVIDER_TYPE
+      = new TypeLiteral<CheckedProvider<?>>() { };
+
+  private static final TypeLiteral<CheckedProviderMethod<?>> CHECKED_PROVIDER_METHOD_TYPE
+      = new TypeLiteral<CheckedProviderMethod<?>>() { };
+
   private final Binder binder;
 
   private ThrowingProviderBinder(Binder binder) {
@@ -134,6 +140,7 @@
     private Class<? extends Annotation> annotationType;
     private Annotation annotation;
     private Key<P> interfaceKey;
+    private boolean scopeExceptions = true;
 
     public SecondaryBinder(Class<P> interfaceType, Type valueType) {
       this.interfaceType = checkNotNull(interfaceType, "interfaceType");
@@ -171,6 +178,15 @@
       return this;
     }
 
+    /**
+     * Determines if exceptions should be scoped. By default exceptions are scoped.
+     * @param scopeExceptions whether exceptions should be scoped.
+     */
+    public SecondaryBinder<P, T> scopeExceptions(boolean scopeExceptions) {
+      this.scopeExceptions = scopeExceptions;
+      return this;
+    }
+    
     public ScopedBindingBuilder to(P target) {
       Key<P> targetKey = Key.get(interfaceType, UniqueAnnotations.create());
       binder.bind(targetKey).toInstance(target);
@@ -236,28 +252,32 @@
         }
       };
       
-      Key<CheckedProvider> targetKey = Key.get(CheckedProvider.class, UniqueAnnotations.create());
+      Key<CheckedProvider<?>> targetKey = Key.get(CHECKED_PROVIDER_TYPE,
+          UniqueAnnotations.create());
       binder.bind(targetKey).toInstance(checkedProvider);
       return toInternal(targetKey);
     }
     
     ScopedBindingBuilder toProviderMethod(CheckedProviderMethod<?> target) {
-      Key<CheckedProviderMethod> targetKey =
-          Key.get(CheckedProviderMethod.class, UniqueAnnotations.create());
+      Key<CheckedProviderMethod<?>> targetKey = 
+          Key.get(CHECKED_PROVIDER_METHOD_TYPE, UniqueAnnotations.create());
       binder.bind(targetKey).toInstance(target);
 
       return toInternal(targetKey);
     }
 
+    @SuppressWarnings("unchecked") // P only extends the raw type of CheckedProvider
     public ScopedBindingBuilder to(Key<? extends P> targetKey) {
       checkNotNull(targetKey, "targetKey");
-      return toInternal(targetKey);
+      return toInternal((Key<? extends CheckedProvider<?>>)targetKey);
     }
     
-    private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider> targetKey) {
+    private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider<?>> targetKey) {
       final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create());
+      // Note that this provider will behave like the final provider Guice creates.
+      // It will especially do scoping if the user adds that.
       final Provider<Result> resultProvider = binder.getProvider(resultKey);
-      final Provider<? extends CheckedProvider> targetProvider = binder.getProvider(targetKey);
+      final Provider<? extends CheckedProvider<?>> targetProvider = binder.getProvider(targetKey);
       interfaceKey = createKey();
 
       // don't bother binding the proxy type if this is in an invalid state.
@@ -272,31 +292,63 @@
                   if (method.getDeclaringClass() == Object.class) {
                     return method.invoke(this, args);
                   }
-                  return resultProvider.get().getOrThrow();
+                  
+                  if (scopeExceptions) {
+                    return resultProvider.get().getOrThrow();
+                  } else {
+                    Result result;
+                    try {
+                      result = resultProvider.get();
+                    } catch (ProvisionException pe) {
+                      Throwable cause = pe.getCause();
+                      if (cause instanceof ResultException) {
+                        throw ((ResultException)cause).getCause();
+                      } else {
+                        throw pe;
+                      }
+                    }
+                    return result.getOrThrow();
+                  }
                 }
               }));
             
+            @Override
             public P get() {
               return instance;
             }
-            
+  
+            @Override
             public Set<Dependency<?>> getDependencies() {
               return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey));
             }
           });
       }
 
-      return binder.bind(resultKey).toProvider(new ProviderWithDependencies<Result>() {
+      // The provider is unscoped, but the user may apply a scope to it through the 
+      // ScopedBindingBuilder this returns.
+      return binder.bind(resultKey).toProvider(
+          createResultProvider(targetKey, targetProvider));
+    }
+
+    private ProviderWithDependencies<Result> createResultProvider(
+        final Key<? extends CheckedProvider<?>> targetKey,
+        final Provider<? extends CheckedProvider<?>> targetProvider) {
+      return new ProviderWithDependencies<Result>() {
+        @Override
         public Result get() {
           try {
             return Result.forValue(targetProvider.get().get());
           } catch (Exception e) {
-            for(Class<? extends Throwable> exceptionType : exceptionTypes) {
+            for (Class<? extends Throwable> exceptionType : exceptionTypes) {
               if (exceptionType.isInstance(e)) {
-                return Result.forException(e);
+                if (scopeExceptions) {
+                  return Result.forException(e);
+                } else {
+                  throw new ResultException(e);
+                }
               }
             }
-            
+
             if (e instanceof RuntimeException) {
               throw (RuntimeException) e;
             } else {
@@ -305,13 +357,14 @@
             }
           }
         }
-        
+
+        @Override
         public Set<Dependency<?>> getDependencies() {
           return ImmutableSet.<Dependency<?>>of(Dependency.get(targetKey));
         }
-      });
+      };
     }
-
+    
     /**
      * Returns the exception type declared to be thrown by the get method of
      * {@code interfaceType}.
@@ -455,10 +508,12 @@
   }
 
   /**
-   * Represents the returned value from a call to {@link
-   * CheckedProvider#get()}. This is the value that will be scoped by Guice.
+   * Represents the returned value from a call to {@link CheckedProvider#get()}. This is the value
+   * that will be scoped by Guice.
    */
   static class Result implements Serializable {
+    private static final long serialVersionUID = 0L;
+
     private final Object value;
     private final Exception exception;
 
@@ -474,7 +529,7 @@
     public static Result forException(Exception e) {
       return new Result(null, e);
     }
-
+    
     public Object getOrThrow() throws Exception {
       if (exception != null) {
         throw exception;
@@ -482,8 +537,17 @@
         return value;
       }
     }
-    
-    private static final long serialVersionUID = 0L;
+  }
+
+  /**
+   * RuntimeException class to wrap exceptions from the checked provider.
+   * The regular guice provider can throw it and the checked provider proxy extracts
+   * the underlying exception and rethrows it.
+   */
+  private static class ResultException extends RuntimeException {
+    ResultException(Exception cause) {
+      super(cause);
+    }
   }
   
   private static class NotSyntheticOrBridgePredicate implements Predicate<Method> {
diff --git a/extensions/throwingproviders/test/com/google/inject/throwingproviders/CheckedProviderTest.java b/extensions/throwingproviders/test/com/google/inject/throwingproviders/CheckedProviderTest.java
index 7a640be..1f4f977 100644
--- a/extensions/throwingproviders/test/com/google/inject/throwingproviders/CheckedProviderTest.java
+++ b/extensions/throwingproviders/test/com/google/inject/throwingproviders/CheckedProviderTest.java
@@ -16,6 +16,9 @@
 
 package com.google.inject.throwingproviders;
 
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -23,6 +26,7 @@
 import com.google.common.collect.Lists;
 import com.google.inject.AbstractModule;
 import com.google.inject.Asserts;
+import com.google.inject.BindingAnnotation;
 import com.google.inject.CreationException;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
@@ -45,6 +49,7 @@
 import junit.framework.TestCase;
 
 import java.io.IOException;
+import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -64,6 +69,8 @@
  * @author sameb@google.com (Sam Berlin)
  */
 public class CheckedProviderTest extends TestCase {
+  @Target(METHOD) @Retention(RUNTIME) @BindingAnnotation
+  @interface NotExceptionScoping { };
   
   private static final Function<Dependency<?>, Key<?>> DEPENDENCY_TO_KEY =
       new Function<Dependency<?>, Key<?>>() {
@@ -95,6 +102,14 @@
             .bind(RemoteProvider.class, Foo.class)
             .to(mockRemoteProvider)
             .in(testScope);
+
+        ThrowingProviderBinder.create(binder())
+            .bind(RemoteProvider.class, Foo.class)
+            .annotatedWith(NotExceptionScoping.class)
+            .scopeExceptions(false)
+            .to(mockRemoteProvider)
+            .in(testScope);
+
       }
     });  
     
@@ -111,6 +126,15 @@
       Foo throwOrGet() throws RemoteException, BindException {
         return mockRemoteProvider.get();
       }
+
+      @SuppressWarnings("unused")
+      @CheckedProvides(value = RemoteProvider.class, scopeExceptions = false)
+      @NotExceptionScoping
+      @TestScope.Scoped
+      Foo notExceptionScopingThrowOrGet() throws RemoteException, BindException {
+        return mockRemoteProvider.get();
+      }    
+      
     });
     
     cxtorInjector = Guice.createInjector(new AbstractModule() {
@@ -120,6 +144,14 @@
           .bind(RemoteProvider.class, Foo.class)
           .providing(MockFoo.class)
           .in(testScope);
+
+        ThrowingProviderBinder.create(binder())
+          .bind(RemoteProvider.class, Foo.class)
+          .annotatedWith(NotExceptionScoping.class)
+          .scopeExceptions(false)
+          .providing(MockFoo.class)
+          .in(testScope);
+        
       }
     });
   }
@@ -151,17 +183,28 @@
   }
 
   public void testValuesScoped_Bind() throws Exception  {
-    tValuesScoped(bindInjector);
+    tValuesScoped(bindInjector, null);
   }
   
   public void testValuesScoped_Provides() throws Exception  {
-    tValuesScoped(providesInjector);
+    tValuesScoped(providesInjector, null);
   }
   
-  private void tValuesScoped(Injector injector) throws Exception {
-    RemoteProvider<Foo> remoteProvider = 
-      injector.getInstance(Key.get(remoteProviderOfFoo));
+  public void testValuesScopedWhenNotExceptionScoping_Bind() throws Exception  {
+    tValuesScoped(bindInjector, NotExceptionScoping.class);
+  }
+  
+  public void testValuesScopedWhenNotExceptionScoping_Provides() throws Exception  {
+    tValuesScoped(providesInjector, NotExceptionScoping.class);
+  }
 
+  private void tValuesScoped(Injector injector, 
+      Class<? extends Annotation> annotation) throws Exception {
+    Key<RemoteProvider<Foo>> key = annotation != null ? 
+        Key.get(remoteProviderOfFoo, annotation) :
+        Key.get(remoteProviderOfFoo);
+    RemoteProvider<Foo> remoteProvider = injector.getInstance(key);
+    
     mockRemoteProvider.setNextToReturn(new SimpleFoo("A"));
     assertEquals("A", remoteProvider.get().s());
 
@@ -218,6 +261,41 @@
     }
   }
   
+  public void testExceptionsNotScopedWhenNotExceptionScoping_Bind() throws Exception {
+    tExceptionsNotScopedWhenNotExceptionScoping(bindInjector);
+  }
+  
+  public void testExceptionsNotScopedWhenNotExceptionScoping_Provides() throws Exception {
+    tExceptionsNotScopedWhenNotExceptionScoping(providesInjector);
+  }
+  
+  public void testExceptionNotScopedWhenNotExceptionScoping_Cxtor() throws Exception {
+    tExceptionsNotScopedWhenNotExceptionScoping(cxtorInjector);
+  }
+  
+  private void tExceptionsNotScopedWhenNotExceptionScoping(Injector injector) throws Exception {
+    RemoteProvider<Foo> remoteProvider = 
+        injector.getInstance(Key.get(remoteProviderOfFoo, NotExceptionScoping.class));
+
+    mockRemoteProvider.throwOnNextGet(new RemoteException("A"));
+    MockFoo.nextToThrow = new RemoteException("A");
+    try {
+      remoteProvider.get();
+      fail();
+    } catch (RemoteException expected) {
+      assertEquals("A", expected.getMessage());
+    }
+    
+    mockRemoteProvider.throwOnNextGet(new RemoteException("B"));
+    MockFoo.nextToThrow = new RemoteException("B");
+    try {
+      remoteProvider.get();
+      fail();
+    } catch (RemoteException expected) {
+      assertEquals("B", expected.getMessage());
+    }
+  }
+  
   public void testAnnotations_Bind() throws Exception {
     final MockRemoteProvider<Foo> mockRemoteProviderA = new MockRemoteProvider<Foo>();
     final MockRemoteProvider<Foo> mockRemoteProviderB = new MockRemoteProvider<Foo>();
diff --git a/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderTest.java b/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderTest.java
index d56cb7d..373c68d 100644
--- a/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderTest.java
+++ b/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderTest.java
@@ -16,11 +16,15 @@
 
 package com.google.inject.throwingproviders;
 
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.inject.AbstractModule;
+import com.google.inject.BindingAnnotation;
 import com.google.inject.CreationException;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
@@ -37,6 +41,9 @@
 import junit.framework.TestCase;
 
 import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
 import java.rmi.AccessException;
 import java.rmi.RemoteException;
 import java.util.Arrays;
@@ -50,6 +57,8 @@
  */
 @SuppressWarnings("deprecation")
 public class ThrowingProviderTest extends TestCase {
+  @Target(METHOD) @Retention(RUNTIME) @BindingAnnotation
+  @interface NotExceptionScoping { };
 
   private final TypeLiteral<RemoteProvider<String>> remoteProviderOfString
       = new TypeLiteral<RemoteProvider<String>>() { };
@@ -61,6 +70,13 @@
           .bind(RemoteProvider.class, String.class)
           .to(mockRemoteProvider)
           .in(testScope);
+      
+      ThrowingProviderBinder.create(binder())
+        .bind(RemoteProvider.class, String.class)
+        .annotatedWith(NotExceptionScoping.class)
+        .scopeExceptions(false)
+        .to(mockRemoteProvider)
+        .in(testScope);
     }
   });
   private Injector providesInjector = Guice.createInjector(new AbstractModule() {
@@ -75,6 +91,14 @@
     String throwOrGet() throws RemoteException {
       return mockRemoteProvider.get();
     }
+    
+    @SuppressWarnings("unused")
+    @CheckedProvides(value = RemoteProvider.class, scopeExceptions = false)
+    @NotExceptionScoping
+    @TestScope.Scoped
+    String notExceptionScopingThrowOrGet() throws RemoteException {
+      return mockRemoteProvider.get();
+    }    
   });
 
   public void testExceptionsThrown_Bind() {
@@ -99,16 +123,27 @@
   }
 
   public void testValuesScoped_Bind() throws RemoteException {
-    tValuesScoped(bindInjector);
+    tValuesScoped(bindInjector, null);
   }
   
   public void testValuesScoped_Provides() throws RemoteException {
-    tValuesScoped(providesInjector);
+    tValuesScoped(providesInjector, null);
   }
   
-  private void tValuesScoped(Injector injector) throws RemoteException {
-    RemoteProvider<String> remoteProvider = 
-      injector.getInstance(Key.get(remoteProviderOfString));
+  public void testValuesScopedWhenNotExceptionScoping_Bind() throws RemoteException {
+    tValuesScoped(bindInjector, NotExceptionScoping.class);
+  }
+  
+  public void testValuesScopedWhenNotExceptionScoping_Provides() throws RemoteException {
+    tValuesScoped(providesInjector, NotExceptionScoping.class);
+  }
+  
+  private void tValuesScoped(Injector injector, Class<? extends Annotation> annotation) 
+      throws RemoteException {
+    Key<RemoteProvider<String>> key = annotation != null ? 
+        Key.get(remoteProviderOfString, annotation) :
+        Key.get(remoteProviderOfString);
+    RemoteProvider<String> remoteProvider = injector.getInstance(key);
 
     mockRemoteProvider.setNextToReturn("A");
     assertEquals("A", remoteProvider.get());
@@ -148,6 +183,35 @@
       assertEquals("A", expected.getMessage());
     }
   }
+
+  public void testExceptionsNotScopedWhenNotExceptionScoping_Bind() {
+    tExceptionsNotScopedWhenNotExceptionScoping(bindInjector);
+  }
+  
+  public void testExceptionsNotScopedWhenNotExceptionScoping_Provides() {
+    tExceptionsNotScopedWhenNotExceptionScoping(providesInjector);
+  }
+  
+  private void tExceptionsNotScopedWhenNotExceptionScoping(Injector injector) {
+    RemoteProvider<String> remoteProvider = 
+        injector.getInstance(Key.get(remoteProviderOfString, NotExceptionScoping.class));
+
+    mockRemoteProvider.throwOnNextGet("A");
+    try {
+      remoteProvider.get();
+      fail();
+    } catch (RemoteException expected) {
+      assertEquals("A", expected.getMessage());
+    }
+    
+    mockRemoteProvider.throwOnNextGet("B");
+    try {
+      remoteProvider.get();
+      fail();
+    } catch (RemoteException expected) {
+      assertEquals("B", expected.getMessage());
+    }
+  }
   
   public void testAnnotations_Bind() throws RemoteException {
     final MockRemoteProvider<String> mockRemoteProviderA = new MockRemoteProvider<String>();