Home | History | Annotate | Download | only in inject
      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;
     18 
     19 import static com.google.inject.Asserts.assertEqualsBothWays;
     20 import static com.google.inject.Asserts.assertNotSerializable;
     21 import static com.google.inject.util.Types.arrayOf;
     22 import static com.google.inject.util.Types.listOf;
     23 import static com.google.inject.util.Types.newParameterizedType;
     24 import static com.google.inject.util.Types.newParameterizedTypeWithOwner;
     25 import static com.google.inject.util.Types.setOf;
     26 
     27 import com.google.common.collect.ImmutableList;
     28 import com.google.inject.util.Types;
     29 
     30 import junit.framework.TestCase;
     31 
     32 import java.io.IOException;
     33 import java.lang.reflect.Constructor;
     34 import java.lang.reflect.Field;
     35 import java.lang.reflect.Method;
     36 import java.lang.reflect.Type;
     37 import java.util.AbstractCollection;
     38 import java.util.AbstractList;
     39 import java.util.ArrayList;
     40 import java.util.Arrays;
     41 import java.util.Collection;
     42 import java.util.HashMap;
     43 import java.util.List;
     44 import java.util.Map;
     45 import java.util.Set;
     46 
     47 /**
     48  * This test checks that TypeLiteral can perform type resolution on its members.
     49  *
     50  * @author jessewilson (at) google.com (Jesse Wilson)
     51  */
     52 public class TypeLiteralTypeResolutionTest extends TestCase {
     53   Type arrayListOfString = newParameterizedType(ArrayList.class, String.class);
     54   Type hasGenericFieldsOfShort = newParameterizedTypeWithOwner(
     55       getClass(), HasGenericFields.class, Short.class);
     56   Type hasGenericConstructorOfShort = newParameterizedTypeWithOwner(
     57       getClass(), GenericConstructor.class, Short.class);
     58   Type throwerOfNpe = newParameterizedTypeWithOwner(
     59       getClass(), Thrower.class, NullPointerException.class);
     60   Type hasArrayOfShort = newParameterizedTypeWithOwner(getClass(), HasArray.class, Short.class);
     61   Type hasRelatedOfString = newParameterizedTypeWithOwner(
     62       getClass(), HasRelated.class, String.class, String.class);
     63   Type mapK = Map.class.getTypeParameters()[0];
     64   Type hashMapK = HashMap.class.getTypeParameters()[0];
     65   Type setEntryKV;
     66   Type entryStringInteger = setOf(newParameterizedTypeWithOwner(
     67       Map.class, Map.Entry.class, String.class, Integer.class));
     68   Field list;
     69   Field instance;
     70   Constructor<GenericConstructor> newHasGenericConstructor;
     71   Constructor<Thrower> newThrower;
     72   Constructor newString;
     73   Method stringIndexOf;
     74   Method comparableCompareTo;
     75   Method getArray;
     76   Method getSetOfArray;
     77   Method echo;
     78   Method throwS;
     79 
     80   @Override protected void setUp() throws Exception {
     81     super.setUp();
     82 
     83     list = HasGenericFields.class.getField("list");
     84     instance = HasGenericFields.class.getField("instance");
     85     newHasGenericConstructor = GenericConstructor.class.getConstructor(Object.class, Object.class);
     86     newThrower = Thrower.class.getConstructor();
     87     stringIndexOf = String.class.getMethod("indexOf", String.class);
     88     newString = String.class.getConstructor(String.class);
     89     comparableCompareTo = Comparable.class.getMethod("compareTo", Object.class);
     90     getArray = HasArray.class.getMethod("getArray");
     91     getSetOfArray = HasArray.class.getMethod("getSetOfArray");
     92     echo = HasRelated.class.getMethod("echo", Object.class);
     93     throwS = Thrower.class.getMethod("throwS");
     94     setEntryKV = HashMap.class.getMethod("entrySet").getGenericReturnType();
     95   }
     96 
     97   public void testDirectInheritance() throws NoSuchMethodException {
     98     TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString);
     99     assertEquals(listOf(String.class),
    100         resolver.getReturnType(List.class.getMethod("subList", int.class, int.class)).getType());
    101     assertEquals(ImmutableList.<TypeLiteral<?>>of(TypeLiteral.get(String.class)),
    102         resolver.getParameterTypes(Collection.class.getMethod("add", Object.class)));
    103   }
    104 
    105   public void testGenericSupertype() {
    106     TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString);
    107     assertEquals(newParameterizedType(Collection.class, String.class),
    108         resolver.getSupertype(Collection.class).getType());
    109     assertEquals(newParameterizedType(Iterable.class, String.class),
    110         resolver.getSupertype(Iterable.class).getType());
    111     assertEquals(newParameterizedType(AbstractList.class, String.class),
    112         resolver.getSupertype(AbstractList.class).getType());
    113     assertEquals(Object.class, resolver.getSupertype(Object.class).getType());
    114   }
    115 
    116   public void testRecursiveTypeVariable() {
    117     TypeLiteral<?> resolver = TypeLiteral.get(MyInteger.class);
    118     assertEquals(MyInteger.class, resolver.getParameterTypes(comparableCompareTo).get(0).getType());
    119   }
    120 
    121   interface MyComparable<E extends MyComparable<E>> extends Comparable<E> {}
    122 
    123   static class MyInteger implements MyComparable<MyInteger> {
    124     int value;
    125     public int compareTo(MyInteger o) {
    126       return value - o.value;
    127     }
    128   }
    129 
    130   public void testFields() {
    131     TypeLiteral<?> resolver = TypeLiteral.get(hasGenericFieldsOfShort);
    132     assertEquals(listOf(Short.class), resolver.getFieldType(list).getType());
    133     assertEquals(Short.class, resolver.getFieldType(instance).getType());
    134   }
    135 
    136   static class HasGenericFields<T> {
    137     public List<T> list;
    138     public T instance;
    139   }
    140 
    141   public void testGenericConstructor() throws NoSuchMethodException {
    142     TypeLiteral<?> resolver = TypeLiteral.get(hasGenericConstructorOfShort);
    143     assertEquals(Short.class,
    144         resolver.getParameterTypes(newHasGenericConstructor).get(0).getType());
    145   }
    146 
    147   static class GenericConstructor<S> {
    148     @SuppressWarnings("UnusedDeclaration")
    149     public <T> GenericConstructor(S s, T t) {}
    150   }
    151 
    152   public void testThrowsExceptions() {
    153     TypeLiteral<?> type = TypeLiteral.get(throwerOfNpe);
    154     assertEquals(NullPointerException.class, type.getExceptionTypes(newThrower).get(0).getType());
    155     assertEquals(NullPointerException.class, type.getExceptionTypes(throwS).get(0).getType());
    156   }
    157 
    158   static class Thrower<S extends Exception> {
    159     public Thrower() throws S {}
    160     public void throwS() throws S {}
    161   }
    162 
    163   public void testArrays() {
    164     TypeLiteral<?> resolver = TypeLiteral.get(hasArrayOfShort);
    165     assertEquals(arrayOf(Short.class), resolver.getReturnType(getArray).getType());
    166     assertEquals(setOf(arrayOf(Short.class)), resolver.getReturnType(getSetOfArray).getType());
    167   }
    168 
    169   static interface HasArray<T extends Number> {
    170     T[] getArray();
    171     Set<T[]> getSetOfArray();
    172   }
    173 
    174   public void testRelatedTypeVariables() {
    175     TypeLiteral<?> resolver = TypeLiteral.get(hasRelatedOfString);
    176     assertEquals(String.class, resolver.getParameterTypes(echo).get(0).getType());
    177     assertEquals(String.class, resolver.getReturnType(echo).getType());
    178   }
    179 
    180   interface HasRelated<T, R extends T> {
    181     T echo(R r);
    182   }
    183 
    184   /** Ensure the cache doesn't cache too much */
    185   public void testCachingAndReindexing() throws NoSuchMethodException {
    186     TypeLiteral<?> resolver = TypeLiteral.get(
    187         newParameterizedTypeWithOwner(getClass(), HasLists.class, String.class, Short.class));
    188     assertEquals(listOf(String.class),
    189         resolver.getReturnType(HasLists.class.getMethod("listS")).getType());
    190     assertEquals(listOf(Short.class),
    191         resolver.getReturnType(HasLists.class.getMethod("listT")).getType());
    192   }
    193 
    194   interface HasLists<S, T> {
    195     List<S> listS();
    196     List<T> listT();
    197     List<Map.Entry<S, T>> listEntries();
    198   }
    199 
    200   public void testUnsupportedQueries() throws NoSuchMethodException {
    201     TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString);
    202 
    203     try {
    204       resolver.getExceptionTypes(stringIndexOf);
    205       fail();
    206     } catch (IllegalArgumentException e) {
    207       assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a "
    208           + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
    209     }
    210     try {
    211       resolver.getParameterTypes(stringIndexOf);
    212       fail();
    213     } catch (Exception e) {
    214       assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a "
    215           + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
    216     }
    217     try {
    218       resolver.getReturnType(stringIndexOf);
    219       fail();
    220     } catch (Exception e) {
    221       assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a "
    222           + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
    223     }
    224     try {
    225       resolver.getSupertype(String.class);
    226       fail();
    227     } catch (Exception e) {
    228       assertEquals("class java.lang.String is not a supertype of "
    229           + "java.util.ArrayList<java.lang.String>", e.getMessage());
    230     }
    231     try {
    232       resolver.getExceptionTypes(newString);
    233       fail();
    234     } catch (Exception e) {
    235       assertEquals("public java.lang.String(java.lang.String) does not construct "
    236           + "a supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
    237     }
    238     try {
    239       resolver.getParameterTypes(newString);
    240       fail();
    241     } catch (Exception e) {
    242       assertEquals("public java.lang.String(java.lang.String) does not construct "
    243           + "a supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
    244     }
    245   }
    246 
    247   public void testResolve() {
    248     TypeLiteral<?> typeResolver = TypeLiteral.get(StringIntegerMap.class);
    249     assertEquals(String.class, typeResolver.resolveType(mapK));
    250 
    251     typeResolver = new TypeLiteral<Map<String, Integer>>() {};
    252     assertEquals(String.class, typeResolver.resolveType(mapK));
    253     assertEquals(Types.mapOf(String.class, Integer.class),
    254         typeResolver.getSupertype(Map.class).getType());
    255 
    256     typeResolver = new TypeLiteral<BetterMap<String, Integer>>() {};
    257     assertEquals(String.class, typeResolver.resolveType(mapK));
    258 
    259     typeResolver = new TypeLiteral<BestMap<String, Integer>>() {};
    260     assertEquals(String.class, typeResolver.resolveType(mapK));
    261 
    262     typeResolver = TypeLiteral.get(StringIntegerHashMap.class);
    263     assertEquals(String.class, typeResolver.resolveType(mapK));
    264     assertEquals(String.class, typeResolver.resolveType(hashMapK));
    265     assertEquals(entryStringInteger, typeResolver.resolveType(setEntryKV));
    266     assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType());
    267   }
    268 
    269   public void testOnObject() {
    270     TypeLiteral<?> typeResolver = TypeLiteral.get(Object.class);
    271     assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType());
    272     assertEquals(Object.class, typeResolver.getRawType());
    273 
    274     // interfaces also resolve Object
    275     typeResolver = TypeLiteral.get(Types.setOf(Integer.class));
    276     assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType());
    277   }
    278 
    279   interface StringIntegerMap extends Map<String, Integer> {}
    280   interface BetterMap<K1, V1> extends Map<K1, V1> {}
    281   interface BestMap<K2, V2> extends BetterMap<K2, V2> {}
    282   static class StringIntegerHashMap extends HashMap<String, Integer> {}
    283 
    284   public void testGetSupertype() {
    285     TypeLiteral<AbstractList<String>> listOfString = new TypeLiteral<AbstractList<String>>() {};
    286     assertEquals(Types.newParameterizedType(AbstractCollection.class, String.class),
    287         listOfString.getSupertype(AbstractCollection.class).getType());
    288 
    289     TypeLiteral arrayListOfE = TypeLiteral.get(newParameterizedType(
    290         ArrayList.class, ArrayList.class.getTypeParameters()));
    291     assertEquals(
    292         newParameterizedType(AbstractCollection.class, ArrayList.class.getTypeParameters()),
    293         arrayListOfE.getSupertype(AbstractCollection.class).getType());
    294   }
    295 
    296   public void testGetSupertypeForArraysAsList() {
    297     Class<? extends List> arraysAsListClass = Arrays.asList().getClass();
    298     Type anotherE = arraysAsListClass.getTypeParameters()[0];
    299     TypeLiteral type = TypeLiteral.get(newParameterizedType(AbstractList.class, anotherE));
    300     assertEquals(newParameterizedType(AbstractCollection.class, anotherE),
    301         type.getSupertype(AbstractCollection.class).getType());
    302   }
    303 
    304   public void testWildcards() throws NoSuchFieldException {
    305     TypeLiteral<Parameterized<String>> ofString = new TypeLiteral<Parameterized<String>>() {};
    306 
    307     assertEquals(new TypeLiteral<List<String>>() {}.getType(),
    308         ofString.getFieldType(Parameterized.class.getField("t")).getType());
    309     assertEquals(new TypeLiteral<List<? extends String>>() {}.getType(),
    310         ofString.getFieldType(Parameterized.class.getField("extendsT")).getType());
    311     assertEquals(new TypeLiteral<List<? super String>>() {}.getType(),
    312         ofString.getFieldType(Parameterized.class.getField("superT")).getType());
    313   }
    314 
    315   static class Parameterized<T> {
    316     public List<T> t;
    317     public List<? extends T> extendsT;
    318     public List<? super T> superT;
    319   }
    320 
    321   // TODO(jessewilson): tests for tricky bounded types like <T extends Collection, Serializable>
    322 
    323   public void testEqualsAndHashCode() throws IOException {
    324     TypeLiteral<?> a1 = TypeLiteral.get(arrayListOfString);
    325     TypeLiteral<?> a2 = TypeLiteral.get(arrayListOfString);
    326     TypeLiteral<?> b = TypeLiteral.get(listOf(String.class));
    327     assertEqualsBothWays(a1, a2);
    328     assertNotSerializable(a1);
    329     assertFalse(a1.equals(b));
    330   }
    331 }
    332