1/**
2 * Copyright (C) 2006 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;
18
19import static com.google.inject.Asserts.assertContains;
20import static com.google.inject.Asserts.assertEqualsBothWays;
21import static com.google.inject.Asserts.assertNotSerializable;
22import static com.google.inject.Asserts.awaitClear;
23import static java.lang.annotation.RetentionPolicy.RUNTIME;
24
25import com.google.inject.name.Named;
26import com.google.inject.name.Names;
27import com.google.inject.spi.Dependency;
28import com.google.inject.util.Types;
29
30import junit.framework.TestCase;
31
32import java.io.IOException;
33import java.lang.annotation.ElementType;
34import java.lang.annotation.Retention;
35import java.lang.annotation.Target;
36import java.lang.ref.WeakReference;
37import java.lang.reflect.Method;
38import java.lang.reflect.ParameterizedType;
39import java.lang.reflect.Type;
40import java.lang.reflect.TypeVariable;
41import java.util.ArrayList;
42import java.util.List;
43import java.util.Map;
44import java.util.concurrent.atomic.AtomicReference;
45
46/**
47 * @author crazybob@google.com (Bob Lee)
48 */
49public class KeyTest extends TestCase {
50
51  public void foo(List<String> a, List<String> b) {}
52  public void bar(Provider<List<String>> a) {}
53  @Foo String baz;
54  List<? extends CharSequence> wildcardExtends;
55
56  public void testOfType() {
57    Key<Object> k = Key.get(Object.class, Foo.class);
58    Key<Integer> ki = k.ofType(Integer.class);
59    assertEquals(Integer.class, ki.getRawType());
60    assertEquals(Foo.class, ki.getAnnotationType());
61  }
62
63  public void testKeyEquality() {
64    Key<List<String>> a = new Key<List<String>>(Foo.class) {};
65    Key<List<String>> b = Key.get(new TypeLiteral<List<String>>() {}, Foo.class);
66    assertEqualsBothWays(a, b);
67  }
68
69  public void testProviderKey() throws NoSuchMethodException {
70    Key<?> actual = Key.get(getClass().getMethod("foo", List.class, List.class)
71        .getGenericParameterTypes()[0]).providerKey();
72    Key<?> expected = Key.get(getClass().getMethod("bar", Provider.class)
73        .getGenericParameterTypes()[0]);
74    assertEqualsBothWays(expected, actual);
75    assertEquals(expected.toString(), actual.toString());
76  }
77
78  public void testTypeEquality() throws Exception {
79    Method m = getClass().getMethod("foo", List.class, List.class);
80    Type[] types = m.getGenericParameterTypes();
81    assertEquals(types[0], types[1]);
82    Key<List<String>> k = new Key<List<String>>() {};
83    assertEquals(types[0], k.getTypeLiteral().getType());
84    assertFalse(types[0].equals(
85        new Key<List<Integer>>() {}.getTypeLiteral().getType()));
86  }
87
88  /**
89   * Key canonicalizes {@link int.class} to {@code Integer.class}, and
90   * won't expose wrapper types.
91   */
92  public void testPrimitivesAndWrappersAreEqual() {
93    Class[] primitives = new Class[] {
94        boolean.class, byte.class, short.class, int.class, long.class,
95        float.class, double.class, char.class, void.class
96    };
97    Class[] wrappers = new Class[] {
98        Boolean.class, Byte.class, Short.class, Integer.class, Long.class,
99        Float.class, Double.class, Character.class, Void.class
100    };
101
102    for (int t = 0; t < primitives.length; t++) {
103      @SuppressWarnings("unchecked")
104      Key primitiveKey = Key.get(primitives[t]);
105      @SuppressWarnings("unchecked")
106      Key wrapperKey = Key.get(wrappers[t]);
107
108      assertEquals(primitiveKey, wrapperKey);
109      assertEquals(wrappers[t], primitiveKey.getRawType());
110      assertEquals(wrappers[t], wrapperKey.getRawType());
111      assertEquals(wrappers[t], primitiveKey.getTypeLiteral().getType());
112      assertEquals(wrappers[t], wrapperKey.getTypeLiteral().getType());
113    }
114
115    Key<Integer> integerKey = Key.get(Integer.class);
116    Key<Integer> integerKey2 = Key.get(Integer.class, Named.class);
117    Key<Integer> integerKey3 = Key.get(Integer.class, Names.named("int"));
118
119    Class<Integer> intClassLiteral = int.class;
120    assertEquals(integerKey, Key.get(intClassLiteral));
121    assertEquals(integerKey2, Key.get(intClassLiteral, Named.class));
122    assertEquals(integerKey3, Key.get(intClassLiteral, Names.named("int")));
123
124    Type intType = int.class;
125    assertEquals(integerKey, Key.get(intType));
126    assertEquals(integerKey2, Key.get(intType, Named.class));
127    assertEquals(integerKey3, Key.get(intType, Names.named("int")));
128
129    TypeLiteral<Integer> intTypeLiteral = TypeLiteral.get(int.class);
130    assertEquals(integerKey, Key.get(intTypeLiteral));
131    assertEquals(integerKey2, Key.get(intTypeLiteral, Named.class));
132    assertEquals(integerKey3, Key.get(intTypeLiteral, Names.named("int")));
133  }
134
135  public void testSerialization() throws IOException, NoSuchFieldException {
136    assertNotSerializable(Key.get(B.class));
137    assertNotSerializable(Key.get(B.class, Names.named("bee")));
138    assertNotSerializable(Key.get(B.class, Named.class));
139    assertNotSerializable(Key.get(B[].class));
140    assertNotSerializable(Key.get(new TypeLiteral<Map<List<B>, B>>() {}));
141    assertNotSerializable(Key.get(new TypeLiteral<List<B[]>>() {}));
142    assertNotSerializable(Key.get(Types.listOf(Types.subtypeOf(CharSequence.class))));
143  }
144
145  public void testEqualityOfAnnotationTypesAndInstances() throws NoSuchFieldException {
146    Foo instance = getClass().getDeclaredField("baz").getAnnotation(Foo.class);
147    Key<String> keyWithInstance = Key.get(String.class, instance);
148    Key<String> keyWithLiteral = Key.get(String.class, Foo.class);
149    assertEqualsBothWays(keyWithInstance, keyWithLiteral);
150  }
151
152  public void testNonBindingAnnotationOnKey() {
153    try {
154      Key.get(String.class, Deprecated.class);
155      fail();
156    } catch (IllegalArgumentException expected) {
157      assertContains(expected.getMessage(), "java.lang.Deprecated is not a binding annotation. ",
158          "Please annotate it with @BindingAnnotation.");
159    }
160  }
161
162  public void testBindingAnnotationWithoutRuntimeRetention() {
163    try {
164      Key.get(String.class, Bar.class);
165      fail();
166    } catch (IllegalArgumentException expected) {
167      assertContains(expected.getMessage(), Bar.class.getName() + " is not retained at runtime.",
168          "Please annotate it with @Retention(RUNTIME).");
169    }
170  }
171
172  <T> void parameterizedWithVariable(List<T> typeWithVariables) {}
173
174  /** Test for issue 186 */
175  public void testCannotCreateKeysWithTypeVariables() throws NoSuchMethodException {
176    ParameterizedType listOfTType = (ParameterizedType) getClass().getDeclaredMethod(
177        "parameterizedWithVariable", List.class).getGenericParameterTypes()[0];
178
179    TypeLiteral<?> listOfT = TypeLiteral.get(listOfTType);
180    try {
181      Key.get(listOfT);
182      fail("Guice should not allow keys for java.util.List<T>");
183    } catch (ConfigurationException e) {
184      assertContains(e.getMessage(),
185          "java.util.List<T> cannot be used as a key; It is not fully specified.");
186    }
187
188    TypeVariable tType = (TypeVariable) listOfTType.getActualTypeArguments()[0];
189    TypeLiteral<?> t = TypeLiteral.get(tType);
190    try {
191      Key.get(t);
192      fail("Guice should not allow keys for T");
193    } catch (ConfigurationException e) {
194      assertContains(e.getMessage(),
195          "T cannot be used as a key; It is not fully specified.");
196    }
197  }
198
199  public void testCannotGetKeyWithUnspecifiedTypeVariables() {
200    TypeLiteral<Integer> typeLiteral = KeyTest.createTypeLiteral();
201    try {
202      Key.get(typeLiteral);
203      fail("Guice should not allow keys for T");
204    } catch (ConfigurationException e) {
205      assertContains(e.getMessage(),
206          "T cannot be used as a key; It is not fully specified.");
207    }
208  }
209
210  private static <T> TypeLiteral<T> createTypeLiteral() {
211    return new TypeLiteral<T>() {};
212  }
213
214  public void testCannotCreateKeySubclassesWithUnspecifiedTypeVariables() {
215    try {
216      KeyTest.<Integer>createKey();
217      fail("Guice should not allow keys for T");
218    } catch (ConfigurationException e) {
219      assertContains(e.getMessage(),
220          "T cannot be used as a key; It is not fully specified.");
221    }
222  }
223
224  private static <T> Key<T> createKey() {
225    return new Key<T>() {};
226  }
227
228  interface B {}
229
230  @Retention(RUNTIME)
231  @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
232  @BindingAnnotation @interface Foo {}
233
234  @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
235  @BindingAnnotation @interface Bar {}
236
237  class HasTypeParameters<A, B extends List<A> & Runnable, C extends Runnable> {
238    A a; B b; C c;
239  }
240
241  public void testKeysWithDefaultAnnotations() {
242    AllDefaults allDefaults = HasAnnotations.class.getAnnotation(AllDefaults.class);
243    assertEquals(Key.get(Foo.class, allDefaults), Key.get(Foo.class, AllDefaults.class));
244
245    Marker marker = HasAnnotations.class.getAnnotation(Marker.class);
246    assertEquals(Key.get(Foo.class, marker), Key.get(Foo.class, Marker.class));
247
248    Key<?> noDefaults = Key.get(Foo.class, NoDefaults.class);
249    assertNull(noDefaults.getAnnotation());
250    assertEquals(NoDefaults.class, noDefaults.getAnnotationType());
251
252    Key<?> someDefaults = Key.get(Foo.class, SomeDefaults.class);
253    assertNull(someDefaults.getAnnotation());
254    assertEquals(SomeDefaults.class, someDefaults.getAnnotationType());
255  }
256
257  @Retention(RUNTIME)
258  @BindingAnnotation @interface AllDefaults {
259    int v1() default 1;
260    String v2() default "foo";
261  }
262
263  @Retention(RUNTIME)
264  @BindingAnnotation @interface SomeDefaults {
265    int v1() default 1;
266    String v2() default "foo";
267    Class<?> clazz();
268  }
269
270  @Retention(RUNTIME)
271  @BindingAnnotation @interface NoDefaults {
272    int value();
273  }
274
275  @Retention(RUNTIME)
276  @BindingAnnotation @interface Marker {
277  }
278
279  @AllDefaults
280  @Marker
281  class HasAnnotations {}
282
283  public void testAnonymousClassesDontHoldRefs() {
284    final AtomicReference<Provider<List<String>>> stringProvider =
285        new AtomicReference<Provider<List<String>>>();
286    final AtomicReference<Provider<List<Integer>>> intProvider =
287        new AtomicReference<Provider<List<Integer>>>();
288    final Object foo = new Object() {
289      @SuppressWarnings("unused") @Inject List<String> list;
290    };
291    Module module = new AbstractModule() {
292      @Override protected void configure() {
293        bind(new Key<List<String>>() {}).toInstance(new ArrayList<String>());
294        bind(new TypeLiteral<List<Integer>>() {}).toInstance(new ArrayList<Integer>());
295
296        stringProvider.set(getProvider(new Key<List<String>>() {}));
297        intProvider.set(binder().getProvider(Dependency.get(new Key<List<Integer>>() {})));
298
299        binder().requestInjection(new TypeLiteral<Object>() {}, foo);
300      }
301    };
302    WeakReference<Module> moduleRef = new WeakReference<Module>(module);
303    final Injector injector = Guice.createInjector(module);
304    module = null;
305    awaitClear(moduleRef); // Make sure anonymous keys & typeliterals don't hold the module.
306
307    Runnable runner = new Runnable() {
308      @Override public void run() {
309        injector.getInstance(new Key<Typed<String>>() {});
310        injector.getInstance(Key.get(new TypeLiteral<Typed<Integer>>() {}));
311      }
312    };
313    WeakReference<Runnable> runnerRef = new WeakReference<Runnable>(runner);
314    runner.run();
315    runner = null;
316    awaitClear(runnerRef); // also make sure anonymous keys & typeliterals don't hold for JITs
317  }
318
319  static class Typed<T> {}
320
321}
322