aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/com/sun/beans/TypeResolver.java
blob: 6196b22f2a14a2881c6b16e45d884bad89162ebf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/*
 * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.beans;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.HashMap;
import java.util.Map;

import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

/**
 * This is utility class to resolve types.
 *
 * @since 1.7
 *
 * @author Eamonn McManus
 * @author Sergey Malenkov
 */
public final class TypeResolver {
    /**
     * Replaces the given {@code type} in an inherited method
     * with the actual type it has in the given {@code inClass}.
     *
     * <p>Although type parameters are not inherited by subclasses in the Java
     * language, they <em>are</em> effectively inherited when using reflection.
     * For example, if you declare an interface like this...</p>
     *
     * <pre>
     * public interface StringToIntMap extends Map&lt;String,Integer> {}
     * </pre>
     *
     * <p>...then StringToIntMap.class.getMethods() will show that it has methods
     * like put(K,V) even though StringToIntMap has no type parameters.  The K
     * and V variables are the ones declared by Map, so
     * {@link TypeVariable#getGenericDeclaration()} will return Map.class.</p>
     *
     * <p>The purpose of this method is to take a Type from a possibly-inherited
     * method and replace it with the correct Type for the inheriting class.
     * So given parameters of K and StringToIntMap.class in the above example,
     * this method will return String.</p>
     *
     * @param inClass  the base class used to resolve
     * @param type     the type to resolve
     * @return a resolved type
     *
     * @see #getActualType(Class)
     * @see #resolve(Type,Type)
     */
    public static Type resolveInClass(Class<?> inClass, Type type) {
        return resolve(getActualType(inClass), type);
    }

    /**
     * Replaces all {@code types} in the given array
     * with the actual types they have in the given {@code inClass}.
     *
     * @param inClass  the base class used to resolve
     * @param types    the array of types to resolve
     * @return an array of resolved types
     *
     * @see #getActualType(Class)
     * @see #resolve(Type,Type[])
     */
    public static Type[] resolveInClass(Class<?> inClass, Type[] types) {
        return resolve(getActualType(inClass), types);
    }

    /**
     * Replaces type variables of the given {@code formal} type
     * with the types they stand for in the given {@code actual} type.
     *
     * <p>A ParameterizedType is a class with type parameters, and the values
     * of those parameters.  For example, Map&lt;K,V> is a generic class, and
     * a corresponding ParameterizedType might look like
     * Map&lt;K=String,V=Integer>.  Given such a ParameterizedType, this method
     * will replace K with String, or List&lt;K> with List&ltString;, or
     * List&lt;? super K> with List&lt;? super String>.</p>
     *
     * <p>The {@code actual} argument to this method can also be a Class.
     * In this case, either it is equivalent to a ParameterizedType with
     * no parameters (for example, Integer.class), or it is equivalent to
     * a "raw" ParameterizedType (for example, Map.class).  In the latter
     * case, every type parameter declared or inherited by the class is replaced
     * by its "erasure".  For a type parameter declared as &lt;T>, the erasure
     * is Object.  For a type parameter declared as &lt;T extends Number>,
     * the erasure is Number.</p>
     *
     * <p>Although type parameters are not inherited by subclasses in the Java
     * language, they <em>are</em> effectively inherited when using reflection.
     * For example, if you declare an interface like this...</p>
     *
     * <pre>
     * public interface StringToIntMap extends Map&lt;String,Integer> {}
     * </pre>
     *
     * <p>...then StringToIntMap.class.getMethods() will show that it has methods
     * like put(K,V) even though StringToIntMap has no type parameters.  The K
     * and V variables are the ones declared by Map, so
     * {@link TypeVariable#getGenericDeclaration()} will return {@link Map Map.class}.</p>
     *
     * <p>For this reason, this method replaces inherited type parameters too.
     * Therefore if this method is called with {@code actual} being
     * StringToIntMap.class and {@code formal} being the K from Map,
     * it will return {@link String String.class}.</p>
     *
     * <p>In the case where {@code actual} is a "raw" ParameterizedType, the
     * inherited type parameters will also be replaced by their erasures.
     * The erasure of a Class is the Class itself, so a "raw" subinterface of
     * StringToIntMap will still show the K from Map as String.class.  But
     * in a case like this...
     *
     * <pre>
     * public interface StringToIntListMap extends Map&lt;String,List&lt;Integer>> {}
     * public interface RawStringToIntListMap extends StringToIntListMap {}
     * </pre>
     *
     * <p>...the V inherited from Map will show up as List&lt;Integer> in
     * StringToIntListMap, but as plain List in RawStringToIntListMap.</p>
     *
     * @param actual  the type that supplies bindings for type variables
     * @param formal  the type where occurrences of the variables
     *                in {@code actual} will be replaced by the corresponding bound values
     * @return a resolved type
     *
     * @see #TypeResolver(Type)
     * @see #resolve(Type)
     */
    public static Type resolve(Type actual, Type formal) {
        return new TypeResolver(actual).resolve(formal);
    }

    /**
     * Replaces type variables of all formal types in the given array
     * with the types they stand for in the given {@code actual} type.
     *
     * @param actual   the type that supplies bindings for type variables
     * @param formals  the array of types to resolve
     * @return an array of resolved types
     *
     * @see #TypeResolver(Type)
     * @see #resolve(Type[])
     */
    public static Type[] resolve(Type actual, Type[] formals) {
        return new TypeResolver(actual).resolve(formals);
    }

    /**
     * Converts the given {@code type} to the corresponding class.
     * This method implements the concept of type erasure,
     * that is described in <a href="http://jscstage.sfbay.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.6">section 4.6</a>
     * of Java Language Specification.
     *
     * @param type  the array of types to convert
     * @return a corresponding class
     */
    public static Class<?> erase(Type type) {
        if (type instanceof Class) {
            return (Class<?>) type;
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            return (Class<?>) pt.getRawType();
        }
        if (type instanceof TypeVariable) {
            TypeVariable tv = (TypeVariable)type;
            Type[] bounds = tv.getBounds();
            return (0 < bounds.length)
                    ? erase(bounds[0])
                    : Object.class;
        }
        if (type instanceof WildcardType) {
            WildcardType wt = (WildcardType)type;
            Type[] bounds = wt.getUpperBounds();
            return (0 < bounds.length)
                    ? erase(bounds[0])
                    : Object.class;
        }
        if (type instanceof GenericArrayType) {
            GenericArrayType gat = (GenericArrayType)type;
            return Array.newInstance(erase(gat.getGenericComponentType()), 0).getClass();
        }
        throw new IllegalArgumentException("Unknown Type kind: " + type.getClass());
    }

    /**
     * Converts all {@code types} in the given array
     * to the corresponding classes.
     *
     * @param types  the array of types to convert
     * @return an array of corresponding classes
     *
     * @see #erase(Type)
     */
    public static Class[] erase(Type[] types) {
        int length = types.length;
        Class[] classes = new Class[length];
        for (int i = 0; i < length; i++) {
            classes[i] = TypeResolver.erase(types[i]);
        }
        return classes;
    }


    private final Map<TypeVariable<?>, Type> map
        = new HashMap<TypeVariable<?>, Type>();

    /**
     * Constructs the type resolver for the given actual type.
     *
     * @param actual  the type that supplies bindings for type variables
     *
     * @see #prepare(Type)
     */
    private TypeResolver(Type actual) {
        prepare(actual);
    }

    /**
     * Fills the map from type parameters
     * to types as seen by the given {@code type}.
     * The method is recursive because the {@code type}
     * inherits mappings from its parent classes and interfaces.
     * The {@code type} can be either a {@link Class Class}
     * or a {@link ParameterizedType ParameterizedType}.
     * If it is a {@link Class Class}, it is either equivalent
     * to a {@link ParameterizedType ParameterizedType} with no parameters,
     * or it represents the erasure of a {@link ParameterizedType ParameterizedType}.
     *
     * @param type  the next type in the hierarchy
     */
    private void prepare(Type type) {
        Class<?> raw = (Class<?>)((type instanceof Class<?>)
                ? type
                : ((ParameterizedType)type).getRawType());

        TypeVariable<?>[] formals = raw.getTypeParameters();

        Type[] actuals = (type instanceof Class<?>)
                ? formals
                : ((ParameterizedType)type).getActualTypeArguments();

        assert formals.length == actuals.length;
        for (int i = 0; i < formals.length; i++) {
            this.map.put(formals[i], actuals[i]);
        }
        Type gSuperclass = raw.getGenericSuperclass();
        if (gSuperclass != null) {
            prepare(gSuperclass);
        }
        for (Type gInterface : raw.getGenericInterfaces()) {
            prepare(gInterface);
        }
        // If type is the raw version of a parameterized class, we type-erase
        // all of its type variables, including inherited ones.
        if (type instanceof Class<?> && formals.length > 0) {
            for (Map.Entry<TypeVariable<?>, Type> entry : this.map.entrySet()) {
                entry.setValue(erase(entry.getValue()));
            }
        }
    }

    /**
     * Replaces the given {@code formal} type
     * with the type it stand for in this type resolver.
     *
     * @param formal  the array of types to resolve
     * @return a resolved type
     */
    private Type resolve(Type formal) {
        if (formal instanceof Class) {
            return formal;
        }
        if (formal instanceof GenericArrayType) {
            Type comp = ((GenericArrayType)formal).getGenericComponentType();
            comp = resolve(comp);
            return (comp instanceof Class)
                    ? Array.newInstance((Class<?>)comp, 0).getClass()
                    : GenericArrayTypeImpl.make(comp);
        }
        if (formal instanceof ParameterizedType) {
            ParameterizedType fpt = (ParameterizedType)formal;
            Type[] actuals = resolve(fpt.getActualTypeArguments());
            return ParameterizedTypeImpl.make(
                    (Class<?>)fpt.getRawType(), actuals, fpt.getOwnerType());
        }
        if (formal instanceof WildcardType) {
            WildcardType fwt = (WildcardType)formal;
            Type[] upper = resolve(fwt.getUpperBounds());
            Type[] lower = resolve(fwt.getLowerBounds());
            return new WildcardTypeImpl(upper, lower);
        }
        if (!(formal instanceof TypeVariable)) {
            throw new IllegalArgumentException("Bad Type kind: " + formal.getClass());
        }
        Type actual = this.map.get((TypeVariable) formal);
        if (actual == null || actual.equals(formal)) {
            return formal;
        }
        actual = fixGenericArray(actual);
        return resolve(actual);
        // A variable can be bound to another variable that is itself bound
        // to something.  For example, given:
        // class Super<T> {...}
        // class Mid<X> extends Super<T> {...}
        // class Sub extends Mid<String>
        // the variable T is bound to X, which is in turn bound to String.
        // So if we have to resolve T, we need the tail recursion here.
    }

    /**
     * Replaces all formal types in the given array
     * with the types they stand for in this type resolver.
     *
     * @param formals  the array of types to resolve
     * @return an array of resolved types
     *
     * @see #resolve(Type)
     */
    private Type[] resolve(Type[] formals) {
        int length = formals.length;
        Type[] actuals = new Type[length];
        for (int i = 0; i < length; i++) {
            actuals[i] = resolve(formals[i]);
        }
        return actuals;
    }

    /**
     * Replaces a {@link GenericArrayType GenericArrayType}
     * with plain array class where it is possible.
     * Bug <a href="http://bugs.sun.com/view_bug.do?bug_id=5041784">5041784</a>
     * is that arrays of non-generic type sometimes show up
     * as {@link GenericArrayType GenericArrayType} when using reflection.
     * For example, a {@code String[]} might show up
     * as a {@link GenericArrayType GenericArrayType}
     * where {@link GenericArrayType#getGenericComponentType getGenericComponentType}
     * is {@code String.class}.  This violates the specification,
     * which says that {@link GenericArrayType GenericArrayType}
     * is used when the component type is a type variable or parameterized type.
     * We fit the specification here.
     *
     * @param type  the type to fix
     * @return a corresponding type for the generic array type,
     *         or the same type as {@code type}
     */
    private static Type fixGenericArray(Type type) {
        if (type instanceof GenericArrayType) {
            Type comp = ((GenericArrayType)type).getGenericComponentType();
            comp = fixGenericArray(comp);
            if (comp instanceof Class) {
                return Array.newInstance((Class<?>)comp, 0).getClass();
            }
        }
        return type;
    }

    /**
     * Replaces a {@link Class Class} with type parameters
     * with a {@link ParameterizedType ParameterizedType}
     * where every parameter is bound to itself.
     * When calling {@link #resolveInClass} in the context of {@code inClass},
     * we can't just pass {@code inClass} as the {@code actual} parameter,
     * because if {@code inClass} has type parameters
     * that would be interpreted as accessing the raw type,
     * so we would get unwanted erasure.
     * This is why we bind each parameter to itself.
     * If {@code inClass} does have type parameters and has methods
     * where those parameters appear in the return type or argument types,
     * we will correctly leave those types alone.
     *
     * @param inClass  the base class used to resolve
     * @return a parameterized type for the class,
     *         or the same class as {@code inClass}
     */
    private static Type getActualType(Class<?> inClass) {
        Type[] params = inClass.getTypeParameters();
        return (params.length == 0)
                ? inClass
                : ParameterizedTypeImpl.make(
                        inClass, params, inClass.getEnclosingClass());
    }
}