commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mben...@apache.org
Subject svn commit: r638703 - in /commons/proper/lang/branches/LANG_POST_2_4/src: java/org/apache/commons/lang/ test/org/apache/commons/lang/
Date Wed, 19 Mar 2008 04:31:47 GMT
Author: mbenson
Date: Tue Mar 18 21:31:46 2008
New Revision: 638703

URL: http://svn.apache.org/viewvc?rev=638703&view=rev
Log:
[LANG-416] import MethodUtils, ConstructorUtils from [beanutils]

Added:
    commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/ConstructorUtils.java   (with props)
    commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MemberUtils.java   (with props)
    commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MethodUtils.java   (with props)
    commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/ConstructorUtilsTest.java   (with props)
    commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/MethodUtilsTest.java   (with props)
Modified:
    commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/LangTestSuite.java

Added: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/ConstructorUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/ConstructorUtils.java?rev=638703&view=auto
==============================================================================
--- commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/ConstructorUtils.java (added)
+++ commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/ConstructorUtils.java Tue Mar 18 21:31:46 2008
@@ -0,0 +1,337 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+
+/**
+ * <p> Utility reflection methods focussed on constructors, modelled after {@link MethodUtils}. </p>
+ *
+ * <h3>Known Limitations</h3>
+ * <h4>Accessing Public Constructors In A Default Access Superclass</h4>
+ * <p>There is an issue when invoking public constructors contained in a default access superclass.
+ * Reflection locates these constructors fine and correctly assigns them as public.
+ * However, an <code>IllegalAccessException</code> is thrown if the constructors is invoked.</p>
+ *
+ * <p><code>ConstructorUtils</code> contains a workaround for this situation.
+ * It will attempt to call <code>setAccessible</code> on this constructor.
+ * If this call succeeds, then the method can be invoked as normal.
+ * This call will only succeed when the application has sufficient security privilages.
+ * If this call fails then a warning will be logged and the method may fail.</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Ralph Schaer
+ * @author Chris Audley
+ * @author Rey Francois
+ * @author Gregor Rayman
+ * @author Jan Sorensen
+ * @author Robert Burrell Donkin
+ * @author Rodney Waldhoff
+ * @version $Revision$ $Date$
+ */
+public class ConstructorUtils {
+
+    /**
+     * <p>ConstructorUtils instances should NOT be constructed in standard programming.
+     * Instead, the class should be used as
+     * <code>ConstructorUtils.invokeConstructor(cls, args)</code>.</p>
+     *
+     * <p>This constructor is public to permit tools that require a JavaBean
+     * instance to operate.</p>
+     */
+    public ConstructorUtils() {
+        super();
+    }
+
+    /**
+     * <p>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
+     * The formal parameter type is inferred from the actual values of <code>arg</code>.
+     * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
+     *
+     * <p>The signatures should be assignment compatible.</p>
+     *
+     * @param cls the class to be constructed.
+     * @param arg the actual argument
+     * @return new instance of <code>klazz</code>
+     *
+     * @throws NoSuchMethodException If the constructor cannot be found
+     * @throws IllegalAccessException If an error occurs accessing the constructor
+     * @throws InvocationTargetException If an error occurs invoking the constructor
+     * @throws InstantiationException If an error occurs instantiating the class
+     *
+     * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
+     */
+    public static Object invokeConstructor(Class cls, Object arg)
+            throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException, InstantiationException {
+        return invokeConstructor(cls, new Object[] { arg });
+    }
+
+    /**
+     * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
+     * The formal parameter types are inferred from the actual values of <code>args</code>.
+     * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
+     *
+     * <p>The signatures should be assignment compatible.</p>
+     *
+     * @param cls the class to be constructed.
+     * @param args actual argument array
+     * @return new instance of <code>klazz</code>
+     *
+     * @throws NoSuchMethodException If the constructor cannot be found
+     * @throws IllegalAccessException If an error occurs accessing the constructor
+     * @throws InvocationTargetException If an error occurs invoking the constructor
+     * @throws InstantiationException If an error occurs instantiating the class
+     *
+     * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
+     */
+    public static Object invokeConstructor(Class cls, Object[] args)
+            throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException, InstantiationException {
+        if (null == args) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        Class parameterTypes[] = new Class[args.length];
+        for (int i = 0; i < args.length; i++) {
+            parameterTypes[i] = args[i].getClass();
+        }
+        return invokeConstructor(cls, args, parameterTypes);
+    }
+
+    /**
+     * <p>Returns new instance of <code>klazz</code> created using constructor
+     * with signature <code>parameterTypes</code> and actual arguments <code>args</code>.</p>
+     *
+     * <p>The signatures should be assignment compatible.</p>
+     *
+     * @param cls the class to be constructed.
+     * @param args actual argument array
+     * @param parameterTypes parameter types array
+     * @return new instance of <code>klazz</code>
+     *
+     * @throws NoSuchMethodException if matching constructor cannot be found
+     * @throws IllegalAccessException thrown on the constructor's invocation
+     * @throws InvocationTargetException thrown on the constructor's invocation
+     * @throws InstantiationException thrown on the constructor's invocation
+     * @see Constructor#newInstance
+     */
+    public static Object invokeConstructor(Class cls, Object[] args,
+            Class[] parameterTypes) throws NoSuchMethodException,
+            IllegalAccessException, InvocationTargetException,
+            InstantiationException {
+        if (parameterTypes == null) {
+            parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+        }
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        Constructor ctor = getMatchingAccessibleConstructor(cls, parameterTypes);
+        if (null == ctor) {
+            throw new NoSuchMethodException(
+                    "No such accessible constructor on object: "
+                            + cls.getName());
+        }
+        return ctor.newInstance(args);
+    }
+
+    /**
+     * <p>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
+     * The formal parameter type is inferred from the actual values of <code>arg</code>.
+     * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
+     *
+     * <p>The signatures should match exactly.</p>
+     *
+     * @param cls the class to be constructed.
+     * @param arg the actual argument
+     * @return new instance of <code>klazz</code>
+     *
+     * @throws NoSuchMethodException If the constructor cannot be found
+     * @throws IllegalAccessException If an error occurs accessing the constructor
+     * @throws InvocationTargetException If an error occurs invoking the constructor
+     * @throws InstantiationException If an error occurs instantiating the class
+     *
+     * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
+     */
+    public static Object invokeExactConstructor(Class cls, Object arg)
+            throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException, InstantiationException {
+        return invokeExactConstructor(cls, new Object[] { arg });
+    }
+
+    /**
+     * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
+     * The formal parameter types are inferred from the actual values of <code>args</code>.
+     * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
+     *
+     * <p>The signatures should match exactly.</p>
+     *
+     * @param cls the class to be constructed.
+     * @param args actual argument array
+     * @return new instance of <code>klazz</code>
+     *
+     * @throws NoSuchMethodException If the constructor cannot be found
+     * @throws IllegalAccessException If an error occurs accessing the constructor
+     * @throws InvocationTargetException If an error occurs invoking the constructor
+     * @throws InstantiationException If an error occurs instantiating the class
+     *
+     * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
+     */
+    public static Object invokeExactConstructor(Class cls, Object[] args)
+            throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException, InstantiationException {
+        if (null == args) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        int arguments = args.length;
+        Class parameterTypes[] = new Class[arguments];
+        for (int i = 0; i < arguments; i++) {
+            parameterTypes[i] = args[i].getClass();
+        }
+        return invokeExactConstructor(cls, args, parameterTypes);
+    }
+
+    /**
+     * <p>Returns new instance of <code>klazz</code> created using constructor
+     * with signature <code>parameterTypes</code> and actual arguments
+     * <code>args</code>.</p>
+     *
+     * <p>The signatures should match exactly.</p>
+     *
+     * @param cls the class to be constructed.
+     * @param args actual argument array
+     * @param parameterTypes parameter types array
+     * @return new instance of <code>klazz</code>
+     *
+     * @throws NoSuchMethodException if matching constructor cannot be found
+     * @throws IllegalAccessException thrown on the constructor's invocation
+     * @throws InvocationTargetException thrown on the constructor's invocation
+     * @throws InstantiationException thrown on the constructor's invocation
+     * @see Constructor#newInstance
+     */
+    public static Object invokeExactConstructor(Class cls, Object[] args,
+            Class[] parameterTypes) throws NoSuchMethodException,
+            IllegalAccessException, InvocationTargetException,
+            InstantiationException {
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        if (parameterTypes == null) {
+            parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+        }
+        Constructor ctor = getAccessibleConstructor(cls, parameterTypes);
+        if (null == ctor) {
+            throw new NoSuchMethodException(
+                    "No such accessible constructor on object: "
+                            + cls.getName());
+        }
+        return ctor.newInstance(args);
+    }
+
+    /**
+     * Returns a constructor with single argument.
+     * @param cls the class to be constructed
+     * @param parameterType The constructor parameter type
+     * @return null if matching accessible constructor can not be found.
+     * @see Class#getConstructor
+     * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
+     */
+    public static Constructor getAccessibleConstructor(Class cls,
+            Class parameterType) {
+        return getAccessibleConstructor(cls, new Class[] { parameterType });
+    }
+
+    /**
+     * Returns a constructor given a class and signature.
+     * @param cls the class to be constructed
+     * @param parameterTypes the parameter array
+     * @return null if matching accessible constructor can not be found
+     * @see Class#getConstructor
+     * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
+     */
+    public static Constructor getAccessibleConstructor(Class cls,
+            Class[] parameterTypes) {
+        try {
+            return getAccessibleConstructor(cls.getConstructor(parameterTypes));
+        } catch (NoSuchMethodException e) {
+            return (null);
+        }
+    }
+
+    /**
+     * Returns accessible version of the given constructor.
+     * @param ctor prototype constructor object.
+     * @return <code>null</code> if accessible constructor can not be found.
+     * @see java.lang.SecurityManager
+     */
+    public static Constructor getAccessibleConstructor(Constructor ctor) {
+        return MemberUtils.isAccessible(ctor)
+                && Modifier.isPublic(ctor.getDeclaringClass().getModifiers()) ? ctor
+                : null;
+    }
+
+    /**
+     * <p>Find an accessible constructor with compatible parameters.
+     * Compatible parameters mean that every method parameter is assignable from
+     * the given parameters. In other words, it finds constructor that will take
+     * the parameters given.</p>
+     *
+     * <p>First it checks if there is constructor matching the exact signature.
+     * If no such, all the constructors of the class are tested if their signatures
+     * are assignment compatible with the parameter types.
+     * The first matching constructor is returned.</p>
+     *
+     * @param cls find constructor for this class
+     * @param parameterTypes find method with compatible parameters
+     * @return a valid Constructor object. If there's no matching constructor, returns <code>null</code>.
+     */
+    public static Constructor getMatchingAccessibleConstructor(Class cls,
+            Class[] parameterTypes) {
+        // see if we can find the constructor directly
+        // most of the time this works and it's much faster
+        try {
+            Constructor ctor = cls.getConstructor(parameterTypes);
+            MemberUtils.setAccessibleWorkaround(ctor);
+            return ctor;
+        } catch (NoSuchMethodException e) { /* SWALLOW */
+        }
+        Constructor result = null;
+        // search through all constructors
+        Constructor[] ctors = cls.getConstructors();
+        for (int i = 0; i < ctors.length; i++) {
+            // compare parameters
+            if (ClassUtils.isAssignable(parameterTypes, ctors[i]
+                    .getParameterTypes(), true)) {
+                // get accessible version of method
+                Constructor ctor = getAccessibleConstructor(ctors[i]);
+                if (ctor != null) {
+                    MemberUtils.setAccessibleWorkaround(ctor);
+                    if (result == null
+                            || MemberUtils.compareParameterTypes(ctor
+                                    .getParameterTypes(), result
+                                    .getParameterTypes(), parameterTypes) < 0) {
+                        result = ctor;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+}

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/ConstructorUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/ConstructorUtils.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Mar 18 21:31:46 2008
@@ -0,0 +1,5 @@
+Date
+Author
+Id
+Revision
+HeadURL

Added: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MemberUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MemberUtils.java?rev=638703&view=auto
==============================================================================
--- commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MemberUtils.java (added)
+++ commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MemberUtils.java Tue Mar 18 21:31:46 2008
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Contains common code for working with Methods/Constructors, extracted and
+ * refactored from <code>MethodUtils</code> when it was imported from Commons BeanUtils.
+ *
+ * @author Steve Cohen
+ * @author Matt Benson
+ */
+abstract class MemberUtils {
+    // TODO extract an interface to implement compareParameterSets(...)?
+
+    private static final Method IS_SYNTHETIC;
+    static {
+        Method isSynthetic = null;
+        if (SystemUtils.isJavaVersionAtLeast(1.5f)) {
+            // cannot call synthetic methods:
+            try {
+                isSynthetic = Member.class.getMethod("isSynthetic",
+                        ArrayUtils.EMPTY_CLASS_ARRAY);
+            } catch (Exception e) {
+            }
+        }
+        IS_SYNTHETIC = isSynthetic;
+    }
+
+    /** Array of primitive number types ordered by "promotability" */
+    private static final Class[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE,
+            Short.TYPE, Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE,
+            Double.TYPE };
+
+    /**
+     * XXX Default access superclass workaround
+     *
+     * When a public class has a default access superclass with public
+     * methods/constructors, these members are accessible. Calling them from
+     * compiled code works fine. Unfortunately, using reflection to invoke these
+     * members seems to (wrongly) to prevent access even when the
+     * modifer is public. Calling setAccessible(true) solves the problem
+     * but will only work from sufficiently privileged code. Better
+     * workarounds would be gratefully accepted.
+     * @param o the AccessibleObject to set as accessible
+     */
+    static void setAccessibleWorkaround(AccessibleObject o) {
+        if (!o.isAccessible() && SystemUtils.isJavaVersionAtLeast(1.4f)) {
+            try {
+                o.setAccessible(true);
+            } catch (SecurityException e) {
+                // ignore in favor of subsequent IllegalAccessException
+            }
+        }
+    }
+
+    /**
+     * Check a Member for basic accessibility.
+     * @param m Member to check
+     * @return true if <code>m</code> is accessible
+     */
+    static boolean isAccessible(Member m) {
+        return m != null && Modifier.isPublic(m.getModifiers())
+                && !isSynthetic(m);
+    }
+
+    /**
+     * Try to learn whether a given member, on JDK >= 1.5, is synthetic.
+     * @param m Member to check
+     * @return true if <code>m</code> was introduced by the compiler.
+     */
+    static boolean isSynthetic(Member m) {
+        if (IS_SYNTHETIC != null) {
+            try {
+                return ((Boolean) IS_SYNTHETIC.invoke(m, null)).booleanValue();
+            } catch (Exception e) {
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Compare the relative fitness of two sets of parameter types in terms of
+     * matching a third set of runtime parameter types, such that a list ordered
+     * by the results of the comparison would return the best match first (least).
+     *
+     * @param left the "left" parameter set
+     * @param right the "right" parameter set
+     * @param actual the runtime parameter types to match against <code>left</code>/<code>right</code>
+     * @return int consistent with <code>compare</code> semantics
+     */
+    static int compareParameterTypes(Class[] left, Class[] right, Class[] actual) {
+        float leftCost = getTotalTransformationCost(actual, left);
+        float rightCost = getTotalTransformationCost(actual, right);
+        return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0;
+    }
+
+    /**
+     * Returns the sum of the object transformation cost for each class in the source
+     * argument list.
+     * @param srcArgs The source arguments
+     * @param destArgs The destination arguments
+     * @return The total transformation cost
+     */
+    private static float getTotalTransformationCost(Class[] srcArgs,
+            Class[] destArgs) {
+        float totalCost = 0.0f;
+        for (int i = 0; i < srcArgs.length; i++) {
+            Class srcClass, destClass;
+            srcClass = srcArgs[i];
+            destClass = destArgs[i];
+            totalCost += getObjectTransformationCost(srcClass, destClass);
+        }
+        return totalCost;
+    }
+
+    /**
+     * Gets the number of steps required needed to turn the source class into the 
+     * destination class. This represents the number of steps in the object hierarchy 
+     * graph.
+     * @param srcClass The source class
+     * @param destClass The destination class
+     * @return The cost of transforming an object
+     */
+    private static float getObjectTransformationCost(Class srcClass,
+            Class destClass) {
+        if (destClass.isPrimitive()) {
+            return getPrimitivePromotionCost(srcClass, destClass);
+        }
+        float cost = 0.0f;
+        while (destClass != null && !destClass.equals(srcClass)) {
+            if (destClass.isInterface()
+                    && ClassUtils.isAssignable(srcClass, destClass)) {
+                // slight penalty for interface match.
+                // we still want an exact match to override an interface match,
+                // but
+                // an interface match should override anything where we have to
+                // get a superclass.
+                cost += 0.25f;
+                break;
+            }
+            cost++;
+            destClass = destClass.getSuperclass();
+        }
+        /*
+         * If the destination class is null, we've travelled all the way up to
+         * an Object match. We'll penalize this by adding 1.5 to the cost.
+         */
+        if (destClass == null) {
+            cost += 1.5f;
+        }
+        return cost;
+    }
+
+    /**
+     * Get the number of steps required to promote a primitive number to another type.
+     * @param srcClass the (primitive) source class
+     * @param destClass the (primitive) destination class
+     * @return The cost of promoting the primitive
+     */
+    private static float getPrimitivePromotionCost(final Class srcClass,
+            final Class destClass) {
+        float cost = 0.0f;
+        Class cls = srcClass;
+        if (!cls.isPrimitive()) {
+            // slight unwrapping penalty
+            cost += 0.1f;
+            cls = ClassUtils.wrapperToPrimitive(cls);
+        }
+        for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) {
+            if (cls == ORDERED_PRIMITIVE_TYPES[i]) {
+                cost += 0.1f;
+                if (i < ORDERED_PRIMITIVE_TYPES.length - 1) {
+                    cls = ORDERED_PRIMITIVE_TYPES[i + 1];
+                }
+            }
+        }
+        return cost;
+    }
+
+}

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MemberUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MemberUtils.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Mar 18 21:31:46 2008
@@ -0,0 +1,5 @@
+Date
+Author
+Id
+Revision
+HeadURL

Added: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MethodUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MethodUtils.java?rev=638703&view=auto
==============================================================================
--- commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MethodUtils.java (added)
+++ commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MethodUtils.java Tue Mar 18 21:31:46 2008
@@ -0,0 +1,842 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.util.Arrays;
+import java.util.WeakHashMap;
+
+/**
+ * <p> Utility reflection methods focused on methods, originally from Commons BeanUtils.
+ * Differences from the BeanUtils version may be noted, especially where similar functionality
+ * already existed within Lang.
+ * </p>
+ *
+ * <h3>Known Limitations</h3>
+ * <h4>Accessing Public Methods In A Default Access Superclass</h4>
+ * <p>There is an issue when invoking public methods contained in a default access superclass on JREs prior to 1.4.
+ * Reflection locates these methods fine and correctly assigns them as public.
+ * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
+ *
+ * <p><code>MethodUtils</code> contains a workaround for this situation. 
+ * It will attempt to call <code>setAccessible</code> on this method.
+ * If this call succeeds, then the method can be invoked as normal.
+ * This call will only succeed when the application has sufficient security privileges. 
+ * If this call fails then the method may fail.</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Ralph Schaer
+ * @author Chris Audley
+ * @author Rey Fran&#231;ois
+ * @author Gregor Ra&#253;man
+ * @author Jan Sorensen
+ * @author Robert Burrell Donkin
+ * @author Niall Pemberton
+ * @author Matt Benson
+ */
+public class MethodUtils {
+
+    /**
+     * Stores a cache of MethodDescriptor -> Method in a WeakHashMap.
+     * <p>
+     * The keys into this map only ever exist as temporary variables within
+     * methods of this class, and are never exposed to users of this class.
+     * This means that the WeakHashMap is used only as a mechanism for 
+     * limiting the size of the cache, ie a way to tell the garbage collector
+     * that the contents of the cache can be completely garbage-collected 
+     * whenever it needs the memory. Whether this is a good approach to
+     * this problem is doubtful; something like the commons-collections
+     * LRUMap may be more appropriate (though of course selecting an
+     * appropriate size is an issue).
+     * <p>
+     * This static variable is safe even when this code is deployed via a
+     * shared classloader because it is keyed via a MethodDescriptor object
+     * which has a Class as one of its members and that member is used in
+     * the MethodDescriptor.equals method. So two components that load the same
+     * class via different classloaders will generate non-equal MethodDescriptor
+     * objects and hence end up with different entries in the map.
+     */
+    private static final WeakHashMap/* <MethodDescriptor, Method> */cache = new WeakHashMap();
+
+    /**
+     * Indicates whether methods should be cached for improved performance.
+     * <p>
+     * Note that when this class is deployed via a shared classloader in
+     * a container, this will affect all webapps. However making this
+     * configurable per webapp would mean having a map keyed by context classloader
+     * which may introduce memory-leak problems.
+     */
+    private static boolean cacheMethods = true;
+
+    /**
+     * <p>MethodUtils instances should NOT be constructed in standard programming.
+     * Instead, the class should be used as
+     * <code>MethodUtils.getAccessibleMethod(method)</code>.</p>
+     *
+     * <p>This constructor is public to permit tools that require a JavaBean
+     * instance to operate.</p>
+     */
+    public MethodUtils() {
+        super();
+    }
+
+    /**
+     * Set whether methods should be cached for greater performance or not,
+     * default is <code>true</code>.
+     *
+     * @param cacheMethods <code>true</code> if methods should be
+     * cached for greater performance, otherwise <code>false</code>
+     */
+    public static synchronized void setCacheMethods(boolean cacheMethods) {
+        MethodUtils.cacheMethods = cacheMethods;
+        if (!MethodUtils.cacheMethods) {
+            clearCache();
+        }
+    }
+
+    /**
+     * Clear the method cache.
+     * @return the number of cached methods cleared
+     */
+    public static synchronized int clearCache() {
+        int size = cache.size();
+        cache.clear();
+        return size;
+    }
+
+    /**
+     * <p>Invoke a named method whose parameter type matches the object type.</p>
+     *
+     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters 
+     * via passing in wrapping classes. So, for example, a <code>Boolean</code> object
+     * would match a <code>boolean</code> primitive.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeMethod(Object object, String methodName, Object[] args)}.
+     * </p>
+     *
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param arg use this argument
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the method invoked
+     * @throws IllegalAccessException if the requested method is not accessible via reflection
+     */
+    public static Object invokeMethod(Object object, String methodName,
+            Object arg) throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        return invokeMethod(object, methodName, new Object[] { arg });
+    }
+
+    /**
+     * <p>Invoke a named method whose parameter type matches the object type.</p>
+     *
+     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters 
+     * via passing in wrapping classes. So, for example, a <code>Boolean</code> object
+     * would match a <code>boolean</code> primitive.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeMethod(Object object,String methodName, Object[] args, Class[] parameterTypes)}.
+     * </p>
+     *
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the method invoked
+     * @throws IllegalAccessException if the requested method is not accessible via reflection
+     */
+    public static Object invokeMethod(Object object, String methodName,
+            Object[] args) throws NoSuchMethodException,
+            IllegalAccessException, InvocationTargetException {
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        int arguments = args.length;
+        Class[] parameterTypes = new Class[arguments];
+        for (int i = 0; i < arguments; i++) {
+            parameterTypes[i] = args[i].getClass();
+        }
+        return invokeMethod(object, methodName, args, parameterTypes);
+    }
+
+    /**
+     * <p>Invoke a named method whose parameter type matches the object type.</p>
+     *
+     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters 
+     * via passing in wrapping classes. So, for example, a <code>Boolean</code> object
+     * would match a <code>boolean</code> primitive.</p>
+     *
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array
+     * @param parameterTypes match these parameters - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the method invoked
+     * @throws IllegalAccessException if the requested method is not accessible via reflection
+     */
+    public static Object invokeMethod(Object object, String methodName,
+            Object[] args, Class[] parameterTypes)
+            throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        if (parameterTypes == null) {
+            parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+        }
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        Method method = getMatchingAccessibleMethod(object.getClass(),
+                methodName, parameterTypes);
+        if (method == null) {
+            throw new NoSuchMethodException("No such accessible method: "
+                    + methodName + "() on object: "
+                    + object.getClass().getName());
+        }
+        return method.invoke(object, args);
+    }
+
+    /**
+     * <p>Invoke a method whose parameter type matches exactly the object
+     * type.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+     * </p>
+     *
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param arg use this argument
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeExactMethod(Object object, String methodName,
+            Object arg) throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        return invokeExactMethod(object, methodName, new Object[] { arg });
+    }
+
+    /**
+     * <p>Invoke a method whose parameter types match exactly the object
+     * types.</p>
+     *
+     * <p> This uses reflection to invoke the method obtained from a call to
+     * <code>getAccessibleMethod()</code>.</p>
+     *
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeExactMethod(Object object, String methodName,
+            Object[] args) throws NoSuchMethodException,
+            IllegalAccessException, InvocationTargetException {
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        int arguments = args.length;
+        Class[] parameterTypes = new Class[arguments];
+        for (int i = 0; i < arguments; i++) {
+            parameterTypes[i] = args[i].getClass();
+        }
+        return invokeExactMethod(object, methodName, args, parameterTypes);
+    }
+
+    /**
+     * <p>Invoke a method whose parameter types match exactly the parameter
+     * types given.</p>
+     *
+     * <p>This uses reflection to invoke the method obtained from a call to
+     * <code>getAccessibleMethod()</code>.</p>
+     *
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array
+     * @param parameterTypes match these parameters - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeExactMethod(Object object, String methodName,
+            Object[] args, Class[] parameterTypes)
+            throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        if (parameterTypes == null) {
+            parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+        }
+        Method method = getAccessibleMethod(object.getClass(), methodName,
+                parameterTypes);
+        if (method == null) {
+            throw new NoSuchMethodException("No such accessible method: "
+                    + methodName + "() on object: "
+                    + object.getClass().getName());
+        }
+        return method.invoke(object, args);
+    }
+
+    /**
+     * <p>Invoke a static method whose parameter types match exactly the parameter
+     * types given.</p>
+     *
+     * <p>This uses reflection to invoke the method obtained from a call to
+     * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+     *
+     * @param cls invoke static method on this class
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array
+     * @param parameterTypes match these parameters - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeExactStaticMethod(Class cls, String methodName,
+            Object[] args, Class[] parameterTypes)
+            throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        if (parameterTypes == null) {
+            parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+        }
+        Method method = getAccessibleMethod(cls, methodName, parameterTypes);
+        if (method == null) {
+            throw new NoSuchMethodException("No such accessible method: "
+                    + methodName + "() on class: " + cls.getName());
+        }
+        return method.invoke(null, args);
+    }
+
+    /**
+     * <p>Invoke a named static method whose parameter type matches the object type.</p>
+     *
+     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters 
+     * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+     * would match a <code>boolean</code> primitive.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
+     * </p>
+     *
+     * @param cls invoke static method on this class
+     * @param methodName get method with this name
+     * @param arg use this argument
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeStaticMethod(Class cls, String methodName,
+            Object arg) throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        return invokeStaticMethod(cls, methodName, new Object[] { arg });
+    }
+
+    /**
+     * <p>Invoke a named static method whose parameter type matches the object type.</p>
+     *
+     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters 
+     * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+     * would match a <code>boolean</code> primitive.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
+     * </p>
+     *
+     * @param cls invoke static method on this class
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeStaticMethod(Class cls, String methodName,
+            Object[] args) throws NoSuchMethodException,
+            IllegalAccessException, InvocationTargetException {
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        int arguments = args.length;
+        Class[] parameterTypes = new Class[arguments];
+        for (int i = 0; i < arguments; i++) {
+            parameterTypes[i] = args[i].getClass();
+        }
+        return invokeStaticMethod(cls, methodName, args, parameterTypes);
+    }
+
+    /**
+     * <p>Invoke a named static method whose parameter type matches the object type.</p>
+     *
+     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters 
+     * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+     * would match a <code>boolean</code> primitive.</p>
+     *
+     *
+     * @param cls invoke static method on this class
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array
+     * @param parameterTypes match these parameters - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeStaticMethod(Class cls, String methodName,
+            Object[] args, Class[] parameterTypes)
+            throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        if (parameterTypes == null) {
+            parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+        }
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        Method method = getMatchingAccessibleMethod(cls, methodName,
+                parameterTypes);
+        if (method == null) {
+            throw new NoSuchMethodException("No such accessible method: "
+                    + methodName + "() on class: " + cls.getName());
+        }
+        return method.invoke(null, args);
+    }
+
+    /**
+     * <p>Invoke a static method whose parameter type matches exactly the object
+     * type.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}.
+     * </p>
+     *
+     * @param cls invoke static method on this class
+     * @param methodName get method with this name
+     * @param arg use this argument
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeExactStaticMethod(Class cls, String methodName,
+            Object arg) throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        return invokeExactStaticMethod(cls, methodName, new Object[] { arg });
+    }
+
+    /**
+     * <p>Invoke a static method whose parameter types match exactly the object
+     * types.</p>
+     *
+     * <p> This uses reflection to invoke the method obtained from a call to
+     * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+     *
+     * @param cls invoke static method on this class
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     */
+    public static Object invokeExactStaticMethod(Class cls, String methodName,
+            Object[] args) throws NoSuchMethodException,
+            IllegalAccessException, InvocationTargetException {
+        if (args == null) {
+            args = ArrayUtils.EMPTY_OBJECT_ARRAY;
+        }
+        int arguments = args.length;
+        Class[] parameterTypes = new Class[arguments];
+        for (int i = 0; i < arguments; i++) {
+            parameterTypes[i] = args[i].getClass();
+        }
+        return invokeExactStaticMethod(cls, methodName, args, parameterTypes);
+    }
+
+    /**
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) with given name and a single parameter.  If no such method
+     * can be found, return <code>null</code>.
+     * Basically, a convenience wrapper that constructs a <code>Class</code>
+     * array for you.</p>
+     *
+     * @param cls get method from this class
+     * @param methodName get method with this name
+     * @param parameterType taking this type of parameter
+     * @return The accessible method
+     */
+    public static Method getAccessibleMethod(Class cls, String methodName,
+            Class parameterType) {
+        return getAccessibleMethod(cls, methodName,
+                new Class[] { parameterType });
+    }
+
+    /**
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) with given name and parameters.  If no such method
+     * can be found, return <code>null</code>.
+     * This is just a convenient wrapper for
+     * {@link #getAccessibleMethod(Method method)}.</p>
+     *
+     * @param cls get method from this class
+     * @param methodName get method with this name
+     * @param parameterTypes with these parameters types
+     * @return The accessible method
+     */
+    public static Method getAccessibleMethod(Class cls, String methodName,
+            Class[] parameterTypes) {
+        try {
+            MethodDescriptor md = new MethodDescriptor(cls, methodName,
+                    parameterTypes, true);
+            // Check the cache first
+            Method method = getCachedMethod(md);
+            if (method != null) {
+                return method;
+            }
+            method = getAccessibleMethod(cls.getMethod(methodName,
+                    parameterTypes));
+            cacheMethod(md, method);
+            return method;
+        } catch (NoSuchMethodException e) {
+            return (null);
+        }
+    }
+
+    /**
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) that implements the specified Method.  If no such method
+     * can be found, return <code>null</code>.</p>
+     *
+     * @param method The method that we wish to call
+     * @return The accessible method
+     */
+    public static Method getAccessibleMethod(Method method) {
+        if (!MemberUtils.isAccessible(method)) {
+            return null;
+        }
+        // If the declaring class is public, we are done
+        Class cls = method.getDeclaringClass();
+        if (Modifier.isPublic(cls.getModifiers())) {
+            return method;
+        }
+        String methodName = method.getName();
+        Class[] parameterTypes = method.getParameterTypes();
+
+        // Check the implemented interfaces and subinterfaces
+        method = getAccessibleMethodFromInterfaceNest(cls, methodName,
+                parameterTypes);
+
+        // Check the superclass chain
+        if (method == null) {
+            method = getAccessibleMethodFromSuperclass(cls, methodName,
+                    parameterTypes);
+        }
+        return method;
+    }
+
+    /**
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) by scanning through the superclasses. If no such method
+     * can be found, return <code>null</code>.</p>
+     *
+     * @param cls Class to be checked
+     * @param methodName Method name of the method we wish to call
+     * @param parameterTypes The parameter type signatures
+     */
+    private static Method getAccessibleMethodFromSuperclass(Class cls,
+            String methodName, Class[] parameterTypes) {
+        Class parentClass = cls.getSuperclass();
+        while (parentClass != null) {
+            if (Modifier.isPublic(parentClass.getModifiers())) {
+                try {
+                    return parentClass.getMethod(methodName, parameterTypes);
+                } catch (NoSuchMethodException e) {
+                    return null;
+                }
+            }
+            parentClass = parentClass.getSuperclass();
+        }
+        return null;
+    }
+
+    /**
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) that implements the specified method, by scanning through
+     * all implemented interfaces and subinterfaces.  If no such method
+     * can be found, return <code>null</code>.</p>
+     *
+     * <p> There isn't any good reason why this method must be private.
+     * It is because there doesn't seem any reason why other classes should
+     * call this rather than the higher level methods.</p>
+     *
+     * @param cls Parent class for the interfaces to be checked
+     * @param methodName Method name of the method we wish to call
+     * @param parameterTypes The parameter type signatures
+     */
+    private static Method getAccessibleMethodFromInterfaceNest(Class cls,
+            String methodName, Class[] parameterTypes) {
+        Method method = null;
+
+        // Search up the superclass chain
+        for (; cls != null; cls = cls.getSuperclass()) {
+
+            // Check the implemented interfaces of the parent class
+            Class[] interfaces = cls.getInterfaces();
+            for (int i = 0; i < interfaces.length; i++) {
+                // Is this interface public?
+                if (!Modifier.isPublic(interfaces[i].getModifiers())) {
+                    continue;
+                }
+                // Does the method exist on this interface?
+                try {
+                    method = interfaces[i].getDeclaredMethod(methodName,
+                            parameterTypes);
+                } catch (NoSuchMethodException e) {
+                    /*
+                     * Swallow, if no method is found after the loop then this
+                     * method returns null.
+                     */
+                }
+                if (method != null) {
+                    break;
+                }
+                // Recursively check our parent interfaces
+                method = getAccessibleMethodFromInterfaceNest(interfaces[i],
+                        methodName, parameterTypes);
+                if (method != null) {
+                    break;
+                }
+            }
+        }
+        return method;
+    }
+
+    /**
+     * <p>Find an accessible method that matches the given name and has compatible parameters.
+     * Compatible parameters mean that every method parameter is assignable from 
+     * the given parameters.
+     * In other words, it finds a method with the given name 
+     * that will take the parameters given.<p>
+     *
+     * <p>This method is used by 
+     * {@link 
+     * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+     *
+     * <p>This method can match primitive parameter by passing in wrapper classes.
+     * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
+     * parameter.
+     *
+     * @param cls find method in this class
+     * @param methodName find method with this name
+     * @param parameterTypes find method with most compatible parameters 
+     * @return The accessible method
+     */
+    public static Method getMatchingAccessibleMethod(Class cls,
+            String methodName, Class[] parameterTypes) {
+        MethodDescriptor md = new MethodDescriptor(cls, methodName,
+                parameterTypes, false);
+        // Check the cache first
+        Method method = getCachedMethod(md);
+        if (method != null) {
+            return method;
+        }
+        // see if we can find the method directly
+        // most of the time this works and it's much faster
+        try {
+            method = cls.getMethod(methodName, parameterTypes);
+            MemberUtils.setAccessibleWorkaround(method);
+            cacheMethod(md, method);
+            return method;
+        } catch (NoSuchMethodException e) { /* SWALLOW */
+        }
+        // search through all methods
+        Method bestMatch = null;
+        Method[] methods = cls.getMethods();
+        for (int i = 0, size = methods.length; i < size; i++) {
+            if (methods[i].getName().equals(methodName)) {
+                // compare parameters
+                if (ClassUtils.isAssignable(parameterTypes, methods[i]
+                        .getParameterTypes(), true)) {
+                    // get accessible version of method
+                    Method accessibleMethod = getAccessibleMethod(methods[i]);
+                    if (accessibleMethod != null) {
+                        if (bestMatch == null
+                                || MemberUtils.compareParameterTypes(
+                                        accessibleMethod.getParameterTypes(),
+                                        bestMatch.getParameterTypes(),
+                                        parameterTypes) < 0) {
+                            bestMatch = accessibleMethod;
+                        }
+                    }
+                }
+            }
+        }
+        if (bestMatch != null) {
+            MemberUtils.setAccessibleWorkaround(bestMatch);
+            cacheMethod(md, bestMatch);
+        }
+        return bestMatch;
+    }
+
+    /**
+     * Return the method from the cache, if present.
+     *
+     * @param md The method descriptor
+     * @return The cached method
+     */
+    private static Method getCachedMethod(MethodDescriptor md) {
+        if (cacheMethods) {
+            return (Method) cache.get(md);
+        }
+        return null;
+    }
+
+    /**
+     * Add a method to the cache.
+     *
+     * @param md The method descriptor
+     * @param method The method to cache
+     */
+    private static void cacheMethod(MethodDescriptor md, Method method) {
+        if (cacheMethods) {
+            if (method != null) {
+                cache.put(md, method);
+            }
+        }
+    }
+
+    /**
+     * Represents the key to looking up a Method by reflection.
+     */
+    private static class MethodDescriptor {
+        private Class cls;
+        private String methodName;
+        private Class[] paramTypes;
+        private boolean exact;
+        private int hashCode;
+
+        /**
+         * The sole constructor.
+         *
+         * @param cls  the class to reflect, must not be null
+         * @param methodName  the method name to obtain
+         * @param paramTypes the array of classes representing the paramater types
+         * @param exact whether the match has to be exact.
+         */
+        public MethodDescriptor(Class cls, String methodName,
+                Class[] paramTypes, boolean exact) {
+            if (cls == null) {
+                throw new IllegalArgumentException("Class cannot be null");
+            }
+            if (methodName == null) {
+                throw new IllegalArgumentException("Method Name cannot be null");
+            }
+            if (paramTypes == null) {
+                paramTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
+            }
+            this.cls = cls;
+            this.methodName = methodName;
+            this.paramTypes = paramTypes;
+            this.exact = exact;
+            // is this adequate? :/
+            this.hashCode = methodName.length();
+        }
+
+        /**
+         * Checks for equality.
+         * @param obj object to be tested for equality
+         * @return true, if the object describes the same Method.
+         */
+        public boolean equals(Object obj) {
+            if (!(obj instanceof MethodDescriptor)) {
+                return false;
+            }
+            MethodDescriptor md = (MethodDescriptor) obj;
+
+            return exact == md.exact && methodName.equals(md.methodName)
+                    && cls.equals(md.cls)
+                    && Arrays.equals(paramTypes, md.paramTypes);
+        }
+
+        /**
+         * Returns the string length of method name. I.e. if the
+         * hashcodes are different, the objects are different. If the
+         * hashcodes are the same, need to use the equals method to
+         * determine equality.
+         * @return the string length of method name.
+         */
+        public int hashCode() {
+            return hashCode;
+        }
+    }
+}

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MethodUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/java/org/apache/commons/lang/MethodUtils.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Mar 18 21:31:46 2008
@@ -0,0 +1,5 @@
+Date
+Author
+Id
+Revision
+HeadURL

Added: commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/ConstructorUtilsTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/ConstructorUtilsTest.java?rev=638703&view=auto
==============================================================================
--- commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/ConstructorUtilsTest.java (added)
+++ commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/ConstructorUtilsTest.java Tue Mar 18 21:31:46 2008
@@ -0,0 +1,207 @@
+package org.apache.commons.lang;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.commons.lang.math.NumberUtils;
+
+public class ConstructorUtilsTest extends TestCase {
+    public static class TestBean {
+        private String toString;
+
+        public TestBean() {
+            toString = "()";
+        }
+
+        public TestBean(int i) {
+            toString = "(int)";
+        }
+
+        public TestBean(Integer i) {
+            toString = "(Integer)";
+        }
+
+        public TestBean(double d) {
+            toString = "(double)";
+        }
+
+        public TestBean(String s) {
+            toString = "(String)";
+        }
+
+        public TestBean(Object o) {
+            toString = "(Object)";
+        }
+
+        public String toString() {
+            return toString;
+        }
+    }
+
+    private static class PrivateClass {
+        public PrivateClass() {
+        }
+    }
+
+    private Map classCache;
+
+    public ConstructorUtilsTest(String name) {
+        super(name);
+        classCache = new HashMap();
+    }
+
+    /**
+     * Run the test cases as a suite.
+     * @return the Test
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite(ConstructorUtilsTest.class);
+        suite.setName("ConstructorUtils Tests");
+        return suite;
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        classCache.clear();
+    }
+
+    public void testConstructor() throws Exception {
+        assertNotNull(MethodUtils.class.newInstance());
+    }
+
+    public void testInvokeConstructor() throws Exception {
+        assertEquals("()", ConstructorUtils.invokeConstructor(TestBean.class,
+                ArrayUtils.EMPTY_CLASS_ARRAY).toString());
+        assertEquals("()", ConstructorUtils.invokeConstructor(TestBean.class,
+                (Class[]) null).toString());
+        assertEquals("(String)", ConstructorUtils.invokeConstructor(
+                TestBean.class, "").toString());
+        assertEquals("(Object)", ConstructorUtils.invokeConstructor(
+                TestBean.class, new Object()).toString());
+        assertEquals("(Object)", ConstructorUtils.invokeConstructor(
+                TestBean.class, Boolean.TRUE).toString());
+        assertEquals("(Integer)", ConstructorUtils.invokeConstructor(
+                TestBean.class, NumberUtils.INTEGER_ONE).toString());
+        assertEquals("(int)", ConstructorUtils.invokeConstructor(
+                TestBean.class, NumberUtils.BYTE_ONE).toString());
+        assertEquals("(double)", ConstructorUtils.invokeConstructor(
+                TestBean.class, NumberUtils.LONG_ONE).toString());
+        assertEquals("(double)", ConstructorUtils.invokeConstructor(
+                TestBean.class, NumberUtils.DOUBLE_ONE).toString());
+    }
+
+    public void testInvokeExactConstructor() throws Exception {
+        assertEquals("()", ConstructorUtils.invokeExactConstructor(
+                TestBean.class, ArrayUtils.EMPTY_CLASS_ARRAY).toString());
+        assertEquals("()", ConstructorUtils.invokeExactConstructor(
+                TestBean.class, (Class[]) null).toString());
+        assertEquals("(String)", ConstructorUtils.invokeExactConstructor(
+                TestBean.class, "").toString());
+        assertEquals("(Object)", ConstructorUtils.invokeExactConstructor(
+                TestBean.class, new Object()).toString());
+        assertEquals("(Integer)", ConstructorUtils.invokeExactConstructor(
+                TestBean.class, NumberUtils.INTEGER_ONE).toString());
+        assertEquals("(double)", ConstructorUtils.invokeExactConstructor(
+                TestBean.class, new Object[] { NumberUtils.DOUBLE_ONE },
+                new Class[] { Double.TYPE }).toString());
+
+        try {
+            ConstructorUtils.invokeExactConstructor(TestBean.class,
+                    NumberUtils.BYTE_ONE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+        try {
+            ConstructorUtils.invokeExactConstructor(TestBean.class,
+                    NumberUtils.LONG_ONE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+        try {
+            ConstructorUtils.invokeExactConstructor(TestBean.class,
+                    Boolean.TRUE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+    }
+
+    public void testGetAccessibleConstructor() throws Exception {
+        assertNotNull(ConstructorUtils.getAccessibleConstructor(Object.class
+                .getConstructor(ArrayUtils.EMPTY_CLASS_ARRAY)));
+        assertNull(ConstructorUtils.getAccessibleConstructor(PrivateClass.class
+                .getConstructor(ArrayUtils.EMPTY_CLASS_ARRAY)));
+    }
+
+    public void testGetAccessibleConstructorFromDescription() throws Exception {
+        assertNotNull(ConstructorUtils.getAccessibleConstructor(Object.class,
+                ArrayUtils.EMPTY_CLASS_ARRAY));
+        assertNull(ConstructorUtils.getAccessibleConstructor(
+                PrivateClass.class, ArrayUtils.EMPTY_CLASS_ARRAY));
+    }
+
+    public void testGetMatchingAccessibleMethod() throws Exception {
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                ArrayUtils.EMPTY_CLASS_ARRAY, ArrayUtils.EMPTY_CLASS_ARRAY);
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class, null,
+                ArrayUtils.EMPTY_CLASS_ARRAY);
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(String.class), singletonArray(String.class));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Object.class), singletonArray(Object.class));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Boolean.class), singletonArray(Object.class));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Byte.class), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Byte.TYPE), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Short.class), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Short.TYPE), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Character.class), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Character.TYPE), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Integer.class), singletonArray(Integer.class));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Integer.TYPE), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Long.class), singletonArray(Double.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Long.TYPE), singletonArray(Double.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Float.class), singletonArray(Double.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Float.TYPE), singletonArray(Double.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Double.class), singletonArray(Double.TYPE));
+        expectMatchingAccessibleConstructorParameterTypes(TestBean.class,
+                singletonArray(Double.TYPE), singletonArray(Double.TYPE));
+    }
+
+    private void expectMatchingAccessibleConstructorParameterTypes(Class cls,
+            Class[] requestTypes, Class[] actualTypes) {
+        Constructor c = ConstructorUtils.getMatchingAccessibleConstructor(cls,
+                requestTypes);
+        assertTrue(Arrays.toString(c.getParameterTypes()) + " not equals "
+                + Arrays.toString(actualTypes), Arrays.equals(actualTypes, c
+                .getParameterTypes()));
+    }
+
+    private Class[] singletonArray(Class c) {
+        Class[] result = (Class[]) classCache.get(c);
+        if (result == null) {
+            result = new Class[] { c };
+            classCache.put(c, result);
+        }
+        return result;
+    }
+
+}

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/ConstructorUtilsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/ConstructorUtilsTest.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Mar 18 21:31:46 2008
@@ -0,0 +1,5 @@
+Date
+Author
+Id
+Revision
+HeadURL

Modified: commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/LangTestSuite.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/LangTestSuite.java?rev=638703&r1=638702&r2=638703&view=diff
==============================================================================
--- commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/LangTestSuite.java (original)
+++ commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/LangTestSuite.java Tue Mar 18 21:31:46 2008
@@ -67,6 +67,7 @@
         suite.addTest(IncompleteArgumentExceptionTest.suite());
         suite.addTest(IntHashMapTest.suite());
         suite.addTest(LocaleUtilsTest.suite());
+        suite.addTest(MethodUtilsTest.suite());
         suite.addTest(NotImplementedExceptionTest.suite());
         suite.addTest(NullArgumentExceptionTest.suite());
         suite.addTest(NumberRangeTest.suite());

Added: commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/MethodUtilsTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/MethodUtilsTest.java?rev=638703&view=auto
==============================================================================
--- commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/MethodUtilsTest.java (added)
+++ commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/MethodUtilsTest.java Tue Mar 18 21:31:46 2008
@@ -0,0 +1,320 @@
+package org.apache.commons.lang;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.commons.lang.math.NumberUtils;
+import org.apache.commons.lang.mutable.Mutable;
+import org.apache.commons.lang.mutable.MutableObject;
+
+public class MethodUtilsTest extends TestCase {
+    public static class TestBean {
+
+        public static String bar() {
+            return "bar()";
+        }
+
+        public static String bar(int i) {
+            return "bar(int)";
+        }
+
+        public static String bar(Integer i) {
+            return "bar(Integer)";
+        }
+
+        public static String bar(double d) {
+            return "bar(double)";
+        }
+
+        public static String bar(String s) {
+            return "bar(String)";
+        }
+
+        public static String bar(Object o) {
+            return "bar(Object)";
+        }
+
+        public String foo() {
+            return "foo()";
+        }
+
+        public String foo(int i) {
+            return "foo(int)";
+        }
+
+        public String foo(Integer i) {
+            return "foo(Integer)";
+        }
+
+        public String foo(double d) {
+            return "foo(double)";
+        }
+
+        public String foo(String s) {
+            return "foo(String)";
+        }
+
+        public String foo(Object o) {
+            return "foo(Object)";
+        }
+    }
+
+    private static class TestMutable implements Mutable {
+        public Object getValue() {
+            return null;
+        }
+
+        public void setValue(Object value) {
+        }
+    }
+
+    private TestBean testBean;
+    private Map classCache;
+
+    public MethodUtilsTest(String name) {
+        super(name);
+        classCache = new HashMap();
+    }
+
+    /**
+     * Run the test cases as a suite.
+     * @return the Test
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite(MethodUtilsTest.class);
+        suite.setName("MethodUtils Tests");
+        return suite;
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        testBean = new TestBean();
+        classCache.clear();
+    }
+
+    public void testConstructor() throws Exception {
+        assertNotNull(MethodUtils.class.newInstance());
+    }
+
+    public void testInvokeMethod() throws Exception {
+        assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
+                ArrayUtils.EMPTY_CLASS_ARRAY));
+        assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
+                (Class[]) null));
+        assertEquals("foo(String)", MethodUtils.invokeMethod(testBean, "foo",
+                ""));
+        assertEquals("foo(Object)", MethodUtils.invokeMethod(testBean, "foo",
+                new Object()));
+        assertEquals("foo(Object)", MethodUtils.invokeMethod(testBean, "foo",
+                Boolean.TRUE));
+        assertEquals("foo(Integer)", MethodUtils.invokeMethod(testBean, "foo",
+                NumberUtils.INTEGER_ONE));
+        assertEquals("foo(int)", MethodUtils.invokeMethod(testBean, "foo",
+                NumberUtils.BYTE_ONE));
+        assertEquals("foo(double)", MethodUtils.invokeMethod(testBean, "foo",
+                NumberUtils.LONG_ONE));
+        assertEquals("foo(double)", MethodUtils.invokeMethod(testBean, "foo",
+                NumberUtils.DOUBLE_ONE));
+    }
+
+    public void testInvokeExactMethod() throws Exception {
+        assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
+                ArrayUtils.EMPTY_CLASS_ARRAY));
+        assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
+                (Class[]) null));
+        assertEquals("foo(String)", MethodUtils.invokeExactMethod(testBean,
+                "foo", ""));
+        assertEquals("foo(Object)", MethodUtils.invokeExactMethod(testBean,
+                "foo", new Object()));
+        assertEquals("foo(Integer)", MethodUtils.invokeExactMethod(testBean,
+                "foo", NumberUtils.INTEGER_ONE));
+        assertEquals("foo(double)", MethodUtils.invokeExactMethod(testBean,
+                "foo", new Object[] { NumberUtils.DOUBLE_ONE },
+                new Class[] { Double.TYPE }));
+
+        try {
+            MethodUtils
+                    .invokeExactMethod(testBean, "foo", NumberUtils.BYTE_ONE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+        try {
+            MethodUtils
+                    .invokeExactMethod(testBean, "foo", NumberUtils.LONG_ONE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+        try {
+            MethodUtils.invokeExactMethod(testBean, "foo", Boolean.TRUE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+    }
+
+    public void testInvokeStaticMethod() throws Exception {
+        assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class,
+                "bar", ArrayUtils.EMPTY_CLASS_ARRAY));
+        assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class,
+                "bar", (Class[]) null));
+        assertEquals("bar(String)", MethodUtils.invokeStaticMethod(
+                TestBean.class, "bar", ""));
+        assertEquals("bar(Object)", MethodUtils.invokeStaticMethod(
+                TestBean.class, "bar", new Object()));
+        assertEquals("bar(Object)", MethodUtils.invokeStaticMethod(
+                TestBean.class, "bar", Boolean.TRUE));
+        assertEquals("bar(Integer)", MethodUtils.invokeStaticMethod(
+                TestBean.class, "bar", NumberUtils.INTEGER_ONE));
+        assertEquals("bar(int)", MethodUtils.invokeStaticMethod(TestBean.class,
+                "bar", NumberUtils.BYTE_ONE));
+        assertEquals("bar(double)", MethodUtils.invokeStaticMethod(
+                TestBean.class, "bar", NumberUtils.LONG_ONE));
+        assertEquals("bar(double)", MethodUtils.invokeStaticMethod(
+                TestBean.class, "bar", NumberUtils.DOUBLE_ONE));
+    }
+
+    public void testInvokeExactStaticMethod() throws Exception {
+        assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class,
+                "bar", ArrayUtils.EMPTY_CLASS_ARRAY));
+        assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class,
+                "bar", (Class[]) null));
+        assertEquals("bar(String)", MethodUtils.invokeExactStaticMethod(
+                TestBean.class, "bar", ""));
+        assertEquals("bar(Object)", MethodUtils.invokeExactStaticMethod(
+                TestBean.class, "bar", new Object()));
+        assertEquals("bar(Integer)", MethodUtils.invokeExactStaticMethod(
+                TestBean.class, "bar", NumberUtils.INTEGER_ONE));
+        assertEquals("bar(double)", MethodUtils.invokeExactStaticMethod(
+                TestBean.class, "bar", new Object[] { NumberUtils.DOUBLE_ONE },
+                new Class[] { Double.TYPE }));
+
+        try {
+            MethodUtils.invokeExactStaticMethod(TestBean.class, "bar",
+                    NumberUtils.BYTE_ONE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+        try {
+            MethodUtils.invokeExactStaticMethod(TestBean.class, "bar",
+                    NumberUtils.LONG_ONE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+        try {
+            MethodUtils.invokeExactStaticMethod(TestBean.class, "bar",
+                    Boolean.TRUE);
+            fail("should throw NoSuchMethodException");
+        } catch (NoSuchMethodException e) {
+        }
+    }
+
+    public void testGetAccessibleInterfaceMethod() throws Exception {
+
+        Class[][] p = { ArrayUtils.EMPTY_CLASS_ARRAY, null };
+        for (int i = 0; i < p.length; i++) {
+            Method method = TestMutable.class.getMethod("getValue", p[i]);
+            Method accessibleMethod = MethodUtils.getAccessibleMethod(method);
+            assertNotSame(accessibleMethod, method);
+            assertSame(Mutable.class, accessibleMethod.getDeclaringClass());
+        }
+    }
+
+    public void testGetAccessibleInterfaceMethodFromDescription()
+            throws Exception {
+        Class[][] p = { ArrayUtils.EMPTY_CLASS_ARRAY, null };
+        for (int i = 0; i < p.length; i++) {
+            Method accessibleMethod = MethodUtils.getAccessibleMethod(
+                    TestMutable.class, "getValue", p[i]);
+            assertSame(Mutable.class, accessibleMethod.getDeclaringClass());
+        }
+    }
+
+    public void testGetAccessiblePublicMethod() throws Exception {
+        assertSame(MutableObject.class, MethodUtils.getAccessibleMethod(
+                MutableObject.class.getMethod("getValue",
+                        ArrayUtils.EMPTY_CLASS_ARRAY)).getDeclaringClass());
+    }
+
+    public void testGetAccessiblePublicMethodFromDescription() throws Exception {
+        assertSame(MutableObject.class, MethodUtils.getAccessibleMethod(
+                MutableObject.class, "getValue", ArrayUtils.EMPTY_CLASS_ARRAY)
+                .getDeclaringClass());
+    }
+
+    public void testGetMatchingAccessibleMethod() throws Exception {
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                ArrayUtils.EMPTY_CLASS_ARRAY, ArrayUtils.EMPTY_CLASS_ARRAY);
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                null, ArrayUtils.EMPTY_CLASS_ARRAY);
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(String.class), singletonArray(String.class));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Object.class), singletonArray(Object.class));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Boolean.class), singletonArray(Object.class));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Byte.class), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Byte.TYPE), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Short.class), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Short.TYPE), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Character.class), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Character.TYPE), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Integer.class), singletonArray(Integer.class));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Integer.TYPE), singletonArray(Integer.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Long.class), singletonArray(Double.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Long.TYPE), singletonArray(Double.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Float.class), singletonArray(Double.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Float.TYPE), singletonArray(Double.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Double.class), singletonArray(Double.TYPE));
+        expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+                singletonArray(Double.TYPE), singletonArray(Double.TYPE));
+    }
+
+    private void expectMatchingAccessibleMethodParameterTypes(Class cls,
+            String methodName, Class[] requestTypes, Class[] actualTypes) {
+        Method m = MethodUtils.getMatchingAccessibleMethod(cls, methodName,
+                requestTypes);
+        assertTrue(Arrays.toString(m.getParameterTypes()) + " not equals "
+                + Arrays.toString(actualTypes), Arrays.equals(actualTypes, m
+                .getParameterTypes()));
+    }
+
+    public void testSetCacheMethods() throws Exception {
+        MethodUtils.clearCache();
+        MethodUtils.setCacheMethods(true);
+        MethodUtils.invokeMethod(testBean, "foo", "");
+        assertEquals(1, MethodUtils.clearCache());
+        assertEquals(0, MethodUtils.clearCache());
+        MethodUtils.setCacheMethods(false);
+        MethodUtils.invokeMethod(testBean, "foo", "");
+        assertEquals(0, MethodUtils.clearCache());
+        MethodUtils.setCacheMethods(true);
+    }
+
+    private Class[] singletonArray(Class c) {
+        Class[] result = (Class[]) classCache.get(c);
+        if (result == null) {
+            result = new Class[] { c };
+            classCache.put(c, result);
+        }
+        return result;
+    }
+
+}

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/MethodUtilsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/lang/branches/LANG_POST_2_4/src/test/org/apache/commons/lang/MethodUtilsTest.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Mar 18 21:31:46 2008
@@ -0,0 +1,5 @@
+Date
+Author
+Id
+Revision
+HeadURL



Mime
View raw message