Home | History | Annotate | Download | only in reflect
      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