1/*
2 * Copyright (c) 2016 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockito.internal.util.reflection;
6
7import java.lang.reflect.ParameterizedType;
8import java.lang.reflect.Type;
9
10/**
11 * Attempts to extract generic type of given target base class or target interface
12 */
13public class GenericTypeExtractor {
14
15    /**
16     * Extract generic type of root class either from the target base class or from target base interface.
17     * Examples:
18     *  <p>
19     *  1. Foo implements IFoo[Integer]:
20     *      genericTypeOf(Foo.class, Object.class, IFoo.class) returns Integer
21     *  <p>
22     *  2. Foo extends BaseFoo[String]:
23     *      genericTypeOf(Foo.class, BaseFoo.class, IFoo.class) returns String
24     *  <p>
25     *  3. Foo extends BaseFoo; BaseFoo implements IFoo[String]:
26     *      genericTypeOf(Foo.class, BaseFoo.class, Object.class) returns String
27     *  <p>
28     *  Does not support nested generics, only supports single type parameter.
29     *
30     * @param rootClass - the root class that the search begins from
31     * @param targetBaseClass - if one of the classes in the root class' hierarchy extends this base class
32     *                        it will be used for generic type extraction
33     * @param targetBaseInterface - if one of the interfaces in the root class' hierarchy implements this interface
34     *                            it will be used for generic type extraction
35     * @return generic interface if found, Object.class if not found.
36     */
37    public static Class<?> genericTypeOf(Class<?> rootClass, Class<?> targetBaseClass, Class<?> targetBaseInterface) {
38        //looking for candidates in the hierarchy of rootClass
39        Class<?> match = rootClass;
40        while(match != Object.class) {
41            //check the super class first
42            if (match.getSuperclass() == targetBaseClass) {
43                return extractGeneric(match.getGenericSuperclass());
44            }
45            //check the interfaces (recursively)
46            Type genericInterface = findGenericInteface(match, targetBaseInterface);
47            if (genericInterface != null) {
48                return extractGeneric(genericInterface);
49            }
50            //recurse the hierarchy
51            match = match.getSuperclass();
52        }
53        return Object.class;
54    }
55
56    /**
57     * Finds generic interface implementation based on the source class and the target interface.
58     * Returns null if not found. Recurses the interface hierarchy.
59     */
60    private static Type findGenericInteface(Class<?> sourceClass, Class<?> targetBaseInterface) {
61        for (int i = 0; i < sourceClass.getInterfaces().length; i++) {
62            Class<?> inter = sourceClass.getInterfaces()[i];
63            if (inter == targetBaseInterface) {
64                return sourceClass.getGenericInterfaces()[0];
65            } else {
66                Type deeper = findGenericInteface(inter, targetBaseInterface);
67                if (deeper != null) {
68                    return deeper;
69                }
70            }
71        }
72        return null;
73    }
74
75    /**
76     * Attempts to extract generic parameter type of given type.
77     * If there is no generic parameter it returns Object.class
78     */
79    private static Class<?> extractGeneric(Type type) {
80        if (type instanceof ParameterizedType) {
81            Type[] genericTypes = ((ParameterizedType) type).getActualTypeArguments();
82            if (genericTypes.length > 0 && genericTypes[0] instanceof Class) {
83                return (Class<?>) genericTypes[0];
84            }
85        }
86        return Object.class;
87    }
88}
89