001 /* 002 * (c) 2003-2005, 2009, 2010 ThoughtWorks Ltd 003 * All rights reserved. 004 * 005 * The software in this package is published under the terms of the BSD 006 * style license a copy of which has been included with this distribution in 007 * the LICENSE.txt file. 008 * 009 * Created on 11-May-2004 010 */ 011 package com.thoughtworks.proxy.kit; 012 013 import java.io.IOException; 014 import java.io.InvalidObjectException; 015 import java.io.ObjectInputStream; 016 import java.io.ObjectOutputStream; 017 import java.lang.reflect.Method; 018 import java.util.HashSet; 019 import java.util.Set; 020 021 import com.thoughtworks.proxy.ProxyFactory; 022 import com.thoughtworks.proxy.factory.InvokerReference; 023 024 /** 025 * Helper class for introspecting interface and class hierarchies. 026 * 027 * @author Aslak Hellesøy 028 * @author Jörg Schaible 029 * @since 0.2 030 */ 031 public class ReflectionUtils { 032 033 /** 034 * the {@link Object#equals(Object)} method. 035 */ 036 public static final Method equals; 037 /** 038 * the {@link Object#hashCode()} method. 039 */ 040 public static final Method hashCode; 041 /** 042 * the {@link Object#toString()} method. 043 */ 044 public static final Method toString; 045 046 static { 047 try { 048 equals = Object.class.getMethod("equals", new Class[]{Object.class}); 049 hashCode = Object.class.getMethod("hashCode"); 050 toString = Object.class.getMethod("toString"); 051 } catch (NoSuchMethodException e) { 052 throw new ExceptionInInitializerError(e.toString()); 053 } 054 } 055 056 /** 057 * Constructor. Do not call, it is a factory. 058 */ 059 private ReflectionUtils() { 060 } 061 062 /** 063 * Get all the interfaces implemented by a list of objects. 064 * 065 * @param objects the list of objects to consider. 066 * @return an set of interfaces. The set may be empty 067 * @since 0.2 068 */ 069 public static Set<Class<?>> getAllInterfaces(final Object... objects) { 070 final Set<Class<?>> interfaces = new HashSet<Class<?>>(); 071 for (Object object : objects) { 072 if (object != null) { 073 getInterfaces(object.getClass(), interfaces); 074 } 075 } 076 interfaces.remove(InvokerReference.class); 077 return interfaces; 078 } 079 080 /** 081 * Get all interfaces of the given type. If the type is a class, the returned set contains any interface, that is 082 * implemented by the class. If the type is an interface, the all superinterfaces and the interface itself are 083 * included. 084 * 085 * @param type type to explore. 086 * @return a {@link Set} with all interfaces. The set may be empty. 087 * @since 0.2 088 */ 089 public static Set<Class<?>> getAllInterfaces(final Class<?> type) { 090 final Set<Class<?>> interfaces = new HashSet<Class<?>>(); 091 getInterfaces(type, interfaces); 092 interfaces.remove(InvokerReference.class); 093 return interfaces; 094 } 095 096 private static void getInterfaces(Class<?> type, final Set<Class<?>> interfaces) { 097 if (type.isInterface()) { 098 interfaces.add(type); 099 } 100 // Class.getInterfaces will return only the interfaces that are 101 // implemented by the current class. Therefore we must loop up 102 // the hierarchy for the superclasses and the superinterfaces. 103 while (type != null) { 104 for (Class<?> anImplemented : type.getInterfaces()) { 105 if (!interfaces.contains(anImplemented)) { 106 getInterfaces(anImplemented, interfaces); 107 } 108 } 109 type = type.getSuperclass(); 110 } 111 } 112 113 /** 114 * Get most common superclass for all given objects. 115 * 116 * @param objects the array of objects to consider. 117 * @return the superclass or <code>{@link Void Void.class}</code> for an empty array. 118 * @since 0.2 119 */ 120 public static Class<?> getMostCommonSuperclass(final Object... objects) { 121 Class<?> type = null; 122 boolean found = false; 123 if (objects != null && objects.length > 0) { 124 while (!found) { 125 for (Object object : objects) { 126 found = true; 127 if (object != null) { 128 final Class<?> currenttype = object.getClass(); 129 if (type == null) { 130 type = currenttype; 131 } 132 if (!type.isAssignableFrom(currenttype)) { 133 if (currenttype.isAssignableFrom(type)) { 134 type = currenttype; 135 } else { 136 type = type.getSuperclass(); 137 found = false; 138 break; 139 } 140 } 141 } 142 } 143 } 144 } 145 if (type == null) { 146 type = Object.class; 147 } 148 return type; 149 } 150 151 /** 152 * Add the given type to the set of interfaces, if the given ProxyFactory supports proxy generation for this type. 153 * 154 * @param type the class type (<code>Object.class</code> will be ignored) 155 * @param interfaces the set of interfaces 156 * @param proxyFactory the {@link ProxyFactory} in use 157 * @since 0.2 158 */ 159 public static void addIfClassProxyingSupportedAndNotObject( 160 final Class<?> type, final Set<Class<?>> interfaces, final ProxyFactory proxyFactory) { 161 if (proxyFactory.canProxy(type) && !type.equals(Object.class)) { 162 interfaces.add(type); 163 } 164 } 165 166 /** 167 * Get the method of the given type, that has matching parameter types to the given arguments. 168 * 169 * @param type the type 170 * @param methodName the name of the method to search 171 * @param args the arguments to match 172 * @return the matching {@link Method} 173 * @throws NoSuchMethodException if no matching {@link Method} exists 174 * @since 0.2 175 */ 176 public static Method getMatchingMethod(final Class<?> type, final String methodName, final Object[] args) 177 throws NoSuchMethodException { 178 final Object[] newArgs = args == null ? new Object[0] : args; 179 final Method[] methods = type.getMethods(); 180 final Set<Method> possibleMethods = new HashSet<Method>(); 181 Method method = null; 182 for (int i = 0; method == null && i < methods.length; i++) { 183 if (methodName.equals(methods[i].getName())) { 184 final Class<?>[] argTypes = methods[i].getParameterTypes(); 185 if (argTypes.length == newArgs.length) { 186 boolean exact = true; 187 Method possibleMethod = methods[i]; 188 for (int j = 0; possibleMethod != null && j < argTypes.length; j++) { 189 final Class<?> newArgType = newArgs[j] != null ? newArgs[j].getClass() : Object.class; 190 if ((argTypes[j].equals(byte.class) && newArgType.equals(Byte.class)) 191 || (argTypes[j].equals(char.class) && newArgType.equals(Character.class)) 192 || (argTypes[j].equals(short.class) && newArgType.equals(Short.class)) 193 || (argTypes[j].equals(int.class) && newArgType.equals(Integer.class)) 194 || (argTypes[j].equals(long.class) && newArgType.equals(Long.class)) 195 || (argTypes[j].equals(float.class) && newArgType.equals(Float.class)) 196 || (argTypes[j].equals(double.class) && newArgType.equals(Double.class)) 197 || (argTypes[j].equals(boolean.class) && newArgType.equals(Boolean.class))) { 198 exact = true; 199 } else if (!argTypes[j].isAssignableFrom(newArgType)) { 200 possibleMethod = null; 201 exact = false; 202 } else if (!argTypes[j].isPrimitive()) { 203 if (!argTypes[j].equals(newArgType)) { 204 exact = false; 205 } 206 } 207 } 208 if (exact) { 209 method = possibleMethod; 210 } else if (possibleMethod != null) { 211 possibleMethods.add(possibleMethod); 212 } 213 } 214 } 215 } 216 if (method == null && possibleMethods.size() > 0) { 217 method = possibleMethods.iterator().next(); 218 } 219 if (method == null) { 220 final StringBuilder name = new StringBuilder(type.getName()); 221 name.append('.'); 222 name.append(methodName); 223 name.append('('); 224 for (int i = 0; i < newArgs.length; i++) { 225 if (i != 0) { 226 name.append(", "); 227 } 228 name.append(newArgs[i].getClass().getName()); 229 } 230 name.append(')'); 231 throw new NoSuchMethodException(name.toString()); 232 } 233 return method; 234 } 235 236 /** 237 * Write a {@link Method} into an {@link ObjectOutputStream}. 238 * 239 * @param out the stream 240 * @param method the {@link Method} to write 241 * @throws IOException if writing causes a problem 242 * @since 0.2 243 */ 244 public static void writeMethod(final ObjectOutputStream out, final Method method) throws IOException { 245 out.writeObject(method.getDeclaringClass()); 246 out.writeObject(method.getName()); 247 out.writeObject(method.getParameterTypes()); 248 } 249 250 /** 251 * Read a {@link Method} from an {@link ObjectInputStream}. 252 * 253 * @param in the stream 254 * @return the read {@link Method} 255 * @throws IOException if reading causes a problem 256 * @throws ClassNotFoundException if class types from objects of the InputStream cannot be found 257 * @throws InvalidObjectException if the {@link Method} cannot be found 258 * @since 0.2 259 */ 260 public static Method readMethod(final ObjectInputStream in) throws IOException, ClassNotFoundException { 261 final Class<?> type = Class.class.cast(in.readObject()); 262 final String name = String.class.cast(in.readObject()); 263 final Class<?>[] parameters = Class[].class.cast(in.readObject()); 264 try { 265 return type.getMethod(name, parameters); 266 } catch (final NoSuchMethodException e) { 267 throw new InvalidObjectException(e.getMessage()); 268 } 269 } 270 271 /** 272 * Create an array of types. 273 * 274 * @param primaryType the primary types 275 * @param types the additional types (may be null) 276 * @return an array of all the given types with the primary type as first element 277 * @since 1.0 278 */ 279 public static Class<?>[] makeTypesArray(Class<?> primaryType, Class<?>[] types) { 280 if (primaryType == null) { 281 return types; 282 } 283 Class<?>[] retVal = new Class[types == null ? 1 : types.length +1]; 284 retVal[0] = primaryType; 285 if (types != null) { 286 System.arraycopy(types, 0, retVal, 1, types.length); 287 } 288 return retVal; 289 } 290 }