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