1/**
2 * Copyright (C) 2007 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.inject.throwingproviders;
18
19import static java.lang.annotation.ElementType.METHOD;
20import static java.lang.annotation.RetentionPolicy.RUNTIME;
21
22import com.google.common.base.Function;
23import com.google.common.collect.ImmutableList;
24import com.google.common.collect.ImmutableSet;
25import com.google.common.collect.Iterables;
26import com.google.inject.AbstractModule;
27import com.google.inject.BindingAnnotation;
28import com.google.inject.CreationException;
29import com.google.inject.Guice;
30import com.google.inject.Inject;
31import com.google.inject.Injector;
32import com.google.inject.Key;
33import com.google.inject.TypeLiteral;
34import com.google.inject.internal.util.Classes;
35import com.google.inject.name.Named;
36import com.google.inject.name.Names;
37import com.google.inject.spi.Dependency;
38import com.google.inject.spi.HasDependencies;
39import com.google.inject.spi.Message;
40
41import junit.framework.TestCase;
42
43import java.io.IOException;
44import java.lang.annotation.Annotation;
45import java.lang.annotation.Retention;
46import java.lang.annotation.Target;
47import java.rmi.AccessException;
48import java.rmi.RemoteException;
49import java.util.Arrays;
50import java.util.List;
51import java.util.Set;
52import java.util.TooManyListenersException;
53
54/**
55 * @author jmourits@google.com (Jerome Mourits)
56 * @author jessewilson@google.com (Jesse Wilson)
57 */
58@SuppressWarnings("deprecation")
59public class ThrowingProviderTest extends TestCase {
60  @Target(METHOD) @Retention(RUNTIME) @BindingAnnotation
61  @interface NotExceptionScoping { };
62
63  private final TypeLiteral<RemoteProvider<String>> remoteProviderOfString
64      = new TypeLiteral<RemoteProvider<String>>() { };
65  private final MockRemoteProvider<String> mockRemoteProvider = new MockRemoteProvider<String>();
66  private final TestScope testScope = new TestScope();
67  private Injector bindInjector = Guice.createInjector(new AbstractModule() {
68    protected void configure() {
69      ThrowingProviderBinder.create(binder())
70          .bind(RemoteProvider.class, String.class)
71          .to(mockRemoteProvider)
72          .in(testScope);
73
74      ThrowingProviderBinder.create(binder())
75        .bind(RemoteProvider.class, String.class)
76        .annotatedWith(NotExceptionScoping.class)
77        .scopeExceptions(false)
78        .to(mockRemoteProvider)
79        .in(testScope);
80    }
81  });
82  private Injector providesInjector = Guice.createInjector(new AbstractModule() {
83    protected void configure() {
84      install(ThrowingProviderBinder.forModule(this));
85     bindScope(TestScope.Scoped.class, testScope);
86    }
87
88    @SuppressWarnings("unused")
89    @CheckedProvides(RemoteProvider.class)
90    @TestScope.Scoped
91    String throwOrGet() throws RemoteException {
92      return mockRemoteProvider.get();
93    }
94
95    @SuppressWarnings("unused")
96    @CheckedProvides(value = RemoteProvider.class, scopeExceptions = false)
97    @NotExceptionScoping
98    @TestScope.Scoped
99    String notExceptionScopingThrowOrGet() throws RemoteException {
100      return mockRemoteProvider.get();
101    }
102  });
103
104  public void testExceptionsThrown_Bind() {
105    tExceptionsThrown(bindInjector);
106  }
107
108  public void testExceptionsThrown_Provides() {
109    tExceptionsThrown(providesInjector);
110  }
111
112  private void tExceptionsThrown(Injector injector) {
113    RemoteProvider<String> remoteProvider =
114      injector.getInstance(Key.get(remoteProviderOfString));
115
116    mockRemoteProvider.throwOnNextGet("kaboom!");
117    try {
118      remoteProvider.get();
119      fail();
120    } catch (RemoteException expected) {
121      assertEquals("kaboom!", expected.getMessage());
122    }
123  }
124
125  public void testValuesScoped_Bind() throws RemoteException {
126    tValuesScoped(bindInjector, null);
127  }
128
129  public void testValuesScoped_Provides() throws RemoteException {
130    tValuesScoped(providesInjector, null);
131  }
132
133  public void testValuesScopedWhenNotExceptionScoping_Bind() throws RemoteException {
134    tValuesScoped(bindInjector, NotExceptionScoping.class);
135  }
136
137  public void testValuesScopedWhenNotExceptionScoping_Provides() throws RemoteException {
138    tValuesScoped(providesInjector, NotExceptionScoping.class);
139  }
140
141  private void tValuesScoped(Injector injector, Class<? extends Annotation> annotation)
142      throws RemoteException {
143    Key<RemoteProvider<String>> key = annotation != null ?
144        Key.get(remoteProviderOfString, annotation) :
145        Key.get(remoteProviderOfString);
146    RemoteProvider<String> remoteProvider = injector.getInstance(key);
147
148    mockRemoteProvider.setNextToReturn("A");
149    assertEquals("A", remoteProvider.get());
150
151    mockRemoteProvider.setNextToReturn("B");
152    assertEquals("A", remoteProvider.get());
153
154    testScope.beginNewScope();
155    assertEquals("B", remoteProvider.get());
156  }
157
158  public void testExceptionsScoped_Bind() {
159    tExceptionsScoped(bindInjector);
160  }
161
162  public void testExceptionsScoped_Provides() {
163    tExceptionsScoped(providesInjector);
164  }
165
166  private void tExceptionsScoped(Injector injector) {
167    RemoteProvider<String> remoteProvider =
168        injector.getInstance(Key.get(remoteProviderOfString));
169
170    mockRemoteProvider.throwOnNextGet("A");
171    try {
172      remoteProvider.get();
173      fail();
174    } catch (RemoteException expected) {
175      assertEquals("A", expected.getMessage());
176    }
177
178    mockRemoteProvider.throwOnNextGet("B");
179    try {
180      remoteProvider.get();
181      fail();
182    } catch (RemoteException expected) {
183      assertEquals("A", expected.getMessage());
184    }
185  }
186
187  public void testExceptionsNotScopedWhenNotExceptionScoping_Bind() {
188    tExceptionsNotScopedWhenNotExceptionScoping(bindInjector);
189  }
190
191  public void testExceptionsNotScopedWhenNotExceptionScoping_Provides() {
192    tExceptionsNotScopedWhenNotExceptionScoping(providesInjector);
193  }
194
195  private void tExceptionsNotScopedWhenNotExceptionScoping(Injector injector) {
196    RemoteProvider<String> remoteProvider =
197        injector.getInstance(Key.get(remoteProviderOfString, NotExceptionScoping.class));
198
199    mockRemoteProvider.throwOnNextGet("A");
200    try {
201      remoteProvider.get();
202      fail();
203    } catch (RemoteException expected) {
204      assertEquals("A", expected.getMessage());
205    }
206
207    mockRemoteProvider.throwOnNextGet("B");
208    try {
209      remoteProvider.get();
210      fail();
211    } catch (RemoteException expected) {
212      assertEquals("B", expected.getMessage());
213    }
214  }
215
216  public void testAnnotations_Bind() throws RemoteException {
217    final MockRemoteProvider<String> mockRemoteProviderA = new MockRemoteProvider<String>();
218    final MockRemoteProvider<String> mockRemoteProviderB = new MockRemoteProvider<String>();
219    bindInjector = Guice.createInjector(new AbstractModule() {
220      protected void configure() {
221        ThrowingProviderBinder.create(binder())
222            .bind(RemoteProvider.class, String.class)
223            .annotatedWith(Names.named("a"))
224            .to(mockRemoteProviderA);
225
226        ThrowingProviderBinder.create(binder())
227            .bind(RemoteProvider.class, String.class)
228            .to(mockRemoteProviderB);
229      }
230    });
231    tAnnotations(bindInjector, mockRemoteProviderA, mockRemoteProviderB);
232  }
233
234  public void testAnnotations_Provides() throws RemoteException {
235    final MockRemoteProvider<String> mockRemoteProviderA = new MockRemoteProvider<String>();
236    final MockRemoteProvider<String> mockRemoteProviderB = new MockRemoteProvider<String>();
237    providesInjector = Guice.createInjector(new AbstractModule() {
238      protected void configure() {
239        install(ThrowingProviderBinder.forModule(this));
240       }
241
242       @SuppressWarnings("unused")
243       @CheckedProvides(RemoteProvider.class)
244       @Named("a")
245       String throwOrGet() throws RemoteException {
246         return mockRemoteProviderA.get();
247       }
248
249       @SuppressWarnings("unused")
250       @CheckedProvides(RemoteProvider.class)
251       String throwOrGet2() throws RemoteException {
252         return mockRemoteProviderB.get();
253       }
254    });
255    tAnnotations(providesInjector, mockRemoteProviderA, mockRemoteProviderB);
256  }
257
258  private void tAnnotations(Injector injector, MockRemoteProvider<String> mockA,
259      MockRemoteProvider<String> mockB) throws RemoteException {
260    mockA.setNextToReturn("A");
261    mockB.setNextToReturn("B");
262    assertEquals("A",
263        injector.getInstance(Key.get(remoteProviderOfString, Names.named("a"))).get());
264
265    assertEquals("B",
266        injector.getInstance(Key.get(remoteProviderOfString)).get());
267  }
268
269  public void testUndeclaredExceptions_Bind() throws RemoteException {
270    tUndeclaredExceptions(bindInjector);
271  }
272
273  public void testUndeclaredExceptions_Provides() throws RemoteException {
274    tUndeclaredExceptions(providesInjector);
275  }
276
277  private void tUndeclaredExceptions(Injector injector) throws RemoteException {
278    RemoteProvider<String> remoteProvider =
279        injector.getInstance(Key.get(remoteProviderOfString));
280    mockRemoteProvider.throwOnNextGet(new IndexOutOfBoundsException("A"));
281    try {
282      remoteProvider.get();
283      fail();
284    } catch (RuntimeException e) {
285      assertEquals("A", e.getCause().getMessage());
286    }
287
288    // undeclared exceptions shouldn't be scoped
289    mockRemoteProvider.throwOnNextGet(new IndexOutOfBoundsException("B"));
290    try {
291      remoteProvider.get();
292      fail();
293    } catch (RuntimeException e) {
294      assertEquals("B", e.getCause().getMessage());
295    }
296  }
297
298  public void testThrowingProviderSubclassing() throws RemoteException {
299    final SubMockRemoteProvider aProvider = new SubMockRemoteProvider();
300    aProvider.setNextToReturn("A");
301
302    bindInjector = Guice.createInjector(new AbstractModule() {
303      protected void configure() {
304        ThrowingProviderBinder.create(binder())
305            .bind(RemoteProvider.class, String.class)
306            .to(aProvider);
307      }
308    });
309
310    assertEquals("A",
311        bindInjector.getInstance(Key.get(remoteProviderOfString)).get());
312  }
313
314  static class SubMockRemoteProvider extends MockRemoteProvider<String> { }
315
316  public void testBindingToNonInterfaceType_Bind() throws RemoteException {
317    try {
318      Guice.createInjector(new AbstractModule() {
319        protected void configure() {
320          ThrowingProviderBinder.create(binder())
321              .bind(MockRemoteProvider.class, String.class)
322              .to(mockRemoteProvider);
323        }
324      });
325      fail();
326    } catch (CreationException expected) {
327      assertEquals(MockRemoteProvider.class.getName() + " must be an interface",
328          Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
329    }
330  }
331
332  public void testBindingToNonInterfaceType_Provides() throws RemoteException {
333    try {
334      Guice.createInjector(new AbstractModule() {
335        protected void configure() {
336          install(ThrowingProviderBinder.forModule(this));
337        }
338
339        @SuppressWarnings("unused")
340        @CheckedProvides(MockRemoteProvider.class)
341        String foo() {
342          return null;
343        }
344      });
345      fail();
346    } catch (CreationException expected) {
347      assertEquals(MockRemoteProvider.class.getName() + " must be an interface",
348          Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
349    }
350  }
351
352  public void testBindingToSubSubInterface_Bind() throws RemoteException {
353    try {
354      bindInjector = Guice.createInjector(new AbstractModule() {
355        protected void configure() {
356          ThrowingProviderBinder.create(binder())
357              .bind(SubRemoteProvider.class, String.class);
358        }
359      });
360      fail();
361    } catch (CreationException expected) {
362      assertEquals(SubRemoteProvider.class.getName() + " must extend CheckedProvider (and only CheckedProvider)",
363          Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
364    }
365  }
366
367  public void testBindingToSubSubInterface_Provides() throws RemoteException {
368    try {
369      Guice.createInjector(new AbstractModule() {
370        protected void configure() {
371          install(ThrowingProviderBinder.forModule(this));
372        }
373
374        @SuppressWarnings("unused")
375        @CheckedProvides(SubRemoteProvider.class)
376        String foo() {
377          return null;
378        }
379      });
380      fail();
381    } catch (CreationException expected) {
382      assertEquals(SubRemoteProvider.class.getName() + " must extend CheckedProvider (and only CheckedProvider)",
383          Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
384    }
385  }
386
387  interface SubRemoteProvider extends RemoteProvider<String> { }
388
389  public void testBindingToInterfaceWithExtraMethod_Bind() throws RemoteException {
390    try {
391      bindInjector = Guice.createInjector(new AbstractModule() {
392        protected void configure() {
393          ThrowingProviderBinder.create(binder())
394              .bind(RemoteProviderWithExtraMethod.class, String.class);
395        }
396      });
397      fail();
398    } catch (CreationException expected) {
399      assertEquals(RemoteProviderWithExtraMethod.class.getName() + " may not declare any new methods, but declared "
400          + RemoteProviderWithExtraMethod.class.getDeclaredMethods()[0].toGenericString(),
401          Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
402    }
403  }
404
405  public void testBindingToInterfaceWithExtraMethod_Provides() throws RemoteException {
406    try {
407      Guice.createInjector(new AbstractModule() {
408        protected void configure() {
409          install(ThrowingProviderBinder.forModule(this));
410        }
411
412        @SuppressWarnings("unused")
413        @CheckedProvides(RemoteProviderWithExtraMethod.class)
414        String foo() {
415          return null;
416        }
417      });
418      fail();
419    } catch (CreationException expected) {
420      assertEquals(RemoteProviderWithExtraMethod.class.getName() + " may not declare any new methods, but declared "
421          + RemoteProviderWithExtraMethod.class.getDeclaredMethods()[0].toGenericString(),
422          Iterables.getOnlyElement(expected.getErrorMessages()).getMessage());
423    }
424  }
425
426  public void testDependencies_Bind() {
427    bindInjector = Guice.createInjector(new AbstractModule() {
428      protected void configure() {
429        bind(String.class).toInstance("Foo");
430        bind(Integer.class).toInstance(5);
431        bind(Double.class).toInstance(5d);
432        bind(Long.class).toInstance(5L);
433        ThrowingProviderBinder.create(binder())
434            .bind(RemoteProvider.class, String.class)
435            .to(DependentRemoteProvider.class);
436      }
437    });
438
439    HasDependencies hasDependencies =
440        (HasDependencies)bindInjector.getBinding(Key.get(remoteProviderOfString));
441    hasDependencies =
442        (HasDependencies)bindInjector.getBinding(
443            Iterables.getOnlyElement(hasDependencies.getDependencies()).getKey());
444    // Make sure that that is dependent on DependentRemoteProvider.
445    assertEquals(Dependency.get(Key.get(DependentRemoteProvider.class)),
446        Iterables.getOnlyElement(hasDependencies.getDependencies()));
447    // And make sure DependentRemoteProvider has the proper dependencies.
448    hasDependencies = (HasDependencies)bindInjector.getBinding(DependentRemoteProvider.class);
449    Set<Key<?>> dependencyKeys = ImmutableSet.copyOf(
450        Iterables.transform(hasDependencies.getDependencies(),
451          new Function<Dependency<?>, Key<?>>() {
452            public Key<?> apply(Dependency<?> from) {
453              return from.getKey();
454            }
455          }));
456    assertEquals(ImmutableSet.<Key<?>>of(Key.get(String.class), Key.get(Integer.class),
457        Key.get(Long.class), Key.get(Double.class)), dependencyKeys);
458  }
459
460  public void testDependencies_Provides() {
461    providesInjector = Guice.createInjector(new AbstractModule() {
462      protected void configure() {
463        bind(String.class).toInstance("Foo");
464        bind(Integer.class).toInstance(5);
465        bind(Double.class).toInstance(5d);
466        bind(Long.class).toInstance(5L);
467        install(ThrowingProviderBinder.forModule(this));
468      }
469
470      @SuppressWarnings("unused")
471      @CheckedProvides(RemoteProvider.class)
472      String foo(String s, Integer i, Double d, Long l) {
473        return null;
474      }
475    });
476
477    HasDependencies hasDependencies =
478        (HasDependencies)providesInjector.getBinding(Key.get(remoteProviderOfString));
479    // RemoteProvider<String> is dependent on the provider method..
480    hasDependencies =
481        (HasDependencies)providesInjector.getBinding(
482            Iterables.getOnlyElement(hasDependencies.getDependencies()).getKey());
483    // And the provider method has our real dependencies..
484    hasDependencies = (HasDependencies)providesInjector.getBinding(
485        Iterables.getOnlyElement(hasDependencies.getDependencies()).getKey());
486    Set<Key<?>> dependencyKeys = ImmutableSet.copyOf(
487        Iterables.transform(hasDependencies.getDependencies(),
488          new Function<Dependency<?>, Key<?>>() {
489            public Key<?> apply(Dependency<?> from) {
490              return from.getKey();
491            }
492          }));
493    assertEquals(ImmutableSet.<Key<?>>of(Key.get(String.class), Key.get(Integer.class),
494        Key.get(Long.class), Key.get(Double.class)), dependencyKeys);
495  }
496
497  interface RemoteProviderWithExtraMethod<T> extends ThrowingProvider<T, RemoteException> {
498    T get(T defaultValue) throws RemoteException;
499  }
500
501  interface RemoteProvider<T> extends ThrowingProvider<T, RemoteException> { }
502
503  static class DependentRemoteProvider<T> implements RemoteProvider<T> {
504    @Inject double foo;
505
506    @Inject public DependentRemoteProvider(String foo, int bar) {
507    }
508
509    @Inject void initialize(long foo) {}
510
511    public T get() throws RemoteException {
512      return null;
513    }
514  }
515
516  static class MockRemoteProvider<T> implements RemoteProvider<T> {
517    Exception nextToThrow;
518    T nextToReturn;
519
520    public void throwOnNextGet(String message) {
521      throwOnNextGet(new RemoteException(message));
522    }
523
524    public void throwOnNextGet(Exception nextToThrow) {
525      this.nextToThrow = nextToThrow;
526    }
527
528    public void setNextToReturn(T nextToReturn) {
529      this.nextToReturn = nextToReturn;
530    }
531
532    public T get() throws RemoteException {
533      if (nextToThrow instanceof RemoteException) {
534        throw (RemoteException) nextToThrow;
535      } else if (nextToThrow instanceof RuntimeException) {
536        throw (RuntimeException) nextToThrow;
537      } else if (nextToThrow == null) {
538        return nextToReturn;
539      } else {
540        throw new AssertionError("nextToThrow must be a runtime or remote exception");
541      }
542    }
543  }
544
545  public void testBindingToInterfaceWithBoundValueType_Bind() throws RemoteException {
546    bindInjector = Guice.createInjector(new AbstractModule() {
547      protected void configure() {
548        ThrowingProviderBinder.create(binder())
549            .bind(StringRemoteProvider.class, String.class)
550            .to(new StringRemoteProvider() {
551              public String get() throws RemoteException {
552                return "A";
553              }
554            });
555      }
556    });
557
558    assertEquals("A", bindInjector.getInstance(StringRemoteProvider.class).get());
559  }
560
561  public void testBindingToInterfaceWithBoundValueType_Provides() throws RemoteException {
562    providesInjector = Guice.createInjector(new AbstractModule() {
563      protected void configure() {
564        install(ThrowingProviderBinder.forModule(this));
565      }
566
567      @SuppressWarnings("unused")
568      @CheckedProvides(StringRemoteProvider.class)
569      String foo() throws RemoteException {
570          return "A";
571      }
572    });
573
574    assertEquals("A", providesInjector.getInstance(StringRemoteProvider.class).get());
575  }
576
577  interface StringRemoteProvider extends ThrowingProvider<String, RemoteException> { }
578
579  public void testBindingToInterfaceWithGeneric_Bind() throws RemoteException {
580    bindInjector = Guice.createInjector(new AbstractModule() {
581      protected void configure() {
582        ThrowingProviderBinder.create(binder())
583            .bind(RemoteProvider.class, new TypeLiteral<List<String>>() { }.getType())
584            .to(new RemoteProvider<List<String>>() {
585              public List<String> get() throws RemoteException {
586                return Arrays.asList("A", "B");
587              }
588            });
589      }
590    });
591
592    Key<RemoteProvider<List<String>>> key
593        = Key.get(new TypeLiteral<RemoteProvider<List<String>>>() { });
594    assertEquals(Arrays.asList("A", "B"), bindInjector.getInstance(key).get());
595  }
596
597  public void testBindingToInterfaceWithGeneric_Provides() throws RemoteException {
598    providesInjector = Guice.createInjector(new AbstractModule() {
599      protected void configure() {
600        install(ThrowingProviderBinder.forModule(this));
601      }
602
603      @SuppressWarnings("unused")
604      @CheckedProvides(RemoteProvider.class)
605      List<String> foo() throws RemoteException {
606          return Arrays.asList("A", "B");
607      }
608    });
609
610    Key<RemoteProvider<List<String>>> key
611        = Key.get(new TypeLiteral<RemoteProvider<List<String>>>() { });
612    assertEquals(Arrays.asList("A", "B"), providesInjector.getInstance(key).get());
613  }
614
615  public void testProviderMethodWithWrongException() {
616    try {
617      Guice.createInjector(new AbstractModule() {
618        protected void configure() {
619          install(ThrowingProviderBinder.forModule(this));
620        }
621
622        @SuppressWarnings("unused")
623        @CheckedProvides(RemoteProvider.class)
624        String foo() throws InterruptedException {
625            return null;
626        }
627      });
628      fail();
629    } catch(CreationException ce) {
630      assertEquals(InterruptedException.class.getName() + " is not compatible with the exceptions (["
631          + RemoteException.class + "]) declared in the CheckedProvider interface ("
632          + RemoteProvider.class.getName() + ")",
633          Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
634    }
635  }
636
637  public void testProviderMethodWithSubclassOfExceptionIsOk() {
638    providesInjector = Guice.createInjector(new AbstractModule() {
639      protected void configure() {
640        install(ThrowingProviderBinder.forModule(this));
641      }
642
643      @SuppressWarnings("unused")
644      @CheckedProvides(RemoteProvider.class)
645      String foo() throws AccessException {
646        throw new AccessException("boo!");
647      }
648    });
649
650    RemoteProvider<String> remoteProvider =
651      providesInjector.getInstance(Key.get(remoteProviderOfString));
652
653    try {
654      remoteProvider.get();
655      fail();
656    } catch (RemoteException expected) {
657      assertTrue(expected instanceof AccessException);
658      assertEquals("boo!", expected.getMessage());
659    }
660  }
661
662  public void testProviderMethodWithSuperclassFails() {
663    try {
664      Guice.createInjector(new AbstractModule() {
665        protected void configure() {
666          install(ThrowingProviderBinder.forModule(this));
667        }
668
669        @SuppressWarnings("unused")
670        @CheckedProvides(RemoteProvider.class)
671        String foo() throws IOException {
672            return null;
673        }
674      });
675      fail();
676    } catch(CreationException ce) {
677      assertEquals(IOException.class.getName() + " is not compatible with the exceptions (["
678          + RemoteException.class + "]) declared in the CheckedProvider interface ("
679          + RemoteProvider.class.getName() + ")",
680          Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
681    }
682  }
683
684  public void testProviderMethodWithRuntimeExceptionsIsOk() throws RemoteException {
685    providesInjector = Guice.createInjector(new AbstractModule() {
686      protected void configure() {
687        install(ThrowingProviderBinder.forModule(this));
688      }
689
690      @SuppressWarnings("unused")
691      @CheckedProvides(RemoteProvider.class)
692      String foo() throws RuntimeException {
693        throw new RuntimeException("boo!");
694      }
695    });
696
697    RemoteProvider<String> remoteProvider =
698      providesInjector.getInstance(Key.get(remoteProviderOfString));
699
700    try {
701      remoteProvider.get();
702      fail();
703    } catch (RuntimeException expected) {
704      assertEquals("boo!", expected.getCause().getMessage());
705    }
706  }
707
708  public void testProviderMethodWithManyExceptions() {
709    try {
710      Guice.createInjector(new AbstractModule() {
711        protected void configure() {
712          install(ThrowingProviderBinder.forModule(this));
713        }
714
715        @SuppressWarnings("unused")
716        @CheckedProvides(RemoteProvider.class)
717        String foo() throws InterruptedException, RuntimeException, RemoteException,
718                            AccessException, TooManyListenersException {
719            return null;
720        }
721      });
722      fail();
723    } catch(CreationException ce) {
724      // The only two that should fail are Interrupted & TooManyListeners.. the rest are OK.
725      List<Message> errors = ImmutableList.copyOf(ce.getErrorMessages());
726      assertEquals(InterruptedException.class.getName() + " is not compatible with the exceptions (["
727          + RemoteException.class + "]) declared in the CheckedProvider interface ("
728          + RemoteProvider.class.getName() + ")",
729          errors.get(0).getMessage());
730      assertEquals(TooManyListenersException.class.getName() + " is not compatible with the exceptions (["
731          + RemoteException.class + "]) declared in the CheckedProvider interface ("
732          + RemoteProvider.class.getName() + ")",
733          errors.get(1).getMessage());
734      assertEquals(2, errors.size());
735    }
736  }
737
738  public void testMoreTypeParameters() {
739    try {
740      Guice.createInjector(new AbstractModule() {
741        protected void configure() {
742          install(ThrowingProviderBinder.forModule(this));
743        }
744
745        @SuppressWarnings("unused")
746        @CheckedProvides(TooManyTypeParameters.class)
747        String foo() {
748            return null;
749        }
750      });
751      fail();
752    } catch(CreationException ce) {
753      assertEquals(TooManyTypeParameters.class.getName() + " has more than one generic type parameter: [T, P]",
754          Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
755    }
756  }
757
758  public void testWrongThrowingProviderType() {
759    try {
760      Guice.createInjector(new AbstractModule() {
761        protected void configure() {
762          install(ThrowingProviderBinder.forModule(this));
763        }
764
765        @SuppressWarnings("unused")
766        @CheckedProvides(WrongThrowingProviderType.class)
767        String foo() {
768            return null;
769        }
770      });
771      fail();
772    } catch(CreationException ce) {
773      assertEquals(WrongThrowingProviderType.class.getName()
774          + " does not properly extend CheckedProvider, the first type parameter of CheckedProvider "
775          + "(java.lang.String) is not a generic type",
776          Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
777    }
778  }
779
780  public void testOneMethodThatIsntGet() {
781    try {
782      Guice.createInjector(new AbstractModule() {
783        protected void configure() {
784          install(ThrowingProviderBinder.forModule(this));
785        }
786
787        @SuppressWarnings("unused")
788        @CheckedProvides(OneNoneGetMethod.class)
789        String foo() {
790            return null;
791        }
792      });
793      fail();
794    } catch(CreationException ce) {
795      assertEquals(OneNoneGetMethod.class.getName()
796          + " may not declare any new methods, but declared " + Classes.toString(OneNoneGetMethod.class.getDeclaredMethods()[0]),
797          Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
798    }
799  }
800
801  public void testManyMethods() {
802    try {
803      Guice.createInjector(new AbstractModule() {
804        protected void configure() {
805          install(ThrowingProviderBinder.forModule(this));
806        }
807
808        @SuppressWarnings("unused")
809        @CheckedProvides(ManyMethods.class)
810        String foo() {
811            return null;
812        }
813      });
814      fail();
815    } catch(CreationException ce) {
816      assertEquals(ManyMethods.class.getName()
817          + " may not declare any new methods, but declared " + Arrays.asList(ManyMethods.class.getDeclaredMethods()),
818          Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
819    }
820  }
821
822  public void testIncorrectPredefinedType_Bind() {
823    try {
824      Guice.createInjector(new AbstractModule() {
825        protected void configure() {
826          ThrowingProviderBinder.create(binder())
827              .bind(StringRemoteProvider.class, Integer.class)
828              .to(new StringRemoteProvider() {
829                public String get() throws RemoteException {
830                  return "A";
831                }
832              });
833        }
834      });
835      fail();
836    } catch(CreationException ce) {
837      assertEquals(StringRemoteProvider.class.getName()
838          + " expects the value type to be java.lang.String, but it was java.lang.Integer",
839          Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
840    }
841  }
842
843  public void testIncorrectPredefinedType_Provides() {
844    try {
845      Guice.createInjector(new AbstractModule() {
846        protected void configure() {
847          install(ThrowingProviderBinder.forModule(this));
848        }
849
850        @SuppressWarnings("unused")
851        @CheckedProvides(StringRemoteProvider.class)
852        Integer foo() {
853            return null;
854        }
855      });
856      fail();
857    } catch(CreationException ce) {
858      assertEquals(StringRemoteProvider.class.getName()
859          + " expects the value type to be java.lang.String, but it was java.lang.Integer",
860          Iterables.getOnlyElement(ce.getErrorMessages()).getMessage());
861    }
862  }
863
864  private static interface TooManyTypeParameters<T, P> extends ThrowingProvider<T, Exception> {
865  }
866
867  private static interface WrongThrowingProviderType<T> extends ThrowingProvider<String, Exception> {
868  }
869
870  private static interface OneNoneGetMethod<T> extends ThrowingProvider<T, Exception> {
871    T bar();
872  }
873
874  private static interface ManyMethods<T> extends ThrowingProvider<T, Exception> {
875    T bar();
876    String baz();
877  }
878}
879