/* * Copyright (c) 2016 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.util.reflection; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; /** * Attempts to extract generic type of given target base class or target interface */ public class GenericTypeExtractor { /** * Extract generic type of root class either from the target base class or from target base interface. * Examples: * <p> * 1. Foo implements IFoo[Integer]: * genericTypeOf(Foo.class, Object.class, IFoo.class) returns Integer * <p> * 2. Foo extends BaseFoo[String]: * genericTypeOf(Foo.class, BaseFoo.class, IFoo.class) returns String * <p> * 3. Foo extends BaseFoo; BaseFoo implements IFoo[String]: * genericTypeOf(Foo.class, BaseFoo.class, Object.class) returns String * <p> * Does not support nested generics, only supports single type parameter. * * @param rootClass - the root class that the search begins from * @param targetBaseClass - if one of the classes in the root class' hierarchy extends this base class * it will be used for generic type extraction * @param targetBaseInterface - if one of the interfaces in the root class' hierarchy implements this interface * it will be used for generic type extraction * @return generic interface if found, Object.class if not found. */ public static Class<?> genericTypeOf(Class<?> rootClass, Class<?> targetBaseClass, Class<?> targetBaseInterface) { //looking for candidates in the hierarchy of rootClass Class<?> match = rootClass; while(match != Object.class) { //check the super class first if (match.getSuperclass() == targetBaseClass) { return extractGeneric(match.getGenericSuperclass()); } //check the interfaces (recursively) Type genericInterface = findGenericInteface(match, targetBaseInterface); if (genericInterface != null) { return extractGeneric(genericInterface); } //recurse the hierarchy match = match.getSuperclass(); } return Object.class; } /** * Finds generic interface implementation based on the source class and the target interface. * Returns null if not found. Recurses the interface hierarchy. */ private static Type findGenericInteface(Class<?> sourceClass, Class<?> targetBaseInterface) { for (int i = 0; i < sourceClass.getInterfaces().length; i++) { Class<?> inter = sourceClass.getInterfaces()[i]; if (inter == targetBaseInterface) { return sourceClass.getGenericInterfaces()[0]; } else { Type deeper = findGenericInteface(inter, targetBaseInterface); if (deeper != null) { return deeper; } } } return null; } /** * Attempts to extract generic parameter type of given type. * If there is no generic parameter it returns Object.class */ private static Class<?> extractGeneric(Type type) { if (type instanceof ParameterizedType) { Type[] genericTypes = ((ParameterizedType) type).getActualTypeArguments(); if (genericTypes.length > 0 && genericTypes[0] instanceof Class) { return (Class<?>) genericTypes[0]; } } return Object.class; } }