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    }