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