diff --git a/src/java/org/apache/commons/lang/ClassUtils.java b/src/java/org/apache/commons/lang/ClassUtils.java
index 4d830fe46..a01f04389 100644
--- a/src/java/org/apache/commons/lang/ClassUtils.java
+++ b/src/java/org/apache/commons/lang/ClassUtils.java
@@ -15,6 +15,9 @@
*/
package org.apache.commons.lang;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -589,6 +592,53 @@ public static Class getClass(String className, boolean initialize) throws ClassN
return getClass(loader, className, initialize );
}
+
+ /**
+ *
Returns the desired Method much like Class.getMethod
, however
+ * it ensures that the returned Method is from a public class or interface and not
+ * from an anonymous inner class. This means that the Method is invokable and
+ * doesn't fall foul of Java bug
+ * 4071957).
+ *
+ * Set set = Collections.unmodifiableSet(...);
+ * Method method = ClassUtils.getPublicMethod(set.getClass(), "isEmpty", new Class[0]);
+ * Object result = method.invoke(set, new Object[]);
+ *
+ */
+ public static Method getPublicMethod(Class cls, String methodName, Class parameterTypes[])
+ throws SecurityException, NoSuchMethodException
+ {
+
+ Method declaredMethod = cls.getMethod(methodName, parameterTypes);
+
+ if (Modifier.isPublic(declaredMethod.getDeclaringClass().getModifiers())) {
+ return declaredMethod;
+ }
+
+ List candidateClasses = new ArrayList();
+ candidateClasses.addAll(getAllInterfaces(cls));
+ candidateClasses.addAll(getAllSuperclasses(cls));
+
+ for (Iterator iter=candidateClasses.iterator(); iter.hasNext(); ) {
+ Class candidateClass = (Class) iter.next();
+ if (!Modifier.isPublic(candidateClass.getModifiers())) {
+ continue;
+ }
+ Method candidateMethod;
+ try {
+ candidateMethod = candidateClass.getMethod(methodName, parameterTypes);
+ } catch (NoSuchMethodException e) {
+ continue;
+ }
+ if (Modifier.isPublic(candidateMethod.getDeclaringClass().getModifiers())) {
+ return candidateMethod;
+ }
+ }
+
+ String message = "Can't find an public method for " + methodName + " " + ArrayUtils.toString(parameterTypes);
+ throw new NoSuchMethodException(message);
+ }
+
/**
* Converts a class name to a JLS stle class name.
*
diff --git a/src/test/org/apache/commons/lang/ClassUtilsTest.java b/src/test/org/apache/commons/lang/ClassUtilsTest.java
index c8d2f22d6..26193a5bf 100644
--- a/src/test/org/apache/commons/lang/ClassUtilsTest.java
+++ b/src/test/org/apache/commons/lang/ClassUtilsTest.java
@@ -16,12 +16,16 @@
package org.apache.commons.lang;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
import junit.framework.Test;
import junit.framework.TestCase;
@@ -520,4 +524,35 @@ public static ClassLoader newSystemClassLoader() throws SecurityException, Illeg
return URLClassLoader.newInstance(urlScl.getURLs(), null);
}
+ // Show the Java bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4071957
+ // We may have to delete this if a JDK fixes the bug.
+ public void testShowJavaBug() throws Exception {
+ // Tests with Collections$UnmodifiableSet
+ Set set = Collections.unmodifiableSet(new HashSet());
+ Method isEmptyMethod = set.getClass().getMethod("isEmpty", new Class[0]);
+ try {
+ isEmptyMethod.invoke(set, new Object[0]);
+ fail("Failed to throw IllegalAccessException as expected");
+ } catch(IllegalAccessException iae) {
+ // expected
+ }
+ }
+
+ public void testGetPublicMethod() throws Exception {
+ // Tests with Collections$UnmodifiableSet
+ Set set = Collections.unmodifiableSet(new HashSet());
+ Method isEmptyMethod = ClassUtils.getPublicMethod(set.getClass(), "isEmpty", new Class[0]);
+ assertTrue(Modifier.isPublic(isEmptyMethod.getDeclaringClass().getModifiers()));
+
+ try {
+ isEmptyMethod.invoke(set, new Object[0]);
+ } catch(java.lang.IllegalAccessException iae) {
+ fail("Should not have thrown IllegalAccessException");
+ }
+
+ // Tests with a public Class
+ Method toStringMethod = ClassUtils.getPublicMethod(Object.class, "toString", new Class[0]);
+ assertEquals(Object.class.getMethod("toString", new Class[0]), toStringMethod);
+ }
+
}