View Javadoc
1   package com.atlassian.plugin.util;
2   
3   import java.lang.reflect.Array;
4   import java.lang.reflect.GenericArrayType;
5   import java.lang.reflect.ParameterizedType;
6   import java.lang.reflect.Type;
7   import java.lang.reflect.TypeVariable;
8   import java.util.ArrayList;
9   import java.util.HashMap;
10  import java.util.HashSet;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Set;
14  
15  /**
16   * Class utility methods
17   */
18  public class ClassUtils {
19      private ClassUtils() {
20      }
21  
22      /**
23       * Finds all super classes and interfaces for a given class
24       *
25       * @param cls The class to scan
26       * @return The collected related classes found
27       */
28      public static Set<Class> findAllTypes(Class cls) {
29          Set<Class> types = new HashSet<>();
30          findAllTypes(cls, types);
31          return types;
32      }
33  
34      /**
35       * Finds all super classes and interfaces for a given class
36       *
37       * @param cls   The class to scan
38       * @param types The collected related classes found
39       */
40      public static void findAllTypes(Class cls, Set<Class> types) {
41          if (cls == null) {
42              return;
43          }
44  
45          // check to ensure it hasn't been scanned yet
46          if (types.contains(cls)) {
47              return;
48          }
49  
50          types.add(cls);
51  
52          findAllTypes(cls.getSuperclass(), types);
53          for (int x = 0; x < cls.getInterfaces().length; x++) {
54              findAllTypes(cls.getInterfaces()[x], types);
55          }
56      }
57  
58      /**
59       * Get the underlying class for a type, or null if the type is a variable type.
60       *
61       * @param type the type
62       * @return the underlying class
63       */
64      private static Class<?> getClass(Type type) {
65          if (type instanceof Class) {
66              return (Class) type;
67          } else if (type instanceof ParameterizedType) {
68              return getClass(((ParameterizedType) type).getRawType());
69          } else if (type instanceof GenericArrayType) {
70              Type componentType = ((GenericArrayType) type).getGenericComponentType();
71              Class<?> componentClass = getClass(componentType);
72              if (componentClass != null) {
73                  return Array.newInstance(componentClass, 0).getClass();
74              } else {
75                  return null;
76              }
77          } else {
78              return null;
79          }
80      }
81  
82      /**
83       * Get the actual type arguments a child class has used to extend a generic base class.
84       *
85       * @param baseClass  the base class
86       * @param childClass the child class
87       * @return a list of the raw classes for the actual type arguments.
88       * @throws IllegalArgumentException If the child class is not the base of the baseClass
89       * @since 2.5.0
90       */
91      public static <T> List<Class<?>> getTypeArguments(
92              Class<T> baseClass, Class<? extends T> childClass) {
93          Map<Type, Type> resolvedTypes = new HashMap<>();
94          Type type = childClass;
95          Class typeClass = getClass(type);
96          // start walking up the inheritance hierarchy until we hit baseClass
97          while (!typeClass.equals(baseClass)) {
98              if (type instanceof Class) {
99                  // there is no useful information for us in raw types, so just keep going.
100                 type = ((Class) type).getGenericSuperclass();
101             } else {
102                 ParameterizedType parameterizedType = (ParameterizedType) type;
103                 Class<?> rawType = (Class) parameterizedType.getRawType();
104 
105                 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
106                 TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
107                 for (int i = 0; i < actualTypeArguments.length; i++) {
108                     resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
109                 }
110 
111                 if (!rawType.equals(baseClass)) {
112                     type = rawType.getGenericSuperclass();
113                 }
114             }
115             typeClass = getClass(type);
116             if (typeClass == null) {
117                 throw new IllegalArgumentException("Unable to find the class for the type " + type);
118             }
119         }
120 
121         // finally, for each actual type argument provided to baseClass, determine (if possible)
122         // the raw class for that type argument.
123         Type[] actualTypeArguments;
124         if (type instanceof Class) {
125             actualTypeArguments = ((Class) type).getTypeParameters();
126         } else {
127             actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
128         }
129         List<Class<?>> typeArgumentsAsClasses = new ArrayList<>();
130         // resolve types by chasing down type variables.
131         for (Type baseType : actualTypeArguments) {
132             while (resolvedTypes.containsKey(baseType)) {
133                 baseType = resolvedTypes.get(baseType);
134             }
135             typeArgumentsAsClasses.add(getClass(baseType));
136         }
137         return typeArgumentsAsClasses;
138     }
139 }