]>
Commit | Line | Data |
---|---|---|
cfd903b6 MG |
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 com.google.gson.internal.$Gson$Preconditions; | |
21 | import java.lang.reflect.GenericArrayType; | |
22 | import java.lang.reflect.ParameterizedType; | |
23 | import java.lang.reflect.Type; | |
24 | import java.lang.reflect.TypeVariable; | |
25 | import java.util.HashMap; | |
26 | import java.util.Map; | |
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 inner class: | |
36 | * | |
37 | * <p> | |
38 | * {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};} | |
39 | * | |
40 | * <p>This syntax cannot be used to create type literals that have wildcard | |
41 | * parameters, such as {@code Class<?>} or {@code List<? extends CharSequence>}. | |
42 | * | |
43 | * @author Bob Lee | |
44 | * @author Sven Mawson | |
45 | * @author Jesse Wilson | |
46 | */ | |
47 | public class TypeToken<T> { | |
48 | final Class<? super T> rawType; | |
49 | final Type type; | |
50 | final int hashCode; | |
51 | ||
52 | /** | |
53 | * Constructs a new type literal. Derives represented class from type | |
54 | * parameter. | |
55 | * | |
56 | * <p>Clients create an empty anonymous subclass. Doing so embeds the type | |
57 | * parameter in the anonymous class's type hierarchy so we can reconstitute it | |
58 | * at runtime despite erasure. | |
59 | */ | |
60 | @SuppressWarnings("unchecked") | |
61 | protected TypeToken() { | |
62 | this.type = getSuperclassTypeParameter(getClass()); | |
63 | this.rawType = (Class<? super T>) $Gson$Types.getRawType(type); | |
64 | this.hashCode = type.hashCode(); | |
65 | } | |
66 | ||
67 | /** | |
68 | * Unsafe. Constructs a type literal manually. | |
69 | */ | |
70 | @SuppressWarnings("unchecked") | |
71 | TypeToken(Type type) { | |
72 | this.type = $Gson$Types.canonicalize($Gson$Preconditions.checkNotNull(type)); | |
73 | this.rawType = (Class<? super T>) $Gson$Types.getRawType(this.type); | |
74 | this.hashCode = this.type.hashCode(); | |
75 | } | |
76 | ||
77 | /** | |
78 | * Returns the type from super class's type parameter in {@link $Gson$Types#canonicalize | |
79 | * canonical form}. | |
80 | */ | |
81 | static Type getSuperclassTypeParameter(Class<?> subclass) { | |
82 | Type superclass = subclass.getGenericSuperclass(); | |
83 | if (superclass instanceof Class) { | |
84 | throw new RuntimeException("Missing type parameter."); | |
85 | } | |
86 | ParameterizedType parameterized = (ParameterizedType) superclass; | |
87 | return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]); | |
88 | } | |
89 | ||
90 | /** | |
91 | * Returns the raw (non-generic) type for this type. | |
92 | */ | |
93 | public final Class<? super T> getRawType() { | |
94 | return rawType; | |
95 | } | |
96 | ||
97 | /** | |
98 | * Gets underlying {@code Type} instance. | |
99 | */ | |
100 | public final Type getType() { | |
101 | return type; | |
102 | } | |
103 | ||
104 | /** | |
105 | * Check if this type is assignable from the given class object. | |
106 | * | |
107 | * @deprecated this implementation may be inconsistent with javac for types | |
108 | * with wildcards. | |
109 | */ | |
110 | @Deprecated | |
111 | public boolean isAssignableFrom(Class<?> cls) { | |
112 | return isAssignableFrom((Type) cls); | |
113 | } | |
114 | ||
115 | /** | |
116 | * Check if this type is assignable from the given Type. | |
117 | * | |
118 | * @deprecated this implementation may be inconsistent with javac for types | |
119 | * with wildcards. | |
120 | */ | |
121 | @Deprecated | |
122 | public boolean isAssignableFrom(Type from) { | |
123 | if (from == null) { | |
124 | return false; | |
125 | } | |
126 | ||
127 | if (type.equals(from)) { | |
128 | return true; | |
129 | } | |
130 | ||
131 | if (type instanceof Class<?>) { | |
132 | return rawType.isAssignableFrom($Gson$Types.getRawType(from)); | |
133 | } else if (type instanceof ParameterizedType) { | |
134 | return isAssignableFrom(from, (ParameterizedType) type, | |
135 | new HashMap<String, Type>()); | |
136 | } else if (type instanceof GenericArrayType) { | |
137 | return rawType.isAssignableFrom($Gson$Types.getRawType(from)) | |
138 | && isAssignableFrom(from, (GenericArrayType) type); | |
139 | } else { | |
140 | throw buildUnexpectedTypeError( | |
141 | type, Class.class, ParameterizedType.class, GenericArrayType.class); | |
142 | } | |
143 | } | |
144 | ||
145 | /** | |
146 | * Check if this type is assignable from the given type token. | |
147 | * | |
148 | * @deprecated this implementation may be inconsistent with javac for types | |
149 | * with wildcards. | |
150 | */ | |
151 | @Deprecated | |
152 | public boolean isAssignableFrom(TypeToken<?> token) { | |
153 | return isAssignableFrom(token.getType()); | |
154 | } | |
155 | ||
156 | /** | |
157 | * Private helper function that performs some assignability checks for | |
158 | * the provided GenericArrayType. | |
159 | */ | |
160 | private static boolean isAssignableFrom(Type from, GenericArrayType to) { | |
161 | Type toGenericComponentType = to.getGenericComponentType(); | |
162 | if (toGenericComponentType instanceof ParameterizedType) { | |
163 | Type t = from; | |
164 | if (from instanceof GenericArrayType) { | |
165 | t = ((GenericArrayType) from).getGenericComponentType(); | |
166 | } else if (from instanceof Class<?>) { | |
167 | Class<?> classType = (Class<?>) from; | |
168 | while (classType.isArray()) { | |
169 | classType = classType.getComponentType(); | |
170 | } | |
171 | t = classType; | |
172 | } | |
173 | return isAssignableFrom(t, (ParameterizedType) toGenericComponentType, | |
174 | new HashMap<String, Type>()); | |
175 | } | |
176 | // No generic defined on "to"; therefore, return true and let other | |
177 | // checks determine assignability | |
178 | return true; | |
179 | } | |
180 | ||
181 | /** | |
182 | * Private recursive helper function to actually do the type-safe checking | |
183 | * of assignability. | |
184 | */ | |
185 | private static boolean isAssignableFrom(Type from, ParameterizedType to, | |
186 | Map<String, Type> typeVarMap) { | |
187 | ||
188 | if (from == null) { | |
189 | return false; | |
190 | } | |
191 | ||
192 | if (to.equals(from)) { | |
193 | return true; | |
194 | } | |
195 | ||
196 | // First figure out the class and any type information. | |
197 | Class<?> clazz = $Gson$Types.getRawType(from); | |
198 | ParameterizedType ptype = null; | |
199 | if (from instanceof ParameterizedType) { | |
200 | ptype = (ParameterizedType) from; | |
201 | } | |
202 | ||
203 | // Load up parameterized variable info if it was parameterized. | |
204 | if (ptype != null) { | |
205 | Type[] tArgs = ptype.getActualTypeArguments(); | |
206 | TypeVariable<?>[] tParams = clazz.getTypeParameters(); | |
207 | for (int i = 0; i < tArgs.length; i++) { | |
208 | Type arg = tArgs[i]; | |
209 | TypeVariable<?> var = tParams[i]; | |
210 | while (arg instanceof TypeVariable<?>) { | |
211 | TypeVariable<?> v = (TypeVariable<?>) arg; | |
212 | arg = typeVarMap.get(v.getName()); | |
213 | } | |
214 | typeVarMap.put(var.getName(), arg); | |
215 | } | |
216 | ||
217 | // check if they are equivalent under our current mapping. | |
218 | if (typeEquals(ptype, to, typeVarMap)) { | |
219 | return true; | |
220 | } | |
221 | } | |
222 | ||
223 | for (Type itype : clazz.getGenericInterfaces()) { | |
224 | if (isAssignableFrom(itype, to, new HashMap<String, Type>(typeVarMap))) { | |
225 | return true; | |
226 | } | |
227 | } | |
228 | ||
229 | // Interfaces didn't work, try the superclass. | |
230 | Type sType = clazz.getGenericSuperclass(); | |
231 | return isAssignableFrom(sType, to, new HashMap<String, Type>(typeVarMap)); | |
232 | } | |
233 | ||
234 | /** | |
235 | * Checks if two parameterized types are exactly equal, under the variable | |
236 | * replacement described in the typeVarMap. | |
237 | */ | |
238 | private static boolean typeEquals(ParameterizedType from, | |
239 | ParameterizedType to, Map<String, Type> typeVarMap) { | |
240 | if (from.getRawType().equals(to.getRawType())) { | |
241 | Type[] fromArgs = from.getActualTypeArguments(); | |
242 | Type[] toArgs = to.getActualTypeArguments(); | |
243 | for (int i = 0; i < fromArgs.length; i++) { | |
244 | if (!matches(fromArgs[i], toArgs[i], typeVarMap)) { | |
245 | return false; | |
246 | } | |
247 | } | |
248 | return true; | |
249 | } | |
250 | return false; | |
251 | } | |
252 | ||
253 | private static AssertionError buildUnexpectedTypeError( | |
254 | Type token, Class<?>... expected) { | |
255 | ||
256 | // Build exception message | |
257 | StringBuilder exceptionMessage = | |
258 | new StringBuilder("Unexpected type. Expected one of: "); | |
259 | for (Class<?> clazz : expected) { | |
260 | exceptionMessage.append(clazz.getName()).append(", "); | |
261 | } | |
262 | exceptionMessage.append("but got: ").append(token.getClass().getName()) | |
263 | .append(", for type token: ").append(token.toString()).append('.'); | |
264 | ||
265 | return new AssertionError(exceptionMessage.toString()); | |
266 | } | |
267 | ||
268 | /** | |
269 | * Checks if two types are the same or are equivalent under a variable mapping | |
270 | * given in the type map that was provided. | |
271 | */ | |
272 | private static boolean matches(Type from, Type to, Map<String, Type> typeMap) { | |
273 | return to.equals(from) | |
274 | || (from instanceof TypeVariable | |
275 | && to.equals(typeMap.get(((TypeVariable<?>) from).getName()))); | |
276 | ||
277 | } | |
278 | ||
279 | @Override public final int hashCode() { | |
280 | return this.hashCode; | |
281 | } | |
282 | ||
283 | @Override public final boolean equals(Object o) { | |
284 | return o instanceof TypeToken<?> | |
285 | && $Gson$Types.equals(type, ((TypeToken<?>) o).type); | |
286 | } | |
287 | ||
288 | @Override public final String toString() { | |
289 | return $Gson$Types.typeToString(type); | |
290 | } | |
291 | ||
292 | /** | |
293 | * Gets type literal for the given {@code Type} instance. | |
294 | */ | |
295 | public static TypeToken<?> get(Type type) { | |
296 | return new TypeToken<Object>(type); | |
297 | } | |
298 | ||
299 | /** | |
300 | * Gets type literal for the given {@code Class} instance. | |
301 | */ | |
302 | public static <T> TypeToken<T> get(Class<T> type) { | |
303 | return new TypeToken<T>(type); | |
304 | } | |
305 | } |