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 //////////////////////////////////////////////////////////////////////////// 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