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 *******************************************************************************/
11package org.eclipse.wb.internal.core.utils.reflect;
12
13import com.google.common.collect.Maps;
14
15import org.eclipse.wb.internal.core.utils.check.Assert;
16
17import java.lang.reflect.Field;
18import java.lang.reflect.GenericArrayType;
19import java.lang.reflect.InvocationTargetException;
20import java.lang.reflect.Method;
21import java.lang.reflect.ParameterizedType;
22import java.lang.reflect.Type;
23import java.lang.reflect.TypeVariable;
24import java.lang.reflect.WildcardType;
25import java.util.Map;
26
27/**
28 * Contains different Java reflection utilities.
29 *
30 * @author scheglov_ke
31 * @coverage core.util
32 */
33public class ReflectionUtils {
34  ////////////////////////////////////////////////////////////////////////////
35  //
36  // Constructor
37  //
38  ////////////////////////////////////////////////////////////////////////////
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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
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
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   */
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