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); + } + }