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.gson.reflect; 18 19 import com.google.gson.internal.$Gson$Types; 20 import java.lang.reflect.GenericArrayType; 21 import java.lang.reflect.ParameterizedType; 22 import java.lang.reflect.Type; 23 import java.lang.reflect.TypeVariable; 24 import java.util.HashMap; 25 import java.util.Map; 26 import java.util.Objects; 27 28 /** 29 * Represents a generic type {@code T}. Java doesn't yet provide a way to 30 * represent generic types, so this class does. Forces clients to create a 31 * subclass of this class which enables retrieval the type information even at 32 * runtime. 33 * 34 * <p>For example, to create a type literal for {@code List<String>}, you can 35 * create an empty anonymous class: 36 * 37 * <p> 38 * {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};} 39 * 40 * <p>Capturing a type variable as type argument of a {@code TypeToken} should 41 * be avoided. Due to type erasure the runtime type of a type variable is not 42 * available to Gson and therefore it cannot provide the functionality one 43 * might expect, which gives a false sense of type-safety at compilation time 44 * and can lead to an unexpected {@code ClassCastException} at runtime. 45 * 46 * <p>If the type arguments of the parameterized type are only available at 47 * runtime, for example when you want to create a {@code List<E>} based on 48 * a {@code Class<E>} representing the element type, the method 49 * {@link #getParameterized(Type, Type...)} can be used. 50 * 51 * @author Bob Lee 52 * @author Sven Mawson 53 * @author Jesse Wilson 54 */ 55 public class TypeToken<T> { 56 private final Class<? super T> rawType; 57 private final Type type; 58 private final int hashCode; 59 60 /** 61 * Constructs a new type literal. Derives represented class from type 62 * parameter. 63 * 64 * <p>Clients create an empty anonymous subclass. Doing so embeds the type 65 * parameter in the anonymous class's type hierarchy so we can reconstitute it 66 * at runtime despite erasure. 67 */ 68 @SuppressWarnings("unchecked") TypeToken()69 protected TypeToken() { 70 this.type = getTypeTokenTypeArgument(); 71 this.rawType = (Class<? super T>) $Gson$Types.getRawType(type); 72 this.hashCode = type.hashCode(); 73 } 74 75 /** 76 * Unsafe. Constructs a type literal manually. 77 */ 78 @SuppressWarnings("unchecked") TypeToken(Type type)79 private TypeToken(Type type) { 80 this.type = $Gson$Types.canonicalize(Objects.requireNonNull(type)); 81 this.rawType = (Class<? super T>) $Gson$Types.getRawType(this.type); 82 this.hashCode = this.type.hashCode(); 83 } 84 85 /** 86 * Verifies that {@code this} is an instance of a direct subclass of TypeToken and 87 * returns the type argument for {@code T} in {@link $Gson$Types#canonicalize 88 * canonical form}. 89 */ getTypeTokenTypeArgument()90 private Type getTypeTokenTypeArgument() { 91 Type superclass = getClass().getGenericSuperclass(); 92 if (superclass instanceof ParameterizedType) { 93 ParameterizedType parameterized = (ParameterizedType) superclass; 94 if (parameterized.getRawType() == TypeToken.class) { 95 return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]); 96 } 97 } 98 // Check for raw TypeToken as superclass 99 else if (superclass == TypeToken.class) { 100 throw new IllegalStateException("TypeToken must be created with a type argument: new TypeToken<...>() {}; " 101 + "When using code shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved."); 102 } 103 104 // User created subclass of subclass of TypeToken 105 throw new IllegalStateException("Must only create direct subclasses of TypeToken"); 106 } 107 108 /** 109 * Returns the raw (non-generic) type for this type. 110 */ getRawType()111 public final Class<? super T> getRawType() { 112 return rawType; 113 } 114 115 /** 116 * Gets underlying {@code Type} instance. 117 */ getType()118 public final Type getType() { 119 return type; 120 } 121 122 /** 123 * Check if this type is assignable from the given class object. 124 * 125 * @deprecated this implementation may be inconsistent with javac for types 126 * with wildcards. 127 */ 128 @Deprecated isAssignableFrom(Class<?> cls)129 public boolean isAssignableFrom(Class<?> cls) { 130 return isAssignableFrom((Type) cls); 131 } 132 133 /** 134 * Check if this type is assignable from the given Type. 135 * 136 * @deprecated this implementation may be inconsistent with javac for types 137 * with wildcards. 138 */ 139 @Deprecated isAssignableFrom(Type from)140 public boolean isAssignableFrom(Type from) { 141 if (from == null) { 142 return false; 143 } 144 145 if (type.equals(from)) { 146 return true; 147 } 148 149 if (type instanceof Class<?>) { 150 return rawType.isAssignableFrom($Gson$Types.getRawType(from)); 151 } else if (type instanceof ParameterizedType) { 152 return isAssignableFrom(from, (ParameterizedType) type, 153 new HashMap<String, Type>()); 154 } else if (type instanceof GenericArrayType) { 155 return rawType.isAssignableFrom($Gson$Types.getRawType(from)) 156 && isAssignableFrom(from, (GenericArrayType) type); 157 } else { 158 throw buildUnexpectedTypeError( 159 type, Class.class, ParameterizedType.class, GenericArrayType.class); 160 } 161 } 162 163 /** 164 * Check if this type is assignable from the given type token. 165 * 166 * @deprecated this implementation may be inconsistent with javac for types 167 * with wildcards. 168 */ 169 @Deprecated isAssignableFrom(TypeToken<?> token)170 public boolean isAssignableFrom(TypeToken<?> token) { 171 return isAssignableFrom(token.getType()); 172 } 173 174 /** 175 * Private helper function that performs some assignability checks for 176 * the provided GenericArrayType. 177 */ isAssignableFrom(Type from, GenericArrayType to)178 private static boolean isAssignableFrom(Type from, GenericArrayType to) { 179 Type toGenericComponentType = to.getGenericComponentType(); 180 if (toGenericComponentType instanceof ParameterizedType) { 181 Type t = from; 182 if (from instanceof GenericArrayType) { 183 t = ((GenericArrayType) from).getGenericComponentType(); 184 } else if (from instanceof Class<?>) { 185 Class<?> classType = (Class<?>) from; 186 while (classType.isArray()) { 187 classType = classType.getComponentType(); 188 } 189 t = classType; 190 } 191 return isAssignableFrom(t, (ParameterizedType) toGenericComponentType, 192 new HashMap<String, Type>()); 193 } 194 // No generic defined on "to"; therefore, return true and let other 195 // checks determine assignability 196 return true; 197 } 198 199 /** 200 * Private recursive helper function to actually do the type-safe checking 201 * of assignability. 202 */ isAssignableFrom(Type from, ParameterizedType to, Map<String, Type> typeVarMap)203 private static boolean isAssignableFrom(Type from, ParameterizedType to, 204 Map<String, Type> typeVarMap) { 205 206 if (from == null) { 207 return false; 208 } 209 210 if (to.equals(from)) { 211 return true; 212 } 213 214 // First figure out the class and any type information. 215 Class<?> clazz = $Gson$Types.getRawType(from); 216 ParameterizedType ptype = null; 217 if (from instanceof ParameterizedType) { 218 ptype = (ParameterizedType) from; 219 } 220 221 // Load up parameterized variable info if it was parameterized. 222 if (ptype != null) { 223 Type[] tArgs = ptype.getActualTypeArguments(); 224 TypeVariable<?>[] tParams = clazz.getTypeParameters(); 225 for (int i = 0; i < tArgs.length; i++) { 226 Type arg = tArgs[i]; 227 TypeVariable<?> var = tParams[i]; 228 while (arg instanceof TypeVariable<?>) { 229 TypeVariable<?> v = (TypeVariable<?>) arg; 230 arg = typeVarMap.get(v.getName()); 231 } 232 typeVarMap.put(var.getName(), arg); 233 } 234 235 // check if they are equivalent under our current mapping. 236 if (typeEquals(ptype, to, typeVarMap)) { 237 return true; 238 } 239 } 240 241 for (Type itype : clazz.getGenericInterfaces()) { 242 if (isAssignableFrom(itype, to, new HashMap<>(typeVarMap))) { 243 return true; 244 } 245 } 246 247 // Interfaces didn't work, try the superclass. 248 Type sType = clazz.getGenericSuperclass(); 249 return isAssignableFrom(sType, to, new HashMap<>(typeVarMap)); 250 } 251 252 /** 253 * Checks if two parameterized types are exactly equal, under the variable 254 * replacement described in the typeVarMap. 255 */ typeEquals(ParameterizedType from, ParameterizedType to, Map<String, Type> typeVarMap)256 private static boolean typeEquals(ParameterizedType from, 257 ParameterizedType to, Map<String, Type> typeVarMap) { 258 if (from.getRawType().equals(to.getRawType())) { 259 Type[] fromArgs = from.getActualTypeArguments(); 260 Type[] toArgs = to.getActualTypeArguments(); 261 for (int i = 0; i < fromArgs.length; i++) { 262 if (!matches(fromArgs[i], toArgs[i], typeVarMap)) { 263 return false; 264 } 265 } 266 return true; 267 } 268 return false; 269 } 270 buildUnexpectedTypeError( Type token, Class<?>... expected)271 private static AssertionError buildUnexpectedTypeError( 272 Type token, Class<?>... expected) { 273 274 // Build exception message 275 StringBuilder exceptionMessage = 276 new StringBuilder("Unexpected type. Expected one of: "); 277 for (Class<?> clazz : expected) { 278 exceptionMessage.append(clazz.getName()).append(", "); 279 } 280 exceptionMessage.append("but got: ").append(token.getClass().getName()) 281 .append(", for type token: ").append(token.toString()).append('.'); 282 283 return new AssertionError(exceptionMessage.toString()); 284 } 285 286 /** 287 * Checks if two types are the same or are equivalent under a variable mapping 288 * given in the type map that was provided. 289 */ matches(Type from, Type to, Map<String, Type> typeMap)290 private static boolean matches(Type from, Type to, Map<String, Type> typeMap) { 291 return to.equals(from) 292 || (from instanceof TypeVariable 293 && to.equals(typeMap.get(((TypeVariable<?>) from).getName()))); 294 295 } 296 hashCode()297 @Override public final int hashCode() { 298 return this.hashCode; 299 } 300 equals(Object o)301 @Override public final boolean equals(Object o) { 302 return o instanceof TypeToken<?> 303 && $Gson$Types.equals(type, ((TypeToken<?>) o).type); 304 } 305 toString()306 @Override public final String toString() { 307 return $Gson$Types.typeToString(type); 308 } 309 310 /** 311 * Gets type literal for the given {@code Type} instance. 312 */ get(Type type)313 public static TypeToken<?> get(Type type) { 314 return new TypeToken<>(type); 315 } 316 317 /** 318 * Gets type literal for the given {@code Class} instance. 319 */ get(Class<T> type)320 public static <T> TypeToken<T> get(Class<T> type) { 321 return new TypeToken<>(type); 322 } 323 324 /** 325 * Gets a type literal for the parameterized type represented by applying {@code typeArguments} to 326 * {@code rawType}. This is mainly intended for situations where the type arguments are not 327 * available at compile time. The following example shows how a type token for {@code Map<K, V>} 328 * can be created: 329 * <pre>{@code 330 * Class<K> keyClass = ...; 331 * Class<V> valueClass = ...; 332 * TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass); 333 * }</pre> 334 * As seen here the result is a {@code TypeToken<?>}; this method cannot provide any type safety, 335 * and care must be taken to pass in the correct number of type arguments. 336 * 337 * @throws IllegalArgumentException 338 * If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for 339 * the raw type 340 */ getParameterized(Type rawType, Type... typeArguments)341 public static TypeToken<?> getParameterized(Type rawType, Type... typeArguments) { 342 Objects.requireNonNull(rawType); 343 Objects.requireNonNull(typeArguments); 344 345 // Perform basic validation here because this is the only public API where users 346 // can create malformed parameterized types 347 if (!(rawType instanceof Class)) { 348 // See also https://bugs.openjdk.org/browse/JDK-8250659 349 throw new IllegalArgumentException("rawType must be of type Class, but was " + rawType); 350 } 351 Class<?> rawClass = (Class<?>) rawType; 352 TypeVariable<?>[] typeVariables = rawClass.getTypeParameters(); 353 354 int expectedArgsCount = typeVariables.length; 355 int actualArgsCount = typeArguments.length; 356 if (actualArgsCount != expectedArgsCount) { 357 throw new IllegalArgumentException(rawClass.getName() + " requires " + expectedArgsCount + 358 " type arguments, but got " + actualArgsCount); 359 } 360 361 for (int i = 0; i < expectedArgsCount; i++) { 362 Type typeArgument = typeArguments[i]; 363 Class<?> rawTypeArgument = $Gson$Types.getRawType(typeArgument); 364 TypeVariable<?> typeVariable = typeVariables[i]; 365 366 for (Type bound : typeVariable.getBounds()) { 367 Class<?> rawBound = $Gson$Types.getRawType(bound); 368 369 if (!rawBound.isAssignableFrom(rawTypeArgument)) { 370 throw new IllegalArgumentException("Type argument " + typeArgument + " does not satisfy bounds " 371 + "for type variable " + typeVariable + " declared by " + rawType); 372 } 373 } 374 } 375 376 return new TypeToken<>($Gson$Types.newParameterizedTypeWithOwner(null, rawType, typeArguments)); 377 } 378 379 /** 380 * Gets type literal for the array type whose elements are all instances of {@code componentType}. 381 */ getArray(Type componentType)382 public static TypeToken<?> getArray(Type componentType) { 383 return new TypeToken<>($Gson$Types.arrayOf(componentType)); 384 } 385 } 386