View Javadoc
1   /*
2    * Copyright (C) 2015 the original author or authors.
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 org.codehaus.gmavenplus.util;
18  
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.Field;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.List;
27  
28  
29  /**
30   * Inspired heavily by Spring's <a href="https://github.com/SpringSource/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java">ReflectionUtils</a>.
31   *
32   * @author Juergen Hoeller
33   * @author Rob Harrop
34   * @author Rod Johnson
35   * @author Costin Leau
36   * @author Sam Brannen
37   * @author Chris Beams
38   * @author Keegan Witt
39   * @since 1.0-beta-1
40   */
41  public class ReflectionUtils {
42  
43      private ReflectionUtils() {
44      }
45  
46      /**
47       * Attempt to find a {@link Constructor} on the supplied class with the supplied parameter types.
48       * Searches all superclasses up to <code>Object</code>.
49       *
50       * @param clazz      The class to introspect
51       * @param paramTypes The parameter types of the method (may be <code>null</code> to indicate any signature)
52       * @return The Constructor object
53       */
54      public static Constructor<?> findConstructor(final Class<?> clazz, final Class<?>... paramTypes) {
55          if (clazz == null) {
56              throw new IllegalArgumentException("Class must not be null.");
57          }
58          Class<?> searchType = clazz;
59          while (searchType != null) {
60              Constructor<?>[] constructors = searchType.isInterface() ? clazz.getConstructors() : clazz.getDeclaredConstructors();
61              for (Constructor<?> constructor : constructors) {
62                  if (paramTypes == null || Arrays.equals(paramTypes, constructor.getParameterTypes())) {
63                      return constructor;
64                  }
65              }
66              searchType = searchType.getSuperclass();
67          }
68          throw new IllegalArgumentException("Unable to find constructor " + clazz.getName() + "(" + Arrays.toString(paramTypes).replaceAll("^\\[", "").replaceAll("]$", "").replaceAll("class ", "") + ").");
69      }
70  
71      /**
72       * Attempt to find a {@link Field field} on the supplied {@link Class} with the supplied <code>name</code> and/or {@link Class type}.
73       * Searches all superclasses up to {@link Object}.
74       *
75       * @param clazz The class to introspect
76       * @param name  The name of the field (may be <code>null</code> if type is specified)
77       * @param type  The type of the field (may be <code>null</code> if name is specified)
78       * @return The corresponding Field object
79       */
80      public static Field findField(final Class<?> clazz, final String name, final Class<?> type) {
81          if (clazz == null) {
82              throw new IllegalArgumentException("Class must not be null");
83          }
84          if (name == null && type == null) {
85              throw new IllegalArgumentException("Either name or type of the field must be specified.");
86          }
87          Class<?> searchType = clazz;
88          while (Object.class != searchType && searchType != null) {
89              Field[] fields = searchType.getDeclaredFields();
90              for (Field field : fields) {
91                  if ((name == null || name.equals(field.getName())) && (type == null || type.equals(field.getType()))) {
92                      return field;
93                  }
94              }
95              searchType = searchType.getSuperclass();
96          }
97          throw new IllegalArgumentException("Unable to find " + (type != null ? type.getName() : "") + " " + (name != null ? name : "") + ".");
98      }
99  
100     /**
101      * Attempt to find a {@link Method} on the supplied class with the supplied name and parameter types.
102      * Searches all superclasses up to <code>Object</code>.
103      *
104      * @param clazz      The class to introspect
105      * @param name       The name of the method
106      * @param paramTypes The parameter types of the method
107      *                   (may be <code>null</code> to indicate any signature)
108      * @return The Method object
109      */
110     public static Method findMethod(final Class<?> clazz, final String name, final Class<?>... paramTypes) {
111         if (clazz == null) {
112             throw new IllegalArgumentException("Class must not be null.");
113         }
114         if (name == null) {
115             throw new IllegalArgumentException("Method name must not be null.");
116         }
117         Class<?> searchType = clazz;
118         while (searchType != null) {
119             Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType));
120             for (Method method : methods) {
121                 if (name.equals(method.getName()) && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
122                     return method;
123                 }
124             }
125             searchType = searchType.getSuperclass();
126         }
127         throw new IllegalArgumentException("Unable to find method " + clazz.getName() + "." + name + "(" + Arrays.toString(paramTypes).replaceAll("^\\[", "").replaceAll("]$", "").replaceAll("class ", "") + ").");
128     }
129 
130     /**
131      * Find and return the specified value from the specified enum class.
132      *
133      * @param clazz     The enum class to introspect
134      * @param valueName The name of the enum value to get
135      * @return The enum value
136      */
137     public static Object getEnumValue(final Class<?> clazz, final String valueName) {
138         if (clazz.isEnum()) {
139             for (Object o : clazz.getEnumConstants()) {
140                 if (o.toString().equals(valueName)) {
141                     return o;
142                 }
143             }
144             throw new IllegalArgumentException("Unable to get an enum constant with that name.");
145         } else {
146             throw new IllegalArgumentException(clazz + " must be an enum.");
147         }
148     }
149 
150     /**
151      * Get the field represented by the supplied {@link Field field object} on the specified {@link Object target object}.
152      * In accordance with {@link Field#get(Object)} semantics, the returned value is automatically wrapped if the underlying field has a primitive type.
153      *
154      * @param field  The field to get
155      * @param target The target object from which to get the field
156      * @return The field's current value
157      * @throws IllegalAccessException when unable to access the specified field because access modifiers prevent it
158      */
159     public static Object getField(final Field field, final Object target) throws IllegalAccessException {
160         field.setAccessible(true);
161         return field.get(target);
162     }
163 
164     /**
165      * Get the field represented by the supplied {@link Field field object} on the specified {@link Object target object}.
166      * In accordance with {@link Field#get(Object)} semantics, the returned value is automatically wrapped if the underlying field has a primitive type.
167      *
168      * @param field The field to get
169      * @return The field's current value
170      * @throws IllegalAccessException when unable to access the specified field because access modifiers prevent it
171      */
172     public static Object getStaticField(final Field field) throws IllegalAccessException {
173         if (!Modifier.isStatic(field.getModifiers())) {
174             throw new IllegalArgumentException("Field must be static.");
175         }
176         return getField(field, null);
177     }
178 
179     /**
180      * Invoke the specified {@link Constructor}  with the supplied arguments.
181      *
182      * @param constructor The method to invoke
183      * @param args        The invocation arguments (may be <code>null</code>)
184      * @return The invocation result, if any
185      * @throws IllegalAccessException                      when unable to access the specified constructor because access modifiers prevent it
186      * @throws java.lang.reflect.InvocationTargetException when a reflection invocation fails
187      * @throws InstantiationException                      when an instantiation fails
188      */
189     public static Object invokeConstructor(final Constructor<?> constructor, final Object... args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
190         if (constructor == null) {
191             throw new IllegalArgumentException("Constructor must not be null.");
192         }
193         constructor.setAccessible(true);
194         return constructor.newInstance(args);
195     }
196 
197     /**
198      * Invoke the specified {@link Method} against the supplied target object with the supplied arguments.
199      * The target object can be <code>null</code> when invoking a static {@link Method}.
200      *
201      * @param method The method to invoke
202      * @param target The target object to invoke the method on
203      * @param args   The invocation arguments (may be <code>null</code>)
204      * @return The invocation result, if any
205      * @throws IllegalAccessException                      when unable to access the specified method because access modifiers prevent it
206      * @throws java.lang.reflect.InvocationTargetException when a reflection invocation fails
207      */
208     public static Object invokeMethod(final Method method, final Object target, final Object... args) throws InvocationTargetException, IllegalAccessException {
209         if (method == null) {
210             throw new IllegalArgumentException("Method must not be null.");
211         }
212         if (target == null) {
213             throw new IllegalArgumentException("Object must not be null.");
214         }
215         method.setAccessible(true);
216         return method.invoke(target, args);
217     }
218 
219     /**
220      * Invoke the specified static {@link Method} with the supplied arguments.
221      *
222      * @param method The method to invoke
223      * @param args   The invocation arguments (may be <code>null</code>)
224      * @return The invocation result, if any
225      * @throws IllegalAccessException                      when unable to access the specified method because access modifiers prevent it
226      * @throws java.lang.reflect.InvocationTargetException when a reflection invocation fails
227      */
228     public static Object invokeStaticMethod(final Method method, final Object... args) throws InvocationTargetException, IllegalAccessException {
229         if (method == null) {
230             throw new IllegalArgumentException("Method must not be null.");
231         }
232         if (!Modifier.isStatic(method.getModifiers())) {
233             throw new IllegalArgumentException("Method must be static.");
234         }
235         method.setAccessible(true);
236         return method.invoke(null, args);
237     }
238 
239     /**
240      * This variant retrieves {@link Class#getDeclaredMethods()} from a local cache in order to avoid the JVM's SecurityManager check and defensive array copying.
241      * In addition, it also includes Java 8 default methods from locally implemented interfaces, since those are effectively to be treated just like declared methods.
242      *
243      * @param clazz the class to introspect
244      * @return the cached array of methods
245      * @see Class#getDeclaredMethods()
246      */
247     private static Method[] getDeclaredMethods(Class<?> clazz) {
248         Method[] result;
249         Method[] declaredMethods = clazz.getDeclaredMethods();
250         List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);
251         if (defaultMethods != null) {
252             result = new Method[declaredMethods.length + defaultMethods.size()];
253             System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
254             int index = declaredMethods.length;
255             for (Method defaultMethod : defaultMethods) {
256                 result[index] = defaultMethod;
257                 index++;
258             }
259         } else {
260             result = declaredMethods;
261         }
262         return result;
263     }
264 
265     private static List<Method> findConcreteMethodsOnInterfaces(Class<?> clazz) {
266         List<Method> result = null;
267         for (Class<?> ifc : clazz.getInterfaces()) {
268             for (Method ifcMethod : ifc.getMethods()) {
269                 if (!Modifier.isAbstract(ifcMethod.getModifiers())) {
270                     if (result == null) {
271                         result = new ArrayList<>();
272                     }
273                     result.add(ifcMethod);
274                 }
275             }
276         }
277         return result;
278     }
279 
280 }