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 }