summaryrefslogtreecommitdiff
path: root/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ReflectionUtils.java
blob: b3622116bdab5e7cae8d616a3f48905a9f3e45c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/*******************************************************************************
 * Copyright (c) 2011 Google, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Google, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.wb.internal.core.utils.reflect;

import com.google.common.collect.Maps;

import org.eclipse.wb.internal.core.utils.check.Assert;

import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Map;

/**
 * Contains different Java reflection utilities.
 *
 * @author scheglov_ke
 * @coverage core.util
 */
public class ReflectionUtils {
  ////////////////////////////////////////////////////////////////////////////
  //
  // Constructor
  //
  ////////////////////////////////////////////////////////////////////////////
  private ReflectionUtils() {
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Signature
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * @param runtime
   *          is <code>true</code> if we need name for class loading, <code>false</code> if we need
   *          name for source generation.
   *
   * @return the fully qualified name of given {@link Type}.
   */
  public static String getFullyQualifiedName(Type type, boolean runtime) {
    Assert.isNotNull(type);
    // Class
    if (type instanceof Class<?>) {
      Class<?> clazz = (Class<?>) type;
      // array
      if (clazz.isArray()) {
        return getFullyQualifiedName(clazz.getComponentType(), runtime) + "[]";
      }
      // object
      String name = clazz.getName();
      if (!runtime) {
        name = name.replace('$', '.');
      }
      return name;
    }
    // GenericArrayType
    if (type instanceof GenericArrayType) {
      GenericArrayType genericArrayType = (GenericArrayType) type;
      return getFullyQualifiedName(genericArrayType.getGenericComponentType(), runtime) + "[]";
    }
    // ParameterizedType
    if (type instanceof ParameterizedType) {
      ParameterizedType parameterizedType = (ParameterizedType) type;
      Type rawType = parameterizedType.getRawType();
      // raw type
      StringBuilder sb = new StringBuilder();
      sb.append(getFullyQualifiedName(rawType, runtime));
      // type arguments
      sb.append("<");
      boolean firstTypeArgument = true;
      for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
        if (!firstTypeArgument) {
          sb.append(",");
        }
        firstTypeArgument = false;
        sb.append(getFullyQualifiedName(typeArgument, runtime));
      }
      sb.append(">");
      // done
      return sb.toString();
    }
    // WildcardType
    if (type instanceof WildcardType) {
      WildcardType wildcardType = (WildcardType) type;
      return "? extends " + getFullyQualifiedName(wildcardType.getUpperBounds()[0], runtime);
    }
    // TypeVariable
    TypeVariable<?> typeVariable = (TypeVariable<?>) type;
    return typeVariable.getName();
  }

  /**
   * Appends fully qualified names of given parameter types (appends also <code>"()"</code>).
   */
  private static void appendParameterTypes(StringBuilder buffer, Type[] parameterTypes) {
    buffer.append('(');
    boolean firstParameter = true;
    for (Type parameterType : parameterTypes) {
      if (firstParameter) {
        firstParameter = false;
      } else {
        buffer.append(',');
      }
      buffer.append(getFullyQualifiedName(parameterType, false));
    }
    buffer.append(')');
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Method
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * @return all declared {@link Method}'s, including protected and private.
   */
  public static Map<String, Method> getMethods(Class<?> clazz) {
    Map<String, Method> methods = Maps.newHashMap();
    // process classes
    for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
      for (Method method : c.getDeclaredMethods()) {
        String signature = getMethodSignature(method);
        if (!methods.containsKey(signature)) {
          method.setAccessible(true);
          methods.put(signature, method);
        }
      }
    }
    // process interfaces
    for (Class<?> interfaceClass : clazz.getInterfaces()) {
      for (Method method : interfaceClass.getDeclaredMethods()) {
        String signature = getMethodSignature(method);
        if (!methods.containsKey(signature)) {
          method.setAccessible(true);
          methods.put(signature, method);
        }
      }
    }
    // done
    return methods;
  }

  /**
   * @return signature for given {@link Method}. This signature is not same signature as in JVM or
   *         JDT, just some string that unique identifies method in its {@link Class}.
   */
  public static String getMethodSignature(Method method) {
    Assert.isNotNull(method);
    return getMethodSignature(method.getName(), method.getParameterTypes());
  }

  /**
   * Returns the signature of {@link Method} with given combination of name and parameter types.
   * This signature is not same signature as in JVM or JDT, just some string that unique identifies
   * method in its {@link Class}.
   *
   * @param name
   *          the name of {@link Method}.
   * @param parameterTypes
   *          the types of {@link Method} parameters.
   *
   * @return signature of {@link Method}.
   */
  public static String getMethodSignature(String name, Type... parameterTypes) {
    Assert.isNotNull(name);
    Assert.isNotNull(parameterTypes);
    //
    StringBuilder buffer = new StringBuilder();
    buffer.append(name);
    appendParameterTypes(buffer, parameterTypes);
    return buffer.toString();
  }

  private static final ClassMap<Map<String, Method>> m_getMethodBySignature = ClassMap.create();

  /**
   * Returns the {@link Method} defined in {@link Class}. This method can have any visibility, i.e.
   * we can find even protected/private methods. Can return <code>null</code> if no method with
   * given signature found.
   *
   * @param clazz
   *          the {@link Class} to get method from it, or its superclass.
   * @param signature
   *          the signature of method in same format as {@link #getMethodSignature(Method)}.
   *
   * @return the {@link Method} for given signature, or <code>null</code> if no such method found.
   */
  public static Method getMethodBySignature(Class<?> clazz, String signature) {
    Assert.isNotNull(clazz);
    Assert.isNotNull(signature);
    // prepare cache
    Map<String, Method> cache = m_getMethodBySignature.get(clazz);
    if (cache == null) {
      cache = getMethods(clazz);
      m_getMethodBySignature.put(clazz, cache);
    }
    // use cache
    return cache.get(signature);
  }

  /**
   * @return the {@link Object} result of invoking method with given signature.
   */
  public static Object invokeMethod(Object object, String signature, Object... arguments)
      throws Exception {
    Assert.isNotNull(object);
    Assert.isNotNull(arguments);
    // prepare class/object
    Class<?> refClass = getRefClass(object);
    Object refObject = getRefObject(object);
    // prepare method
    Method method = getMethodBySignature(refClass, signature);
    Assert.isNotNull(method, "Can not find method " + signature + " in " + refClass);
    // do invoke
    try {
      return method.invoke(refObject, arguments);
    } catch (InvocationTargetException e) {
      throw propagate(e.getCause());
    }
  }

  /**
   * Invokes method by name and parameter types.
   *
   * @param object
   *          the object to call, may be {@link Class} for invoking static method.
   * @param name
   *          the name of method.
   * @param parameterTypes
   *          the types of parameters.
   * @param arguments
   *          the values of argument for invocation.
   *
   * @return the {@link Object} result of invoking method.
   */
  public static Object invokeMethod2(Object object,
      String name,
      Class<?>[] parameterTypes,
      Object[] arguments) throws Exception {
    Assert.equals(parameterTypes.length, arguments.length);
    String signature = getMethodSignature(name, parameterTypes);
    return invokeMethod(object, signature, arguments);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Utils
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * @return the {@link Class} of given {@link Object} or casted object, if it is {@link Class}
   *         itself.
   */
  private static Class<?> getRefClass(Object object) {
    return object instanceof Class<?> ? (Class<?>) object : object.getClass();
  }

  /**
   * @return the {@link Object} that should be used as argument for {@link Field#get(Object)} and
   *         {@link Method#invoke(Object, Object[])}.
   */
  private static Object getRefObject(Object object) {
    return object instanceof Class<?> ? null : object;
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Throwable propagation
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Helper class used in {@link #propagate(Throwable)}.
   */
  private static class ExceptionThrower {
    private static Throwable throwable;

    private ExceptionThrower() throws Throwable {
      if (System.getProperty("wbp.ReflectionUtils.propagate().InstantiationException") != null) {
        throw new InstantiationException();
      }
      if (System.getProperty("wbp.ReflectionUtils.propagate().IllegalAccessException") != null) {
        throw new IllegalAccessException();
      }
      throw throwable;
    }

    public static synchronized void spit(Throwable t) {
      if (System.getProperty("wbp.ReflectionUtils.propagate().dontThrow") == null) {
        ExceptionThrower.throwable = t;
        try {
          ExceptionThrower.class.newInstance();
        } catch (InstantiationException e) {
        } catch (IllegalAccessException e) {
        } finally {
          ExceptionThrower.throwable = null;
        }
      }
    }
  }

  /**
   * Propagates {@code throwable} as-is without any wrapping. This is trick.
   *
   * @return nothing will ever be returned; this return type is only for your convenience, to use
   *         this method in "throw" statement.
   */
  public static RuntimeException propagate(Throwable throwable) {
    if (System.getProperty("wbp.ReflectionUtils.propagate().forceReturn") == null) {
      ExceptionThrower.spit(throwable);
    }
    return null;
  }
}