• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright (c) 2011 Google, Inc.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *    Google, Inc. - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.wb.internal.core.utils.reflect;
12 
13 import com.google.common.collect.Maps;
14 
15 import org.eclipse.wb.internal.core.utils.check.Assert;
16 
17 import java.lang.reflect.Field;
18 import java.lang.reflect.GenericArrayType;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.ParameterizedType;
22 import java.lang.reflect.Type;
23 import java.lang.reflect.TypeVariable;
24 import java.lang.reflect.WildcardType;
25 import java.util.Map;
26 
27 /**
28  * Contains different Java reflection utilities.
29  *
30  * @author scheglov_ke
31  * @coverage core.util
32  */
33 public class ReflectionUtils {
34   ////////////////////////////////////////////////////////////////////////////
35   //
36   // Constructor
37   //
38   ////////////////////////////////////////////////////////////////////////////
ReflectionUtils()39   private ReflectionUtils() {
40   }
41 
42   ////////////////////////////////////////////////////////////////////////////
43   //
44   // Signature
45   //
46   ////////////////////////////////////////////////////////////////////////////
47   /**
48    * @param runtime
49    *          is <code>true</code> if we need name for class loading, <code>false</code> if we need
50    *          name for source generation.
51    *
52    * @return the fully qualified name of given {@link Type}.
53    */
getFullyQualifiedName(Type type, boolean runtime)54   public static String getFullyQualifiedName(Type type, boolean runtime) {
55     Assert.isNotNull(type);
56     // Class
57     if (type instanceof Class<?>) {
58       Class<?> clazz = (Class<?>) type;
59       // array
60       if (clazz.isArray()) {
61         return getFullyQualifiedName(clazz.getComponentType(), runtime) + "[]";
62       }
63       // object
64       String name = clazz.getName();
65       if (!runtime) {
66         name = name.replace('$', '.');
67       }
68       return name;
69     }
70     // GenericArrayType
71     if (type instanceof GenericArrayType) {
72       GenericArrayType genericArrayType = (GenericArrayType) type;
73       return getFullyQualifiedName(genericArrayType.getGenericComponentType(), runtime) + "[]";
74     }
75     // ParameterizedType
76     if (type instanceof ParameterizedType) {
77       ParameterizedType parameterizedType = (ParameterizedType) type;
78       Type rawType = parameterizedType.getRawType();
79       // raw type
80       StringBuilder sb = new StringBuilder();
81       sb.append(getFullyQualifiedName(rawType, runtime));
82       // type arguments
83       sb.append("<");
84       boolean firstTypeArgument = true;
85       for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
86         if (!firstTypeArgument) {
87           sb.append(",");
88         }
89         firstTypeArgument = false;
90         sb.append(getFullyQualifiedName(typeArgument, runtime));
91       }
92       sb.append(">");
93       // done
94       return sb.toString();
95     }
96     // WildcardType
97     if (type instanceof WildcardType) {
98       WildcardType wildcardType = (WildcardType) type;
99       return "? extends " + getFullyQualifiedName(wildcardType.getUpperBounds()[0], runtime);
100     }
101     // TypeVariable
102     TypeVariable<?> typeVariable = (TypeVariable<?>) type;
103     return typeVariable.getName();
104   }
105 
106   /**
107    * Appends fully qualified names of given parameter types (appends also <code>"()"</code>).
108    */
appendParameterTypes(StringBuilder buffer, Type[] parameterTypes)109   private static void appendParameterTypes(StringBuilder buffer, Type[] parameterTypes) {
110     buffer.append('(');
111     boolean firstParameter = true;
112     for (Type parameterType : parameterTypes) {
113       if (firstParameter) {
114         firstParameter = false;
115       } else {
116         buffer.append(',');
117       }
118       buffer.append(getFullyQualifiedName(parameterType, false));
119     }
120     buffer.append(')');
121   }
122 
123   ////////////////////////////////////////////////////////////////////////////
124   //
125   // Method
126   //
127   ////////////////////////////////////////////////////////////////////////////
128   /**
129    * @return all declared {@link Method}'s, including protected and private.
130    */
getMethods(Class<?> clazz)131   public static Map<String, Method> getMethods(Class<?> clazz) {
132     Map<String, Method> methods = Maps.newHashMap();
133     // process classes
134     for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
135       for (Method method : c.getDeclaredMethods()) {
136         String signature = getMethodSignature(method);
137         if (!methods.containsKey(signature)) {
138           method.setAccessible(true);
139           methods.put(signature, method);
140         }
141       }
142     }
143     // process interfaces
144     for (Class<?> interfaceClass : clazz.getInterfaces()) {
145       for (Method method : interfaceClass.getDeclaredMethods()) {
146         String signature = getMethodSignature(method);
147         if (!methods.containsKey(signature)) {
148           method.setAccessible(true);
149           methods.put(signature, method);
150         }
151       }
152     }
153     // done
154     return methods;
155   }
156 
157   /**
158    * @return signature for given {@link Method}. This signature is not same signature as in JVM or
159    *         JDT, just some string that unique identifies method in its {@link Class}.
160    */
getMethodSignature(Method method)161   public static String getMethodSignature(Method method) {
162     Assert.isNotNull(method);
163     return getMethodSignature(method.getName(), method.getParameterTypes());
164   }
165 
166   /**
167    * Returns the signature of {@link Method} with given combination of name and parameter types.
168    * This signature is not same signature as in JVM or JDT, just some string that unique identifies
169    * method in its {@link Class}.
170    *
171    * @param name
172    *          the name of {@link Method}.
173    * @param parameterTypes
174    *          the types of {@link Method} parameters.
175    *
176    * @return signature of {@link Method}.
177    */
getMethodSignature(String name, Type... parameterTypes)178   public static String getMethodSignature(String name, Type... parameterTypes) {
179     Assert.isNotNull(name);
180     Assert.isNotNull(parameterTypes);
181     //
182     StringBuilder buffer = new StringBuilder();
183     buffer.append(name);
184     appendParameterTypes(buffer, parameterTypes);
185     return buffer.toString();
186   }
187 
188   private static final ClassMap<Map<String, Method>> m_getMethodBySignature = ClassMap.create();
189 
190   /**
191    * Returns the {@link Method} defined in {@link Class}. This method can have any visibility, i.e.
192    * we can find even protected/private methods. Can return <code>null</code> if no method with
193    * given signature found.
194    *
195    * @param clazz
196    *          the {@link Class} to get method from it, or its superclass.
197    * @param signature
198    *          the signature of method in same format as {@link #getMethodSignature(Method)}.
199    *
200    * @return the {@link Method} for given signature, or <code>null</code> if no such method found.
201    */
getMethodBySignature(Class<?> clazz, String signature)202   public static Method getMethodBySignature(Class<?> clazz, String signature) {
203     Assert.isNotNull(clazz);
204     Assert.isNotNull(signature);
205     // prepare cache
206     Map<String, Method> cache = m_getMethodBySignature.get(clazz);
207     if (cache == null) {
208       cache = getMethods(clazz);
209       m_getMethodBySignature.put(clazz, cache);
210     }
211     // use cache
212     return cache.get(signature);
213   }
214 
215   /**
216    * @return the {@link Object} result of invoking method with given signature.
217    */
invokeMethod(Object object, String signature, Object... arguments)218   public static Object invokeMethod(Object object, String signature, Object... arguments)
219       throws Exception {
220     Assert.isNotNull(object);
221     Assert.isNotNull(arguments);
222     // prepare class/object
223     Class<?> refClass = getRefClass(object);
224     Object refObject = getRefObject(object);
225     // prepare method
226     Method method = getMethodBySignature(refClass, signature);
227     Assert.isNotNull(method, "Can not find method " + signature + " in " + refClass);
228     // do invoke
229     try {
230       return method.invoke(refObject, arguments);
231     } catch (InvocationTargetException e) {
232       throw propagate(e.getCause());
233     }
234   }
235 
236   /**
237    * Invokes method by name and parameter types.
238    *
239    * @param object
240    *          the object to call, may be {@link Class} for invoking static method.
241    * @param name
242    *          the name of method.
243    * @param parameterTypes
244    *          the types of parameters.
245    * @param arguments
246    *          the values of argument for invocation.
247    *
248    * @return the {@link Object} result of invoking method.
249    */
invokeMethod2(Object object, String name, Class<?>[] parameterTypes, Object[] arguments)250   public static Object invokeMethod2(Object object,
251       String name,
252       Class<?>[] parameterTypes,
253       Object[] arguments) throws Exception {
254     Assert.equals(parameterTypes.length, arguments.length);
255     String signature = getMethodSignature(name, parameterTypes);
256     return invokeMethod(object, signature, arguments);
257   }
258 
259   ////////////////////////////////////////////////////////////////////////////
260   //
261   // Utils
262   //
263   ////////////////////////////////////////////////////////////////////////////
264   /**
265    * @return the {@link Class} of given {@link Object} or casted object, if it is {@link Class}
266    *         itself.
267    */
getRefClass(Object object)268   private static Class<?> getRefClass(Object object) {
269     return object instanceof Class<?> ? (Class<?>) object : object.getClass();
270   }
271 
272   /**
273    * @return the {@link Object} that should be used as argument for {@link Field#get(Object)} and
274    *         {@link Method#invoke(Object, Object[])}.
275    */
getRefObject(Object object)276   private static Object getRefObject(Object object) {
277     return object instanceof Class<?> ? null : object;
278   }
279 
280   ////////////////////////////////////////////////////////////////////////////
281   //
282   // Throwable propagation
283   //
284   ////////////////////////////////////////////////////////////////////////////
285   /**
286    * Helper class used in {@link #propagate(Throwable)}.
287    */
288   private static class ExceptionThrower {
289     private static Throwable throwable;
290 
ExceptionThrower()291     private ExceptionThrower() throws Throwable {
292       if (System.getProperty("wbp.ReflectionUtils.propagate().InstantiationException") != null) {
293         throw new InstantiationException();
294       }
295       if (System.getProperty("wbp.ReflectionUtils.propagate().IllegalAccessException") != null) {
296         throw new IllegalAccessException();
297       }
298       throw throwable;
299     }
300 
spit(Throwable t)301     public static synchronized void spit(Throwable t) {
302       if (System.getProperty("wbp.ReflectionUtils.propagate().dontThrow") == null) {
303         ExceptionThrower.throwable = t;
304         try {
305           ExceptionThrower.class.newInstance();
306         } catch (InstantiationException e) {
307         } catch (IllegalAccessException e) {
308         } finally {
309           ExceptionThrower.throwable = null;
310         }
311       }
312     }
313   }
314 
315   /**
316    * Propagates {@code throwable} as-is without any wrapping. This is trick.
317    *
318    * @return nothing will ever be returned; this return type is only for your convenience, to use
319    *         this method in "throw" statement.
320    */
propagate(Throwable throwable)321   public static RuntimeException propagate(Throwable throwable) {
322     if (System.getProperty("wbp.ReflectionUtils.propagate().forceReturn") == null) {
323       ExceptionThrower.spit(throwable);
324     }
325     return null;
326   }
327 }
328