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@google.com (Jesse Wilson)
49  */
50 public class InjectionPointTest extends TestCase {
51 
52   public @Inject @Named("a") String foo;
bar(@amed"b") String param)53   public @Inject void bar(@Named("b") String param) {}
54 
55   public static class Constructable {
Constructable(@amed"c") String param)56     @Inject 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("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 
testMethodInjectionPoint()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 
testConstructorInjectionPoint()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 
testUnattachedDependency()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 
testForConstructor()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 
testForConstructorOf()167   public void testForConstructorOf() {
168     InjectionPoint injectionPoint = InjectionPoint.forConstructorOf(Constructable.class);
169     assertEquals(Constructable.class.getName() + ".<init>()", injectionPoint.toString());
170   }
171 
testAddForInstanceMethodsAndFields()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 
testAddForStaticMethodsAndFields()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 {
staticMethod(@amed"a") String a)196     @Inject public static void staticMethod(@Named("a") String a) {}
197     @Inject @Named("c") public static String staticField;
instanceMethod(@amed"d") String d)198     @Inject public void instanceMethod(@Named("d") String d) {}
199     @Inject @Named("f") public String instanceField;
200   }
201 
testAddForParameterizedInjections()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;
ParameterizedInjections(Map<T, T> map)215     @Inject public ParameterizedInjections(Map<T, T> map) {}
216   }
217 
testSignature()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 {
a(String s, int i)233     void a(String s, int i) {}
b()234     int b() {
235       return 0;
236     }
237   }
238   static class Bar {
a(String s, int i)239     public void a(String s, int i) {}
b()240     void b() {}
241   }
242 
testOverrideBehavior()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    */
testSyntheticBridgeMethodsInSubclasses()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 
assertPoints(Iterable<InjectionPoint> points, Class<?> clazz, String... methodNames)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. */
assertPointDependencies(Iterable<InjectionPoint> points, TypeLiteral<?>... literals)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 {
atInject()323     @javax.inject.Inject public void atInject() {}
gInject()324     @com.google.inject.Inject public void gInject() {}
325 
privateAtAndPublicG()326     @javax.inject.Inject private void privateAtAndPublicG() {}
privateGAndPublicAt()327     @com.google.inject.Inject private void privateGAndPublicAt() {}
328 
atFirstThenG()329     @javax.inject.Inject public void atFirstThenG() {}
gFirstThenAt()330     @com.google.inject.Inject public void gFirstThenAt() {}
331   }
332 
333   static class Sub extends Super {
atInject()334     public void atInject() {}
gInject()335     public void gInject() {}
336 
privateAtAndPublicG()337     @com.google.inject.Inject public void privateAtAndPublicG() {}
privateGAndPublicAt()338     @javax.inject.Inject public void privateGAndPublicAt() {}
339 
340     @com.google.inject.Inject
341     @Override
atFirstThenG()342     public void atFirstThenG() {}
343 
344     @javax.inject.Inject
345     @Override
gFirstThenAt()346     public void gFirstThenAt() {}
347   }
348 
349   static class SubSub extends Sub {
privateAtAndPublicG()350     @Override public void privateAtAndPublicG() {}
privateGAndPublicAt()351     @Override public void privateGAndPublicAt() {}
352 
atFirstThenG()353     @Override public void atFirstThenG() {}
gFirstThenAt()354     @Override public void gFirstThenAt() {}
355   }
356 
357   static class RestrictedSuper {
gInject(Provider<String> p)358     @com.google.inject.Inject public void gInject(Provider<String> p) {}
jInject(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