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