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