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 */
011package com.thoughtworks.proxy.kit;
012
013import java.io.IOException;
014import java.io.InvalidObjectException;
015import java.io.ObjectInputStream;
016import java.io.ObjectOutputStream;
017import java.lang.reflect.Method;
018import java.util.HashSet;
019import java.util.Set;
020
021import com.thoughtworks.proxy.ProxyFactory;
022import 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 */
031public 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}