/********************************************************************* Copyright 2014 the Flapi authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ********************************************************************/ package unquietcode.tools.flapi.runtime; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.util.*; /** * Method reflection methods copied from Spring Framework's ReflectionUtils class, * with the minimum modifications. */ public class SpringMethodUtils { /** * Attempt to find a {@link java.lang.reflect.Method} on the supplied class with the supplied name * and parameter types. Searches all superclasses up to {@code Object}. * <p>Returns {@code null} if no {@link java.lang.reflect.Method} can be found. * @param clazz the class to introspect * @param name the name of the method * @param paramTypes the parameter types of the method * (may be {@code null} to indicate any signature) * @return the Method object, or {@code null} if none found */ public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) { if (clazz == null) { throw new NullPointerException("Class must not be null"); } if (name == null) { throw new NullPointerException("Method name must not be null"); } Class<?> searchType = clazz; while (searchType != null) { Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType)); for (Method method : methods) { if (name.equals(method.getName()) && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { return method; } } searchType = searchType.getSuperclass(); } return null; } /** * Cache for {@link Class#getDeclaredMethods()}, allowing for fast resolution. */ private static final Map<Class<?>, Reference<Method[]>> declaredMethodsCache = Collections.synchronizedMap(new WeakHashMap<Class<?>, Reference<Method[]>>()); /** * This method retrieves {@link Class#getDeclaredMethods()} from a local cache * in order to avoid the JVM's SecurityManager check and defensive array copying. */ private static Method[] getDeclaredMethods(Class<?> clazz) { Reference<Method[]> ref = declaredMethodsCache.get(clazz); Method[] result = null; if (ref != null) { result = ref.get(); } if (result == null) { result = clazz.getDeclaredMethods(); declaredMethodsCache.put(clazz, new SoftReference<Method[]>(result)); } return result; } /** * Perform the given callback operation on all matching methods of the given * class and superclasses. * <p>The same named method occurring on subclass and superclass will appear * twice, unless excluded by a {@link MethodFilter}. * @param clazz class to start looking at * @param mc the callback to invoke for each method * @see #doWithMethods(Class, MethodCallback, MethodFilter) */ public static void doWithMethods(Class<?> clazz, MethodCallback mc) throws IllegalArgumentException { doWithMethods(clazz, mc, null); } /** * Perform the given callback operation on all matching methods of the given * class and superclasses (or given interface and super-interfaces). * <p>The same named method occurring on subclass and superclass will appear * twice, unless excluded by the specified {@link MethodFilter}. * @param clazz class to start looking at * @param mc the callback to invoke for each method * @param mf the filter that determines the methods to apply the callback to */ public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) throws IllegalArgumentException { // Keep backing up the inheritance hierarchy. Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (mf != null && !mf.matches(method)) { continue; } try { mc.doWith(method); } catch (IllegalAccessException ex) { throw new IllegalStateException("Shouldn't be illegal to access method '" + method.getName() + "': " + ex); } } if (clazz.getSuperclass() != null) { doWithMethods(clazz.getSuperclass(), mc, mf); } else if (clazz.isInterface()) { for (Class<?> superIfc : clazz.getInterfaces()) { doWithMethods(superIfc, mc, mf); } } } /** * Get the unique set of declared methods on the leaf class and all superclasses. Leaf * class methods are included first and while traversing the superclass hierarchy any methods found * with signatures matching a method already included are filtered out. */ public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException { final List<Method> methods = new ArrayList<Method>(32); doWithMethods(leafClass, new MethodCallback() { @Override public void doWith(Method method) { boolean knownSignature = false; Method methodBeingOverriddenWithCovariantReturnType = null; for (Method existingMethod : methods) { if (method.getName().equals(existingMethod.getName()) && Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) { // Is this a covariant return type situation? if (existingMethod.getReturnType() != method.getReturnType() && existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) { methodBeingOverriddenWithCovariantReturnType = existingMethod; } else { knownSignature = true; } break; } } if (methodBeingOverriddenWithCovariantReturnType != null) { methods.remove(methodBeingOverriddenWithCovariantReturnType); } if (!knownSignature) { methods.add(method); } } }); return methods.toArray(new Method[methods.size()]); } /** * Action to take on each method. */ public interface MethodCallback { /** * Perform an operation using the given method. * @param method the method to operate on */ void doWith(Method method) throws IllegalArgumentException, IllegalAccessException; } /** * Callback optionally used to filter methods to be operated on by a method callback. */ public interface MethodFilter { /** * Determine whether the given method matches. * @param method the method to check */ boolean matches(Method method); } }