diff options
Diffstat (limited to 'src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java')
-rw-r--r-- | src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java | 1092 |
1 files changed, 1092 insertions, 0 deletions
diff --git a/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java new file mode 100644 index 000000000..21535a5c5 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java @@ -0,0 +1,1092 @@ +/* + * 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.lang3.reflect; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.hamcrest.Matchers.hasItems; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.awt.Color; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.AbstractLangTest; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.ClassUtils.Interfaces; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.lang3.mutable.Mutable; +import org.apache.commons.lang3.mutable.MutableObject; +import org.apache.commons.lang3.reflect.testbed.Annotated; +import org.apache.commons.lang3.reflect.testbed.GenericConsumer; +import org.apache.commons.lang3.reflect.testbed.GenericParent; +import org.apache.commons.lang3.reflect.testbed.PublicChild; +import org.apache.commons.lang3.reflect.testbed.StringParameterizedChild; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests MethodUtils + */ +public class MethodUtilsTest extends AbstractLangTest { + + private interface PrivateInterface { + } + + static class TestBeanWithInterfaces implements PrivateInterface { + public String foo() { + return "foo()"; + } + } + + public static class TestBean { + + public static String bar() { + return "bar()"; + } + + public static String bar(final int i) { + return "bar(int)"; + } + + public static String bar(final Integer i) { + return "bar(Integer)"; + } + + public static String bar(final double d) { + return "bar(double)"; + } + + public static String bar(final String s) { + return "bar(String)"; + } + + public static String bar(final Object o) { + return "bar(Object)"; + } + + public static String bar(final String... s) { + return "bar(String...)"; + } + + public static String bar(final long... s) { + return "bar(long...)"; + } + + public static String bar(final Integer i, final String... s) { + return "bar(int, String...)"; + } + + public static void oneParameterStatic(final String s) { + // empty + } + + @SuppressWarnings("unused") + private void privateStuff() { + } + + @SuppressWarnings("unused") + private String privateStringStuff() { + return "privateStringStuff()"; + } + + @SuppressWarnings("unused") + private String privateStringStuff(final int i) { + return "privateStringStuff(int)"; + } + + @SuppressWarnings("unused") + private String privateStringStuff(final Integer i) { + return "privateStringStuff(Integer)"; + } + + @SuppressWarnings("unused") + private String privateStringStuff(final double d) { + return "privateStringStuff(double)"; + } + + @SuppressWarnings("unused") + private String privateStringStuff(final String s) { + return "privateStringStuff(String)"; + } + + @SuppressWarnings("unused") + private String privateStringStuff(final Object s) { + return "privateStringStuff(Object)"; + } + + public String foo() { + return "foo()"; + } + + public String foo(final int i) { + return "foo(int)"; + } + + public String foo(final Integer i) { + return "foo(Integer)"; + } + + public String foo(final double d) { + return "foo(double)"; + } + + public String foo(final long l) { + return "foo(long)"; + } + + public String foo(final String s) { + return "foo(String)"; + } + + public String foo(final Object o) { + return "foo(Object)"; + } + + public String foo(final String... s) { + return "foo(String...)"; + } + + public String foo(final long... l) { + return "foo(long...)"; + } + + public String foo(final Integer i, final String... s) { + return "foo(int, String...)"; + } + + public void oneParameter(final String s) { + // empty + } + + public String foo(final Object... s) { + return "foo(Object...)"; + } + + public int[] unboxing(final int... values) { + return values; + } + + // This method is overloaded for the wrapper class for every primitive type, plus the common supertypes + // Number and Object. This is an acid test since it easily leads to ambiguous methods. + public static String varOverload(final Byte... args) { + return "Byte..."; + } + + public static String varOverload(final Character... args) { + return "Character..."; + } + + public static String varOverload(final Short... args) { + return "Short..."; + } + + public static String varOverload(final Boolean... args) { + return "Boolean..."; + } + + public static String varOverload(final Float... args) { + return "Float..."; + } + + public static String varOverload(final Double... args) { + return "Double..."; + } + + public static String varOverload(final Integer... args) { + return "Integer..."; + } + + public static String varOverload(final Long... args) { + return "Long..."; + } + + public static String varOverload(final Number... args) { + return "Number..."; + } + + public static String varOverload(final Object... args) { + return "Object..."; + } + + public static String varOverload(final String... args) { + return "String..."; + } + + // This method is overloaded for the wrapper class for every numeric primitive type, plus the common + // supertype Number + public static String numOverload(final Byte... args) { + return "Byte..."; + } + + public static String numOverload(final Short... args) { + return "Short..."; + } + + public static String numOverload(final Float... args) { + return "Float..."; + } + + public static String numOverload(final Double... args) { + return "Double..."; + } + + public static String numOverload(final Integer... args) { + return "Integer..."; + } + + public static String numOverload(final Long... args) { + return "Long..."; + } + + public static String numOverload(final Number... args) { + return "Number..."; + } + + // These varOverloadEcho and varOverloadEchoStatic methods are designed to verify that + // not only is the correct overloaded variant invoked, but that the varags arguments + // are also delivered correctly to the method. + public ImmutablePair<String, Object[]> varOverloadEcho(final String... args) { + return new ImmutablePair<>("String...", args); + } + + public ImmutablePair<String, Object[]> varOverloadEcho(final Number... args) { + return new ImmutablePair<>("Number...", args); + } + + public static ImmutablePair<String, Object[]> varOverloadEchoStatic(final String... args) { + return new ImmutablePair<>("String...", args); + } + + public static ImmutablePair<String, Object[]> varOverloadEchoStatic(final Number... args) { + return new ImmutablePair<>("Number...", args); + } + + static void verify(final ImmutablePair<String, Object[]> a, final ImmutablePair<String, Object[]> b) { + assertEquals(a.getLeft(), b.getLeft()); + assertArrayEquals(a.getRight(), b.getRight()); + } + + static void verify(final ImmutablePair<String, Object[]> a, final Object obj) { + @SuppressWarnings("unchecked") + final ImmutablePair<String, Object[]> pair = (ImmutablePair<String, Object[]>) obj; + verify(a, pair); + } + + } + + private static class TestMutable implements Mutable<Object> { + @Override + public Object getValue() { + return null; + } + + @Override + public void setValue(final Object value) { + } + } + + private TestBean testBean; + private final Map<Class<?>, Class<?>[]> classCache = new HashMap<>(); + + @BeforeEach + public void setUp() { + testBean = new TestBean(); + classCache.clear(); + } + + @Test + public void testConstructor() throws Exception { + assertNotNull(MethodUtils.class.newInstance()); + } + + @Test + public void verifyJavaVarargsOverloadingResolution() { + // This code is not a test of MethodUtils. + // Rather it makes explicit the behavior of the Java specification for + // various cases of overload resolution. + assertEquals("Byte...", TestBean.varOverload((byte) 1, (byte) 2)); + assertEquals("Short...", TestBean.varOverload((short) 1, (short) 2)); + assertEquals("Integer...", TestBean.varOverload(1, 2)); + assertEquals("Long...", TestBean.varOverload(1L, 2L)); + assertEquals("Float...", TestBean.varOverload(1f, 2f)); + assertEquals("Double...", TestBean.varOverload(1d, 2d)); + assertEquals("Character...", TestBean.varOverload('a', 'b')); + assertEquals("String...", TestBean.varOverload("a", "b")); + assertEquals("Boolean...", TestBean.varOverload(true, false)); + + assertEquals("Object...", TestBean.varOverload(1, "s")); + assertEquals("Object...", TestBean.varOverload(1, true)); + assertEquals("Object...", TestBean.varOverload(1.1, true)); + assertEquals("Object...", TestBean.varOverload('c', true)); + assertEquals("Number...", TestBean.varOverload(1, 1.1)); + assertEquals("Number...", TestBean.varOverload(1, 1L)); + assertEquals("Number...", TestBean.varOverload(1d, 1f)); + assertEquals("Number...", TestBean.varOverload((short) 1, (byte) 1)); + assertEquals("Object...", TestBean.varOverload(1, 'c')); + assertEquals("Object...", TestBean.varOverload('c', "s")); + } + + @Test + public void testInvokeJavaVarargsOverloadingResolution() throws Exception { + assertEquals("Byte...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", (byte) 1, (byte) 2)); + assertEquals("Short...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", (short) 1, (short) 2)); + assertEquals("Integer...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1, 2)); + assertEquals("Long...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1L, 2L)); + assertEquals("Float...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1f, 2f)); + assertEquals("Double...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1d, 2d)); + assertEquals("Character...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 'a', 'b')); + assertEquals("String...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", "a", "b")); + assertEquals("Boolean...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", true, false)); + + assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1, "s")); + assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1, true)); + assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1.1, true)); + assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 'c', true)); + assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1, 1.1)); + assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1, 1L)); + assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1d, 1f)); + assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", (short) 1, (byte) 1)); + assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 1, 'c')); + assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, + "varOverload", 'c', "s")); + + assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, "varOverload", + (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY)); + assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class, "numOverload", + (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY)); + } + + @Test + public void testInvokeMethod() throws Exception { + assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo", + (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY)); + assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo")); + assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo", + (Object[]) null)); + assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo", + null, 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(long)", MethodUtils.invokeMethod(testBean, "foo", + NumberUtils.LONG_ONE)); + assertEquals("foo(double)", MethodUtils.invokeMethod(testBean, "foo", + NumberUtils.DOUBLE_ONE)); + assertEquals("foo(String...)", MethodUtils.invokeMethod(testBean, "foo", + "a", "b", "c")); + assertEquals("foo(String...)", MethodUtils.invokeMethod(testBean, "foo", + "a", "b", "c")); + assertEquals("foo(int, String...)", MethodUtils.invokeMethod(testBean, "foo", + 5, "a", "b", "c")); + assertEquals("foo(long...)", MethodUtils.invokeMethod(testBean, "foo", + 1L, 2L)); + + assertThrows(NoSuchMethodException.class, () -> MethodUtils.invokeMethod(testBean, "foo", 1, 2)); + + TestBean.verify(new ImmutablePair<>("String...", new String[]{"x", "y"}), + MethodUtils.invokeMethod(testBean, "varOverloadEcho", "x", "y")); + TestBean.verify(new ImmutablePair<>("Number...", new Number[]{17, 23, 42}), + MethodUtils.invokeMethod(testBean, "varOverloadEcho", 17, 23, 42)); + TestBean.verify(new ImmutablePair<>("String...", new String[]{"x", "y"}), + MethodUtils.invokeMethod(testBean, "varOverloadEcho", "x", "y")); + TestBean.verify(new ImmutablePair<>("Number...", new Number[]{17, 23, 42}), + MethodUtils.invokeMethod(testBean, "varOverloadEcho", 17, 23, 42)); + } + + @Test + public void testInvokeMethod_VarArgsWithNullValues() throws Exception { + assertEquals("String...", MethodUtils.invokeMethod(testBean, "varOverload", + "a", null, "c")); + assertEquals("String...", MethodUtils.invokeMethod(testBean, "varOverload", + "a", "b", null)); + } + + @Test + public void testInvokeMethod_VarArgsNotUniqueResolvable() throws Exception { + assertEquals("Boolean...", MethodUtils.invokeMethod(testBean, "varOverload", + new Object[] {null})); + assertEquals("Object...", MethodUtils.invokeMethod(testBean, "varOverload", + (Object[]) null)); + } + + @Test + public void testInvokeExactMethod() throws Exception { + assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo", + (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY)); + assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo")); + assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo", + (Object[]) null)); + assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo", + null, 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})); + + assertThrows( + NoSuchMethodException.class, + () -> MethodUtils.invokeExactMethod(testBean, "foo", NumberUtils.BYTE_ONE)); + + assertThrows( + NoSuchMethodException.class, + () -> MethodUtils.invokeExactMethod(testBean, "foo", NumberUtils.LONG_ONE)); + assertThrows(NoSuchMethodException.class, () -> MethodUtils.invokeExactMethod(testBean, "foo", Boolean.TRUE)); + } + + @Test + public void testInvokeStaticMethod() throws Exception { + assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class, + "bar", (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY)); + assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class, + "bar", (Object[]) null)); + assertEquals("bar()", MethodUtils.invokeStaticMethod(TestBean.class, + "bar", null, 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.DOUBLE_ONE)); + assertEquals("bar(String...)", MethodUtils.invokeStaticMethod( + TestBean.class, "bar", "a", "b")); + assertEquals("bar(long...)", MethodUtils.invokeStaticMethod( + TestBean.class, "bar", 1L, 2L)); + assertEquals("bar(int, String...)", MethodUtils.invokeStaticMethod( + TestBean.class, "bar", NumberUtils.INTEGER_ONE, "a", "b")); + + TestBean.verify(new ImmutablePair<>("String...", new String[]{"x", "y"}), + MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", "x", "y")); + TestBean.verify(new ImmutablePair<>("Number...", new Number[]{17, 23, 42}), + MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", 17, 23, 42)); + TestBean.verify(new ImmutablePair<>("String...", new String[]{"x", "y"}), + MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", "x", "y")); + TestBean.verify(new ImmutablePair<>("Number...", new Number[]{17, 23, 42}), + MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", 17, 23, 42)); + + assertThrows( + NoSuchMethodException.class, () -> MethodUtils.invokeStaticMethod(TestBean.class, "does_not_exist")); + } + + @Test + public void testInvokeExactStaticMethod() throws Exception { + assertEquals("bar()", MethodUtils.invokeExactStaticMethod(TestBean.class, + "bar", (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY)); + assertEquals("bar()", MethodUtils.invokeExactStaticMethod(TestBean.class, + "bar", (Object[]) null)); + assertEquals("bar()", MethodUtils.invokeExactStaticMethod(TestBean.class, + "bar", null, 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})); + + assertThrows( + NoSuchMethodException.class, + () -> MethodUtils.invokeExactStaticMethod(TestBean.class, "bar", NumberUtils.BYTE_ONE)); + assertThrows( + NoSuchMethodException.class, + () -> MethodUtils.invokeExactStaticMethod(TestBean.class, "bar", NumberUtils.LONG_ONE)); + assertThrows( + NoSuchMethodException.class, + () -> MethodUtils.invokeExactStaticMethod(TestBean.class, "bar", Boolean.TRUE)); + } + + @Test + public void testGetAccessibleInterfaceMethod() throws Exception { + final Class<?>[][] p = {ArrayUtils.EMPTY_CLASS_ARRAY, null}; + for (final Class<?>[] element : p) { + final Method method = TestMutable.class.getMethod("getValue", element); + final Method accessibleMethod = MethodUtils.getAccessibleMethod(method); + assertNotSame(accessibleMethod, method); + assertSame(Mutable.class, accessibleMethod.getDeclaringClass()); + } + } + + @Test + public void testGetAccessibleMethodPrivateInterface() throws Exception { + final Method expected = TestBeanWithInterfaces.class.getMethod("foo"); + assertNotNull(expected); + final Method actual = MethodUtils.getAccessibleMethod(TestBeanWithInterfaces.class, "foo"); + assertNull(actual); + } + + @Test + public void testGetAccessibleInterfaceMethodFromDescription() { + final Class<?>[][] p = {ArrayUtils.EMPTY_CLASS_ARRAY, null}; + for (final Class<?>[] element : p) { + final Method accessibleMethod = MethodUtils.getAccessibleMethod( + TestMutable.class, "getValue", element); + assertSame(Mutable.class, accessibleMethod.getDeclaringClass()); + } + } + + @Test + public void testGetAccessiblePublicMethod() throws Exception { + assertSame(MutableObject.class, MethodUtils.getAccessibleMethod( + MutableObject.class.getMethod("getValue", + ArrayUtils.EMPTY_CLASS_ARRAY)).getDeclaringClass()); + } + + @Test + public void testGetAccessiblePublicMethodFromDescription() { + assertSame(MutableObject.class, MethodUtils.getAccessibleMethod( + MutableObject.class, "getValue", ArrayUtils.EMPTY_CLASS_ARRAY) + .getDeclaringClass()); + } + + @Test + public void testGetAccessibleMethodInaccessible() throws Exception { + final Method expected = TestBean.class.getDeclaredMethod("privateStuff"); + final Method actual = MethodUtils.getAccessibleMethod(expected); + assertNull(actual); + } + + @Test + public void testGetMatchingAccessibleMethod() { + 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(Long.TYPE)); + expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo", + singletonArray(Long.TYPE), singletonArray(Long.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)); + expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo", + singletonArray(Double.TYPE), singletonArray(Double.TYPE)); + expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo", + new Class[]{String.class, String.class}, new Class[]{String[].class}); + expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo", + new Class[]{Integer.TYPE, String.class, String.class}, new Class[]{Integer.class, String[].class}); + expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testOne", + singletonArray(ParentObject.class), singletonArray(ParentObject.class)); + expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testOne", + singletonArray(ChildObject.class), singletonArray(ParentObject.class)); + expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testTwo", + singletonArray(ParentObject.class), singletonArray(GrandParentObject.class)); + expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testTwo", + singletonArray(ChildObject.class), singletonArray(ChildInterface.class)); + } + + @Test + public void testNullArgument() { + expectMatchingAccessibleMethodParameterTypes(TestBean.class, "oneParameter", + singletonArray(null), singletonArray(String.class)); + } + + @Test + public void testGetOverrideHierarchyIncludingInterfaces() { + final Method method = MethodUtils.getAccessibleMethod(StringParameterizedChild.class, "consume", String.class); + final Iterator<MethodDescriptor> expected = + Arrays.asList(new MethodDescriptor(StringParameterizedChild.class, "consume", String.class), + new MethodDescriptor(GenericParent.class, "consume", GenericParent.class.getTypeParameters()[0]), + new MethodDescriptor(GenericConsumer.class, "consume", GenericConsumer.class.getTypeParameters()[0])) + .iterator(); + for (final Method m : MethodUtils.getOverrideHierarchy(method, Interfaces.INCLUDE)) { + assertTrue(expected.hasNext()); + final MethodDescriptor md = expected.next(); + assertEquals(md.declaringClass, m.getDeclaringClass()); + assertEquals(md.name, m.getName()); + assertEquals(md.parameterTypes.length, m.getParameterTypes().length); + for (int i = 0; i < md.parameterTypes.length; i++) { + assertTrue(TypeUtils.equals(md.parameterTypes[i], m.getGenericParameterTypes()[i])); + } + } + assertFalse(expected.hasNext()); + } + + @Test + public void testGetOverrideHierarchyExcludingInterfaces() { + final Method method = MethodUtils.getAccessibleMethod(StringParameterizedChild.class, "consume", String.class); + final Iterator<MethodDescriptor> expected = + Arrays.asList(new MethodDescriptor(StringParameterizedChild.class, "consume", String.class), + new MethodDescriptor(GenericParent.class, "consume", GenericParent.class.getTypeParameters()[0])) + .iterator(); + for (final Method m : MethodUtils.getOverrideHierarchy(method, Interfaces.EXCLUDE)) { + assertTrue(expected.hasNext()); + final MethodDescriptor md = expected.next(); + assertEquals(md.declaringClass, m.getDeclaringClass()); + assertEquals(md.name, m.getName()); + assertEquals(md.parameterTypes.length, m.getParameterTypes().length); + for (int i = 0; i < md.parameterTypes.length; i++) { + assertTrue(TypeUtils.equals(md.parameterTypes[i], m.getGenericParameterTypes()[i])); + } + } + assertFalse(expected.hasNext()); + } + + @Test + @Annotated + public void testGetMethodsWithAnnotation() throws NoSuchMethodException { + assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class)); + + final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(MethodUtilsTest.class, Annotated.class); + assertEquals(2, methodsWithAnnotation.length); + assertThat(methodsWithAnnotation, hasItemInArray(MethodUtilsTest.class.getMethod("testGetMethodsWithAnnotation"))); + assertThat(methodsWithAnnotation, hasItemInArray(MethodUtilsTest.class.getMethod("testGetMethodsListWithAnnotation"))); + } + + @Test + public void testGetMethodsWithAnnotationSearchSupersAndIgnoreAccess() { + assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class, + true, true)); + + final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class, + true, true); + assertEquals(4, methodsWithAnnotation.length); + assertEquals("PublicChild", methodsWithAnnotation[0].getDeclaringClass().getSimpleName()); + assertEquals("PublicChild", methodsWithAnnotation[1].getDeclaringClass().getSimpleName()); + assertTrue(methodsWithAnnotation[0].getName().endsWith("AnnotatedMethod")); + assertTrue(methodsWithAnnotation[1].getName().endsWith("AnnotatedMethod")); + assertEquals("Foo.doIt", + methodsWithAnnotation[2].getDeclaringClass().getSimpleName() + '.' + + methodsWithAnnotation[2].getName()); + assertEquals("Parent.parentProtectedAnnotatedMethod", + methodsWithAnnotation[3].getDeclaringClass().getSimpleName() + '.' + + methodsWithAnnotation[3].getName()); + } + + @Test + public void testGetMethodsWithAnnotationNotSearchSupersButIgnoreAccess() { + assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class, + false, true)); + + final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class, + false, true); + assertEquals(2, methodsWithAnnotation.length); + assertEquals("PublicChild", methodsWithAnnotation[0].getDeclaringClass().getSimpleName()); + assertEquals("PublicChild", methodsWithAnnotation[1].getDeclaringClass().getSimpleName()); + assertTrue(methodsWithAnnotation[0].getName().endsWith("AnnotatedMethod")); + assertTrue(methodsWithAnnotation[1].getName().endsWith("AnnotatedMethod")); + } + + @Test + public void testGetMethodsWithAnnotationSearchSupersButNotIgnoreAccess() { + assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class, + true, false)); + + final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class, + true, false); + assertEquals(2, methodsWithAnnotation.length); + assertEquals("PublicChild.publicAnnotatedMethod", + methodsWithAnnotation[0].getDeclaringClass().getSimpleName() + '.' + + methodsWithAnnotation[0].getName()); + assertEquals("Foo.doIt", + methodsWithAnnotation[1].getDeclaringClass().getSimpleName() + '.' + + methodsWithAnnotation[1].getName()); + } + + @Test + public void testGetMethodsWithAnnotationNotSearchSupersAndNotIgnoreAccess() { + assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class, + false, false)); + + final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class, + false, false); + assertEquals(1, methodsWithAnnotation.length); + assertEquals("PublicChild.publicAnnotatedMethod", + methodsWithAnnotation[0].getDeclaringClass().getSimpleName() + '.' + + methodsWithAnnotation[0].getName()); + } + + @Test + public void testGetAnnotationSearchSupersAndIgnoreAccess() throws NoSuchMethodException { + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"), + Annotated.class, true, true)); + assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class, + true, true)); + assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"), + Annotated.class, true, true)); + assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"), + Annotated.class, true, true)); + assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"), + Annotated.class, true, true)); + + assertNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("parentNotAnnotatedMethod", String.class), + Annotated.class, true, true)); + assertNotNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("parentProtectedAnnotatedMethod", String.class), + Annotated.class, true, true)); + assertNotNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getDeclaredMethod("privateAnnotatedMethod", String.class), + Annotated.class, true, true)); + assertNotNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("publicAnnotatedMethod", String.class), + Annotated.class, true, true)); + } + + @Test + public void testGetAnnotationNotSearchSupersButIgnoreAccess() throws NoSuchMethodException { + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"), + Annotated.class, false, true)); + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class, + false, true)); + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"), + Annotated.class, false, true)); + assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"), + Annotated.class, false, true)); + assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"), + Annotated.class, false, true)); + } + + @Test + public void testGetAnnotationSearchSupersButNotIgnoreAccess() throws NoSuchMethodException { + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"), + Annotated.class, true, false)); + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class, + true, false)); + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"), + Annotated.class, true, false)); + assertNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"), + Annotated.class, true, false)); + assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"), + Annotated.class, true, false)); + + assertNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("parentNotAnnotatedMethod", String.class), + Annotated.class, true, false)); + assertNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("parentProtectedAnnotatedMethod", String.class), + Annotated.class, true, false)); + assertNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getDeclaredMethod("privateAnnotatedMethod", String.class), + Annotated.class, true, false)); + assertNotNull(MethodUtils.getAnnotation(StringParameterizedChild.class.getMethod("publicAnnotatedMethod", String.class), + Annotated.class, true, false)); + } + + @Test + public void testGetAnnotationNotSearchSupersAndNotIgnoreAccess() throws NoSuchMethodException { + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"), + Annotated.class, false, false)); + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class, + false, false)); + assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"), + Annotated.class, false, false)); + assertNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"), + Annotated.class, false, false)); + assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"), + Annotated.class, false, false)); + } + + @Test + public void testGetMethodsWithAnnotationIllegalArgumentException1() { + assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsWithAnnotation(FieldUtilsTest.class, null)); + } + + @Test + public void testGetMethodsWithAnnotationIllegalArgumentException2() { + assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsWithAnnotation(null, Annotated.class)); + } + + @Test + public void testGetMethodsWithAnnotationIllegalArgumentException3() { + assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsWithAnnotation(null, null)); + } + + @Test + @Annotated + public void testGetMethodsListWithAnnotation() throws NoSuchMethodException { + assertEquals(0, MethodUtils.getMethodsListWithAnnotation(Object.class, Annotated.class).size()); + + final List<Method> methodWithAnnotation = MethodUtils.getMethodsListWithAnnotation(MethodUtilsTest.class, Annotated.class); + assertEquals(2, methodWithAnnotation.size()); + assertThat(methodWithAnnotation, hasItems( + MethodUtilsTest.class.getMethod("testGetMethodsWithAnnotation"), + MethodUtilsTest.class.getMethod("testGetMethodsListWithAnnotation") + )); + } + + @Test + public void testGetMethodsListWithAnnotationIllegalArgumentException1() { + assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsListWithAnnotation(FieldUtilsTest.class, null)); + } + + @Test + public void testGetMethodsListWithAnnotationIllegalArgumentException2() { + assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsListWithAnnotation(null, Annotated.class)); + } + + @Test + public void testGetMethodsListWithAnnotationIllegalArgumentException3() { + assertThrows(NullPointerException.class, () -> MethodUtils.getMethodsListWithAnnotation(null, null)); + } + + @Test + public void testGetAnnotationIllegalArgumentException1() { + assertThrows(NullPointerException.class, + () -> MethodUtils.getAnnotation(FieldUtilsTest.class.getDeclaredMethods()[0], null, true, true)); + } + + @Test + public void testGetAnnotationIllegalArgumentException2() { + assertThrows(NullPointerException.class, () -> MethodUtils.getAnnotation(null, Annotated.class, true, true)); + } + + @Test + public void testGetAnnotationIllegalArgumentException3() { + assertThrows(NullPointerException.class, () -> MethodUtils.getAnnotation(null, null, true, true)); + } + + private void expectMatchingAccessibleMethodParameterTypes(final Class<?> cls, + final String methodName, final Class<?>[] requestTypes, final Class<?>[] actualTypes) { + final Method m = MethodUtils.getMatchingAccessibleMethod(cls, methodName, + requestTypes); + assertNotNull(m, "could not find any matches for " + methodName + + " (" + (requestTypes == null ? null : toString(requestTypes)) + ")"); + assertArrayEquals(actualTypes, m.getParameterTypes(), toString(m.getParameterTypes()) + " not equals " + toString(actualTypes)); + } + + private String toString(final Class<?>[] c) { + return Arrays.asList(c).toString(); + } + + private Class<?>[] singletonArray(final Class<?> c) { + Class<?>[] result = classCache.get(c); + if (result == null) { + result = new Class[]{c}; + classCache.put(c, result); + } + return result; + } + + public static class InheritanceBean { + public void testOne(final Object obj) { + } + + public void testOne(final GrandParentObject obj) { + } + + public void testOne(final ParentObject obj) { + } + + public void testTwo(final Object obj) { + } + + public void testTwo(final GrandParentObject obj) { + } + + public void testTwo(final ChildInterface obj) { + } + } + + interface ChildInterface { + } + + public static class GrandParentObject { + } + + public static class ParentObject extends GrandParentObject { + } + + public static class ChildObject extends ParentObject implements ChildInterface { + } + + private static class MethodDescriptor { + final Class<?> declaringClass; + final String name; + final Type[] parameterTypes; + + MethodDescriptor(final Class<?> declaringClass, final String name, final Type... parameterTypes) { + this.declaringClass = declaringClass; + this.name = name; + this.parameterTypes = parameterTypes; + } + } + + @Test + public void testVarArgsUnboxing() throws Exception { + final TestBean testBean = new TestBean(); + final int[] actual = (int[]) MethodUtils.invokeMethod(testBean, "unboxing", Integer.valueOf(1), Integer.valueOf(2)); + assertArrayEquals(new int[]{1, 2}, actual); + } + + @Test + public void testInvokeMethodForceAccessNoArgs() throws Exception { + assertEquals("privateStringStuff()", MethodUtils.invokeMethod(testBean, true, "privateStringStuff")); + } + + @Test + public void testInvokeMethodForceAccessWithArgs() throws Exception { + assertEquals("privateStringStuff(Integer)", MethodUtils.invokeMethod(testBean, true, "privateStringStuff", 5)); + assertEquals("privateStringStuff(double)", MethodUtils.invokeMethod(testBean, true, "privateStringStuff", 5.0d)); + assertEquals("privateStringStuff(String)", MethodUtils.invokeMethod(testBean, true, "privateStringStuff", "Hi There")); + assertEquals("privateStringStuff(Object)", MethodUtils.invokeMethod(testBean, true, "privateStringStuff", new Date())); + } + + @Test + public void testDistance() throws Exception { + final Method distanceMethod = MethodUtils.getMatchingMethod(MethodUtils.class, "distance", Class[].class, Class[].class); + distanceMethod.setAccessible(true); + + assertEquals(-1, distanceMethod.invoke(null, new Class[]{String.class}, new Class[]{Date.class})); + assertEquals(0, distanceMethod.invoke(null, new Class[]{Date.class}, new Class[]{Date.class})); + assertEquals(1, distanceMethod.invoke(null, new Class[]{Integer.class}, new Class[]{ClassUtils.wrapperToPrimitive(Integer.class)})); + assertEquals(2, distanceMethod.invoke(null, new Class[]{Integer.class}, new Class[]{Object.class})); + + distanceMethod.setAccessible(false); + } + + @Test + public void testGetMatchingMethod() throws NoSuchMethodException { + assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod"), + GetMatchingMethodClass.class.getMethod("testMethod")); + + assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod", Long.TYPE), + GetMatchingMethodClass.class.getMethod("testMethod", Long.TYPE)); + + assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod", Long.class), + GetMatchingMethodClass.class.getMethod("testMethod", Long.class)); + + assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod", (Class<?>) null), + GetMatchingMethodClass.class.getMethod("testMethod", Long.class)); + + assertThrows(IllegalStateException.class, + () -> MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod2", (Class<?>) null)); + + assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod3", Long.TYPE, Long.class), + GetMatchingMethodClass.class.getMethod("testMethod3", Long.TYPE, Long.class)); + + assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod3", Long.class, Long.TYPE), + GetMatchingMethodClass.class.getMethod("testMethod3", Long.class, Long.TYPE)); + + assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod3", null, Long.TYPE), + GetMatchingMethodClass.class.getMethod("testMethod3", Long.class, Long.TYPE)); + + assertEquals(MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod3", Long.TYPE, null), + GetMatchingMethodClass.class.getMethod("testMethod3", Long.TYPE, Long.class)); + + assertThrows(IllegalStateException.class, + () -> MethodUtils.getMatchingMethod(GetMatchingMethodClass.class, "testMethod4", null, null)); + } + + private static final class GetMatchingMethodClass { + public void testMethod() { + } + + public void testMethod(final Long aLong) { + } + + public void testMethod(final long aLong) { + } + + public void testMethod2(final Long aLong) { + } + + public void testMethod2(final Color aColor) { + } + + public void testMethod2(final long aLong) { + } + + public void testMethod3(final long aLong, final Long anotherLong) { + } + + public void testMethod3(final Long aLong, final long anotherLong) { + } + + public void testMethod3(final Long aLong, final Long anotherLong) { + } + + public void testMethod4(final Long aLong, final Long anotherLong) { + } + + public void testMethod4(final Color aColor1, final Color aColor2) { + } + } +} |