• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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