ProviderMethodsTest.java revision 2e39ef748a1c4e4dcab506ccfcdb14ca6e01c9c6
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.spi;
18
19import static com.google.inject.Asserts.assertContains;
20import static java.lang.annotation.RetentionPolicy.RUNTIME;
21
22import com.google.common.collect.ImmutableList;
23import com.google.common.collect.ImmutableSet;
24import com.google.inject.AbstractModule;
25import com.google.inject.Binder;
26import com.google.inject.BindingAnnotation;
27import com.google.inject.CreationException;
28import com.google.inject.Guice;
29import com.google.inject.Inject;
30import com.google.inject.Injector;
31import com.google.inject.Key;
32import com.google.inject.Module;
33import com.google.inject.Provider;
34import com.google.inject.Provides;
35import com.google.inject.Singleton;
36import com.google.inject.internal.ProviderMethod;
37import com.google.inject.internal.ProviderMethodsModule;
38import com.google.inject.name.Named;
39import com.google.inject.name.Names;
40import com.google.inject.util.Types;
41
42import junit.framework.TestCase;
43
44import java.lang.annotation.ElementType;
45import java.lang.annotation.Retention;
46import java.lang.annotation.Target;
47import java.util.List;
48import java.util.Set;
49import java.util.concurrent.atomic.AtomicReference;
50import java.util.logging.Logger;
51
52/**
53 * @author crazybob@google.com (Bob Lee)
54 */
55public class ProviderMethodsTest extends TestCase implements Module {
56
57  @SuppressWarnings("unchecked")
58  public void testProviderMethods() {
59    Injector injector = Guice.createInjector(this);
60
61    Bob bob = injector.getInstance(Bob.class);
62    assertEquals("A Bob", bob.getName());
63
64    Bob clone = injector.getInstance(Bob.class);
65    assertEquals("A Bob", clone.getName());
66
67    assertNotSame(bob, clone);
68    assertSame(bob.getDaughter(), clone.getDaughter());
69
70    Key soleBobKey = Key.get(Bob.class, Sole.class);
71    assertSame(
72        injector.getInstance(soleBobKey),
73        injector.getInstance(soleBobKey)
74    );
75  }
76
77  public void configure(Binder binder) {}
78
79  interface Bob {
80    String getName();
81    Dagny getDaughter();
82  }
83
84  interface Dagny {
85    int getAge();
86  }
87
88  @Provides
89  Bob provideBob(final Dagny dagny) {
90    return new Bob() {
91      public String getName() {
92        return "A Bob";
93      }
94
95      public Dagny getDaughter() {
96        return dagny;
97      }
98    };
99  }
100
101  @Provides
102  @Singleton
103  @Sole
104  Bob provideSoleBob(final Dagny dagny) {
105    return new Bob() {
106      public String getName() {
107        return "Only Bob";
108      }
109
110      public Dagny getDaughter() {
111        return dagny;
112      }
113    };
114  }
115
116  @Provides
117  @Singleton
118  Dagny provideDagny() {
119    return new Dagny() {
120      public int getAge() {
121        return 1;
122      }
123    };
124  }
125
126  @Retention(RUNTIME)
127  @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
128  @BindingAnnotation
129  @interface Sole {}
130
131
132
133// We'll have to make getProvider() support circular dependencies before this
134// will work.
135//
136//  public void testCircularDependency() {
137//    Injector injector = Guice.createInjector(new Module() {
138//      public void configure(Binder binder) {
139//        binder.install(ProviderMethods.from(ProviderMethodsTest.this));
140//      }
141//    });
142//
143//    Foo foo = injector.getInstance(Foo.class);
144//    assertEquals(5, foo.getI());
145//    assertEquals(10, foo.getBar().getI());
146//    assertEquals(5, foo.getBar().getFoo().getI());
147//  }
148//
149//  interface Foo {
150//    Bar getBar();
151//    int getI();
152//  }
153//
154//  interface Bar {
155//    Foo getFoo();
156//    int getI();
157//  }
158//
159//  @Provides Foo newFoo(final Bar bar) {
160//    return new Foo() {
161//
162//      public Bar getBar() {
163//        return bar;
164//      }
165//
166//      public int getI() {
167//        return 5;
168//      }
169//    };
170//  }
171//
172//  @Provides Bar newBar(final Foo foo) {
173//    return new Bar() {
174//
175//      public Foo getFoo() {
176//        return foo;
177//      }
178//
179//      public int getI() {
180//        return 10;
181//      }
182//    };
183//  }
184
185
186  public void testMultipleBindingAnnotations() {
187    try {
188      Guice.createInjector(new AbstractModule() {
189        @Override protected void configure() {}
190
191        @Provides @Named("A") @Blue
192        public String provideString() {
193          return "a";
194        }
195      });
196      fail();
197    } catch (CreationException expected) {
198      assertContains(expected.getMessage(),
199          "more than one annotation annotated with @BindingAnnotation:", "Named", "Blue",
200          "at " + getClass().getName(), ".provideString(ProviderMethodsTest.java:");
201    }
202
203  }
204
205  @Retention(RUNTIME)
206  @BindingAnnotation @interface Blue {}
207
208  public void testGenericProviderMethods() {
209    Injector injector = Guice.createInjector(
210        new ProvideTs<String>("A", "B") {}, new ProvideTs<Integer>(1, 2) {});
211
212    assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("First"))));
213    assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Second"))));
214    assertEquals(ImmutableSet.of("A", "B"),
215        injector.getInstance(Key.get(Types.setOf(String.class))));
216
217    assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("First"))).intValue());
218    assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("Second"))).intValue());
219    assertEquals(ImmutableSet.of(1, 2),
220        injector.getInstance(Key.get(Types.setOf(Integer.class))));
221  }
222
223  abstract class ProvideTs<T> extends AbstractModule {
224    final T first;
225    final T second;
226
227    protected ProvideTs(T first, T second) {
228      this.first = first;
229      this.second = second;
230    }
231
232    @Override protected void configure() {}
233
234    @Named("First") @Provides T provideFirst() {
235      return first;
236    }
237
238    @Named("Second") @Provides T provideSecond() {
239      return second;
240    }
241
242    @Provides Set<T> provideBoth(@Named("First") T first, @Named("Second") T second) {
243      return ImmutableSet.of(first, second);
244    }
245  }
246
247  public void testAutomaticProviderMethods() {
248    Injector injector = Guice.createInjector((Module) new AbstractModule() {
249      @Override protected void configure() { }
250      private int next = 1;
251
252      @Provides @Named("count")
253      public Integer provideCount() {
254        return next++;
255      }
256    });
257
258    assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue());
259    assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue());
260    assertEquals(3, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue());
261  }
262
263  /**
264   * If the user installs provider methods for the module manually, that shouldn't cause a double
265   * binding of the provider methods' types.
266   */
267  public void testAutomaticProviderMethodsDoNotCauseDoubleBinding() {
268    Module installsSelf = new AbstractModule() {
269      @Override protected void configure() {
270        install(this);
271        bind(Integer.class).toInstance(5);
272      }
273      @Provides public String provideString(Integer count) {
274        return "A" + count;
275      }
276    };
277
278    Injector injector = Guice.createInjector(installsSelf);
279    assertEquals("A5", injector.getInstance(String.class));
280  }
281
282  public void testWildcardProviderMethods() {
283    final List<String> strings = ImmutableList.of("A", "B", "C");
284    final List<Number> numbers = ImmutableList.<Number>of(1, 2, 3);
285
286    Injector injector = Guice.createInjector(new AbstractModule() {
287      @Override protected void configure() {
288        @SuppressWarnings("unchecked")
289        Key<List<? super Integer>> listOfSupertypesOfInteger = (Key<List<? super Integer>>)
290            Key.get(Types.listOf(Types.supertypeOf(Integer.class)));
291        bind(listOfSupertypesOfInteger).toInstance(numbers);
292      }
293      @Provides public List<? extends CharSequence> provideCharSequences() {
294        return strings;
295      }
296      @Provides public Class<?> provideType() {
297        return Float.class;
298      }
299    });
300
301    assertSame(strings, injector.getInstance(HasWildcardInjection.class).charSequences);
302    assertSame(numbers, injector.getInstance(HasWildcardInjection.class).numbers);
303    assertSame(Float.class, injector.getInstance(HasWildcardInjection.class).type);
304  }
305
306  static class HasWildcardInjection {
307    @Inject List<? extends CharSequence> charSequences;
308    @Inject List<? super Integer> numbers;
309    @Inject Class<?> type;
310  }
311
312  public void testProviderMethodDependenciesAreExposed() {
313    Injector injector = Guice.createInjector(new AbstractModule() {
314      @Override protected void configure() {
315        bind(Integer.class).toInstance(50);
316        bindConstant().annotatedWith(Names.named("units")).to("Kg");
317      }
318      @Provides @Named("weight") String provideWeight(Integer count, @Named("units") String units) {
319        return count + units;
320      }
321    });
322
323    ProviderInstanceBinding<?> binding = (ProviderInstanceBinding<?>) injector.getBinding(
324        Key.get(String.class, Names.named("weight")));
325    assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Integer.class)),
326        Dependency.get(Key.get(String.class, Names.named("units")))),
327        binding.getDependencies());
328  }
329
330  public void testNonModuleProviderMethods() {
331    final Object methodsObject = new Object() {
332      @Provides @Named("foo") String provideFoo() {
333        return "foo-value";
334      }
335    };
336
337    Module module = new AbstractModule() {
338      @Override protected void configure() {
339        install(ProviderMethodsModule.forObject(methodsObject));
340      }
341    };
342
343    Injector injector = Guice.createInjector(module);
344
345    Key<String> key = Key.get(String.class, Names.named("foo"));
346    assertEquals("foo-value", injector.getInstance(key));
347
348    // Test the provider method object itself. This makes sure getInstance works, since GIN uses it
349    List<Element> elements = Elements.getElements(module);
350    assertEquals(1, elements.size());
351
352    Element element = elements.get(0);
353    assertTrue(element + " instanceof ProviderInstanceBinding",
354        element instanceof ProviderInstanceBinding);
355
356    ProviderInstanceBinding binding = (ProviderInstanceBinding) element;
357    Provider provider = binding.getProviderInstance();
358    assertEquals(ProviderMethod.class, provider.getClass());
359    assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());
360  }
361
362  public void testVoidProviderMethods() {
363    try {
364      Guice.createInjector(new AbstractModule() {
365        @Override protected void configure() {}
366
367        @Provides void provideFoo() {}
368      });
369      fail();
370    } catch (CreationException expected) {
371      assertContains(expected.getMessage(),
372          "1) Provider methods must return a value. Do not return void.",
373          getClass().getName(), ".provideFoo(ProviderMethodsTest.java:");
374    }
375  }
376
377  public void testInjectsJustOneLogger() {
378    AtomicReference<Logger> loggerRef = new AtomicReference<Logger>();
379    Injector injector = Guice.createInjector(new FooModule(loggerRef));
380
381    assertNull(loggerRef.get());
382    injector.getInstance(Integer.class);
383    Logger lastLogger = loggerRef.getAndSet(null);
384    assertNotNull(lastLogger);
385    injector.getInstance(Integer.class);
386    assertSame(lastLogger, loggerRef.get());
387
388    assertEquals(FooModule.class.getName() + ".foo", lastLogger.getName());
389  }
390
391  private static class FooModule extends AbstractModule {
392    private final AtomicReference<Logger> loggerRef;
393
394    public FooModule(AtomicReference<Logger> loggerRef) {
395      this.loggerRef = loggerRef;
396    }
397
398    @Override protected void configure() {}
399
400    @SuppressWarnings("unused")
401    @Provides Integer foo(Logger logger) {
402      loggerRef.set(logger);
403      return 42;
404    }
405  }
406}