Home | History | Annotate | Download | only in spi
      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