1 /* 2 * Copyright (C) 2008 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 17 package com.google.inject.spi; 18 19 import static com.google.common.collect.Iterables.getOnlyElement; 20 import static com.google.common.truth.Truth.assertThat; 21 import static com.google.inject.Asserts.assertContains; 22 import static com.google.inject.Asserts.assertEqualsBothWays; 23 import static com.google.inject.Asserts.assertNotSerializable; 24 import static com.google.inject.name.Names.named; 25 26 import com.google.common.collect.ImmutableList; 27 import com.google.common.collect.ImmutableSet; 28 import com.google.inject.ConfigurationException; 29 import com.google.inject.Inject; 30 import com.google.inject.Key; 31 import com.google.inject.Provider; 32 import com.google.inject.TypeLiteral; 33 import com.google.inject.internal.Annotations; 34 import com.google.inject.internal.ErrorsException; 35 import com.google.inject.name.Named; 36 import com.google.inject.spi.InjectionPoint.Signature; 37 import java.io.IOException; 38 import java.lang.reflect.Constructor; 39 import java.lang.reflect.Field; 40 import java.lang.reflect.Method; 41 import java.util.HashSet; 42 import java.util.LinkedHashSet; 43 import java.util.Map; 44 import java.util.Set; 45 import junit.framework.TestCase; 46 47 /** @author jessewilson@google.com (Jesse Wilson) */ 48 public class InjectionPointTest extends TestCase { 49 50 public @Inject @Named("a") String foo; 51 bar(@amed"b") String param)52 public @Inject void bar(@Named("b") String param) {} 53 54 public static class Constructable { 55 @Inject Constructable(@amed"c") String param)56 public Constructable(@Named("c") String param) {} 57 } 58 testFieldInjectionPoint()59 public void testFieldInjectionPoint() throws NoSuchFieldException, IOException, ErrorsException { 60 TypeLiteral<?> typeLiteral = TypeLiteral.get(getClass()); 61 Field fooField = getClass().getField("foo"); 62 63 InjectionPoint injectionPoint = new InjectionPoint(typeLiteral, fooField, false); 64 assertSame(fooField, injectionPoint.getMember()); 65 assertFalse(injectionPoint.isOptional()); 66 assertEquals(getClass().getName() + ".foo", injectionPoint.toString()); 67 assertEqualsBothWays(injectionPoint, new InjectionPoint(typeLiteral, fooField, false)); 68 assertNotSerializable(injectionPoint); 69 70 Dependency<?> dependency = getOnlyElement(injectionPoint.getDependencies()); 71 assertEquals( 72 "Key[type=java.lang.String, annotation=@com.google.inject.name.Named(value=" 73 + Annotations.memberValueString("a") 74 + ")]@" 75 + getClass().getName() 76 + ".foo", 77 dependency.toString()); 78 assertEquals(fooField, dependency.getInjectionPoint().getMember()); 79 assertEquals(-1, dependency.getParameterIndex()); 80 assertEquals(Key.get(String.class, named("a")), dependency.getKey()); 81 assertFalse(dependency.isNullable()); 82 assertNotSerializable(dependency); 83 assertEqualsBothWays( 84 dependency, 85 getOnlyElement(new InjectionPoint(typeLiteral, fooField, false).getDependencies())); 86 } 87 testMethodInjectionPoint()88 public void testMethodInjectionPoint() throws Exception { 89 TypeLiteral<?> typeLiteral = TypeLiteral.get(getClass()); 90 91 Method barMethod = getClass().getMethod("bar", String.class); 92 InjectionPoint injectionPoint = new InjectionPoint(typeLiteral, barMethod, false); 93 assertSame(barMethod, injectionPoint.getMember()); 94 assertFalse(injectionPoint.isOptional()); 95 assertEquals(getClass().getName() + ".bar()", injectionPoint.toString()); 96 assertEqualsBothWays(injectionPoint, new InjectionPoint(typeLiteral, barMethod, false)); 97 assertNotSerializable(injectionPoint); 98 99 Dependency<?> dependency = getOnlyElement(injectionPoint.getDependencies()); 100 assertEquals( 101 "Key[type=java.lang.String, annotation=@com.google.inject.name.Named(value=" 102 + Annotations.memberValueString("b") 103 + ")]@" 104 + getClass().getName() 105 + ".bar()[0]", 106 dependency.toString()); 107 assertEquals(barMethod, dependency.getInjectionPoint().getMember()); 108 assertEquals(0, dependency.getParameterIndex()); 109 assertEquals(Key.get(String.class, named("b")), dependency.getKey()); 110 assertFalse(dependency.isNullable()); 111 assertNotSerializable(dependency); 112 assertEqualsBothWays( 113 dependency, 114 getOnlyElement(new InjectionPoint(typeLiteral, barMethod, false).getDependencies())); 115 } 116 testConstructorInjectionPoint()117 public void testConstructorInjectionPoint() 118 throws NoSuchMethodException, IOException, ErrorsException { 119 TypeLiteral<?> typeLiteral = TypeLiteral.get(Constructable.class); 120 121 Constructor<?> constructor = Constructable.class.getConstructor(String.class); 122 InjectionPoint injectionPoint = new InjectionPoint(typeLiteral, constructor); 123 assertSame(constructor, injectionPoint.getMember()); 124 assertFalse(injectionPoint.isOptional()); 125 assertEquals(Constructable.class.getName() + ".<init>()", injectionPoint.toString()); 126 assertEqualsBothWays(injectionPoint, new InjectionPoint(typeLiteral, constructor)); 127 assertNotSerializable(injectionPoint); 128 129 Dependency<?> dependency = getOnlyElement(injectionPoint.getDependencies()); 130 assertEquals( 131 "Key[type=java.lang.String, annotation=@com.google.inject.name.Named(value=" 132 + Annotations.memberValueString("c") 133 + ")]@" 134 + Constructable.class.getName() 135 + ".<init>()[0]", 136 dependency.toString()); 137 assertEquals(constructor, dependency.getInjectionPoint().getMember()); 138 assertEquals(0, dependency.getParameterIndex()); 139 assertEquals(Key.get(String.class, named("c")), dependency.getKey()); 140 assertFalse(dependency.isNullable()); 141 assertNotSerializable(dependency); 142 assertEqualsBothWays( 143 dependency, getOnlyElement(new InjectionPoint(typeLiteral, constructor).getDependencies())); 144 } 145 testUnattachedDependency()146 public void testUnattachedDependency() throws IOException { 147 Dependency<String> dependency = Dependency.get(Key.get(String.class, named("d"))); 148 assertEquals( 149 "Key[type=java.lang.String, annotation=@com.google.inject.name.Named(value=" 150 + Annotations.memberValueString("d") 151 + ")]", 152 dependency.toString()); 153 assertNull(dependency.getInjectionPoint()); 154 assertEquals(-1, dependency.getParameterIndex()); 155 assertEquals(Key.get(String.class, named("d")), dependency.getKey()); 156 assertTrue(dependency.isNullable()); 157 assertNotSerializable(dependency); 158 assertEqualsBothWays(dependency, Dependency.get(Key.get(String.class, named("d")))); 159 } 160 testForConstructor()161 public void testForConstructor() throws NoSuchMethodException { 162 Constructor<HashSet> constructor = HashSet.class.getConstructor(); 163 TypeLiteral<HashSet<String>> hashSet = new TypeLiteral<HashSet<String>>() {}; 164 165 InjectionPoint injectionPoint = InjectionPoint.forConstructor(constructor, hashSet); 166 assertSame(constructor, injectionPoint.getMember()); 167 assertEquals(ImmutableList.<Dependency>of(), injectionPoint.getDependencies()); 168 assertFalse(injectionPoint.isOptional()); 169 170 try { 171 InjectionPoint.forConstructor(constructor, new TypeLiteral<LinkedHashSet<String>>() {}); 172 fail("Expected ConfigurationException"); 173 } catch (ConfigurationException expected) { 174 assertContains( 175 expected.getMessage(), 176 "java.util.LinkedHashSet<java.lang.String>", 177 " does not define java.util.HashSet.<init>()", 178 " while locating java.util.LinkedHashSet<java.lang.String>"); 179 } 180 181 try { 182 InjectionPoint.forConstructor((Constructor) constructor, new TypeLiteral<Set<String>>() {}); 183 fail("Expected ConfigurationException"); 184 } catch (ConfigurationException expected) { 185 assertContains( 186 expected.getMessage(), 187 "java.util.Set<java.lang.String>", 188 " does not define java.util.HashSet.<init>()", 189 " while locating java.util.Set<java.lang.String>"); 190 } 191 } 192 testForConstructorOf()193 public void testForConstructorOf() { 194 InjectionPoint injectionPoint = InjectionPoint.forConstructorOf(Constructable.class); 195 assertEquals(Constructable.class.getName() + ".<init>()", injectionPoint.toString()); 196 } 197 testAddForInstanceMethodsAndFields()198 public void testAddForInstanceMethodsAndFields() throws Exception { 199 Method instanceMethod = HasInjections.class.getMethod("instanceMethod", String.class); 200 Field instanceField = HasInjections.class.getField("instanceField"); 201 Field instanceField2 = HasInjections.class.getField("instanceField2"); 202 203 TypeLiteral<HasInjections> type = TypeLiteral.get(HasInjections.class); 204 Set<InjectionPoint> injectionPoints = 205 InjectionPoint.forInstanceMethodsAndFields(HasInjections.class); 206 // there is a defined order. assert on it 207 assertThat(injectionPoints) 208 .containsExactly( 209 new InjectionPoint(type, instanceField, false), 210 new InjectionPoint(type, instanceField2, false), 211 new InjectionPoint(type, instanceMethod, false)) 212 .inOrder(); 213 } 214 testAddForStaticMethodsAndFields()215 public void testAddForStaticMethodsAndFields() throws Exception { 216 Method staticMethod = HasInjections.class.getMethod("staticMethod", String.class); 217 Field staticField = HasInjections.class.getField("staticField"); 218 Field staticField2 = HasInjections.class.getField("staticField2"); 219 220 Set<InjectionPoint> injectionPoints = 221 InjectionPoint.forStaticMethodsAndFields(HasInjections.class); 222 TypeLiteral<HasInjections> type = TypeLiteral.get(HasInjections.class); 223 assertThat(injectionPoints) 224 .containsExactly( 225 new InjectionPoint(type, staticField, false), 226 new InjectionPoint(type, staticField2, false), 227 new InjectionPoint(type, staticMethod, false)) 228 .inOrder(); 229 } 230 231 static class HasInjections { 232 @Inject staticMethod(@amed"a") String a)233 public static void staticMethod(@Named("a") String a) {} 234 235 @Inject 236 @Named("c") 237 public static String staticField; 238 239 @Inject 240 @Named("c") 241 public static String staticField2; 242 243 @Inject instanceMethod(@amed"d") String d)244 public void instanceMethod(@Named("d") String d) {} 245 246 @Inject 247 @Named("f") 248 public String instanceField; 249 250 @Inject 251 @Named("f") 252 public String instanceField2; 253 } 254 testAddForParameterizedInjections()255 public void testAddForParameterizedInjections() { 256 TypeLiteral<?> type = new TypeLiteral<ParameterizedInjections<String>>() {}; 257 258 InjectionPoint constructor = InjectionPoint.forConstructorOf(type); 259 assertEquals( 260 new Key<Map<String, String>>() {}, getOnlyElement(constructor.getDependencies()).getKey()); 261 262 InjectionPoint field = getOnlyElement(InjectionPoint.forInstanceMethodsAndFields(type)); 263 assertEquals(new Key<Set<String>>() {}, getOnlyElement(field.getDependencies()).getKey()); 264 } 265 266 static class ParameterizedInjections<T> { 267 @Inject Set<T> setOfTees; 268 269 @Inject ParameterizedInjections(Map<T, T> map)270 public ParameterizedInjections(Map<T, T> map) {} 271 } 272 testSignature()273 public void testSignature() throws Exception { 274 Signature fooA = new Signature(Foo.class.getDeclaredMethod("a", String.class, int.class)); 275 Signature fooB = new Signature(Foo.class.getDeclaredMethod("b")); 276 Signature barA = new Signature(Bar.class.getDeclaredMethod("a", String.class, int.class)); 277 Signature barB = new Signature(Bar.class.getDeclaredMethod("b")); 278 279 assertEquals(fooA.hashCode(), barA.hashCode()); 280 assertEquals(fooB.hashCode(), barB.hashCode()); 281 assertEquals(fooA, barA); 282 assertEquals(fooB, barB); 283 } 284 285 static class Foo { a(String s, int i)286 void a(String s, int i) {} 287 b()288 int b() { 289 return 0; 290 } 291 } 292 293 static class Bar { a(String s, int i)294 public void a(String s, int i) {} 295 b()296 void b() {} 297 } 298 testOverrideBehavior()299 public void testOverrideBehavior() { 300 Set<InjectionPoint> points; 301 302 points = InjectionPoint.forInstanceMethodsAndFields(Super.class); 303 assertEquals(points.toString(), 6, points.size()); 304 assertPoints( 305 points, 306 Super.class, 307 "atInject", 308 "gInject", 309 "privateAtAndPublicG", 310 "privateGAndPublicAt", 311 "atFirstThenG", 312 "gFirstThenAt"); 313 314 points = InjectionPoint.forInstanceMethodsAndFields(Sub.class); 315 assertEquals(points.toString(), 7, points.size()); 316 // Superclass will always have is private members injected, 317 // and 'gInject' was last @Injected in Super, so that remains the owner 318 assertPoints(points, Super.class, "privateAtAndPublicG", "privateGAndPublicAt", "gInject"); 319 // Subclass also has the "private" methods, but they do not override 320 // the superclass' methods, and it now owns the inject2 methods. 321 assertPoints( 322 points, 323 Sub.class, 324 "privateAtAndPublicG", 325 "privateGAndPublicAt", 326 "atFirstThenG", 327 "gFirstThenAt"); 328 329 points = InjectionPoint.forInstanceMethodsAndFields(SubSub.class); 330 assertEquals(points.toString(), 6, points.size()); 331 // Superclass still has all the injection points it did before.. 332 assertPoints(points, Super.class, "privateAtAndPublicG", "privateGAndPublicAt", "gInject"); 333 // Subclass is missing the privateGAndPublicAt because it first became public with 334 // javax.inject.Inject and was overrode without an annotation, which means it 335 // disappears. (It was guice @Inject in Super, but it was private there, so it doesn't 336 // effect the annotations of the subclasses.) 337 assertPoints(points, Sub.class, "privateAtAndPublicG", "atFirstThenG", "gFirstThenAt"); 338 } 339 340 /** 341 * This test serves two purposes: 1) It makes sure that the bridge methods javax generates don't 342 * stop us from injecting superclass methods in the case of javax.inject.Inject. This would happen 343 * prior to java8 (where javac didn't copy annotations from the superclass into the subclass 344 * method when it generated the bridge methods). 345 * 346 * <p>2) It makes sure that the methods we're going to inject have the correct generic types. 347 * Java8 copies the annotations from super to subclasses, but it doesn't copy the generic type 348 * information. Guice would naively consider the subclass an injectable method and eject the 349 * superclass from the 'overrideIndex', leaving only a class with improper generic types. 350 */ testSyntheticBridgeMethodsInSubclasses()351 public void testSyntheticBridgeMethodsInSubclasses() { 352 Set<InjectionPoint> points; 353 354 points = InjectionPoint.forInstanceMethodsAndFields(RestrictedSuper.class); 355 assertPointDependencies(points, new TypeLiteral<Provider<String>>() {}); 356 assertEquals(points.toString(), 2, points.size()); 357 assertPoints(points, RestrictedSuper.class, "jInject", "gInject"); 358 359 points = InjectionPoint.forInstanceMethodsAndFields(ExposedSub.class); 360 assertPointDependencies(points, new TypeLiteral<Provider<String>>() {}); 361 assertEquals(points.toString(), 2, points.size()); 362 assertPoints(points, RestrictedSuper.class, "jInject", "gInject"); 363 } 364 assertPoints( Iterable<InjectionPoint> points, Class<?> clazz, String... methodNames)365 private void assertPoints( 366 Iterable<InjectionPoint> points, Class<?> clazz, String... methodNames) { 367 Set<String> methods = new HashSet<String>(); 368 for (InjectionPoint point : points) { 369 if (point.getDeclaringType().getRawType() == clazz) { 370 methods.add(point.getMember().getName()); 371 } 372 } 373 assertEquals(points.toString(), ImmutableSet.copyOf(methodNames), methods); 374 } 375 376 /** Asserts that each injection point has the specified dependencies, in the given order. */ assertPointDependencies( Iterable<InjectionPoint> points, TypeLiteral<?>... literals)377 private void assertPointDependencies( 378 Iterable<InjectionPoint> points, TypeLiteral<?>... literals) { 379 for (InjectionPoint point : points) { 380 assertEquals(literals.length, point.getDependencies().size()); 381 for (Dependency<?> dep : point.getDependencies()) { 382 assertEquals(literals[dep.getParameterIndex()], dep.getKey().getTypeLiteral()); 383 } 384 } 385 } 386 387 static class Super { 388 @javax.inject.Inject atInject()389 public void atInject() {} 390 391 @com.google.inject.Inject gInject()392 public void gInject() {} 393 394 @javax.inject.Inject privateAtAndPublicG()395 private void privateAtAndPublicG() {} 396 397 @com.google.inject.Inject privateGAndPublicAt()398 private void privateGAndPublicAt() {} 399 400 @javax.inject.Inject atFirstThenG()401 public void atFirstThenG() {} 402 403 @com.google.inject.Inject gFirstThenAt()404 public void gFirstThenAt() {} 405 } 406 407 static class Sub extends Super { 408 @Override 409 @SuppressWarnings("OverridesJavaxInjectableMethod") atInject()410 public void atInject() {} 411 412 @Override 413 @SuppressWarnings("OverridesGuiceInjectableMethod") gInject()414 public void gInject() {} 415 416 @com.google.inject.Inject privateAtAndPublicG()417 public void privateAtAndPublicG() {} 418 419 @javax.inject.Inject privateGAndPublicAt()420 public void privateGAndPublicAt() {} 421 422 @com.google.inject.Inject 423 @Override atFirstThenG()424 public void atFirstThenG() {} 425 426 @javax.inject.Inject 427 @Override gFirstThenAt()428 public void gFirstThenAt() {} 429 } 430 431 static class SubSub extends Sub { 432 @SuppressWarnings("OverridesGuiceInjectableMethod") 433 @Override privateAtAndPublicG()434 public void privateAtAndPublicG() {} 435 436 @SuppressWarnings("OverridesJavaxInjectableMethod") 437 @Override privateGAndPublicAt()438 public void privateGAndPublicAt() {} 439 440 @SuppressWarnings("OverridesGuiceInjectableMethod") 441 @Override atFirstThenG()442 public void atFirstThenG() {} 443 444 @SuppressWarnings("OverridesGuiceInjectableMethod") 445 @Override gFirstThenAt()446 public void gFirstThenAt() {} 447 } 448 449 static class RestrictedSuper { 450 @com.google.inject.Inject gInject(Provider<String> p)451 public void gInject(Provider<String> p) {} 452 453 @javax.inject.Inject jInject(Provider<String> p)454 public void jInject(Provider<String> p) {} 455 } 456 457 public static class ExposedSub extends RestrictedSuper { 458 // The subclass may generate bridge/synthetic methods to increase the visibility 459 // of the superclass methods, since the superclass was package-private but this is public. 460 } 461 } 462