• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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