diff --git a/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/reflect/ConfigurationUtilityTest.java b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/reflect/ConfigurationUtilityTest.java new file mode 100644 index 00000000000..326167f1734 --- /dev/null +++ b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/reflect/ConfigurationUtilityTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010, 2024 BSI Business Systems Integration AG + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.scout.rt.platform.reflect; + +import static org.junit.Assert.*; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.scout.rt.platform.util.CollectionUtility; +import org.junit.Test; + +/** + * Testcases for ConfigurationUtility + */ +public class ConfigurationUtilityTest { + + @Test + public void testGetDeclaredMethod() { + runTestGetDeclaredMethod(Fixture.class, "privateMethod", int.class); + runTestGetDeclaredMethod(Fixture.class, "defaultMethod", long.class); + runTestGetDeclaredMethod(Fixture.class, "protectedMethod", float.class); + runTestGetDeclaredMethod(Fixture.class, "publicMethod", double.class); + + runTestGetDeclaredMethod(SubFixture.class, "privateSubMethod", int.class); + runTestGetDeclaredMethod(SubFixture.class, "defaultSubMethod", long.class); + runTestGetDeclaredMethod(SubFixture.class, "protectedSubMethod", float.class); + runTestGetDeclaredMethod(SubFixture.class, "publicSubMethod", double.class); + runTestGetDeclaredMethod(SubFixture.class, "publicMethod", double.class); + + assertNull(ConfigurationUtility.getDeclaredMethod(SubFixture.class, "privateMethod", new Class[]{int.class})); + assertNull(ConfigurationUtility.getDeclaredMethod(SubFixture.class, "defaultMethod", new Class[]{long.class})); + assertNull(ConfigurationUtility.getDeclaredMethod(SubFixture.class, "protectedMethod", new Class[]{float.class})); + } + + @Test + public void testGetAllDeclaredMethods() { + Method[] methods = ConfigurationUtility.getAllDeclaredMethods(Fixture.class); + // NOTE: do not assert method count and exact method set since test framework agents could add additional methods (e.g. $jacocoInit method used for profiling) + assertTrue(CollectionUtility.containsAll(Arrays.stream(methods).map(Method::getName).collect(Collectors.toSet()), Set.of("privateMethod", "defaultMethod", "protectedMethod", "publicMethod"))); + assertSame(methods, ConfigurationUtility.getAllDeclaredMethods(Fixture.class)); + + Method[] methodsSub = ConfigurationUtility.getAllDeclaredMethods(SubFixture.class); + // NOTE: do not assert method count and exact method set since test framework agents could add additional methods (e.g. $jacocoInit method used for profiling) + assertTrue(CollectionUtility.containsAll(Arrays.stream(methodsSub).map(Method::getName).collect(Collectors.toSet()), Set.of("privateSubMethod", "defaultSubMethod", "protectedSubMethod", "publicSubMethod", "publicMethod"))); + assertSame(methodsSub, ConfigurationUtility.getAllDeclaredMethods(SubFixture.class)); + } + + @Test + public void testIsMethodOverride() { + assertTrue(ConfigurationUtility.isMethodOverwrite(Fixture.class, "publicMethod", new Class[]{double.class}, SubFixture.class)); + assertTrue(ConfigurationUtility.isMethodOverwrite(Fixture.class, "protectedMethod", new Class[]{float.class}, SubSubFixture.class)); + + // method overridden by SubFixture + assertTrue(ConfigurationUtility.isMethodOverwrite(Fixture.class, "publicMethod", new Class[]{double.class}, SubSubFixture.class)); + + // method not overridden + assertFalse(ConfigurationUtility.isMethodOverwrite(Fixture.class, "protectedMethod", new Class[]{float.class}, SubFixture.class)); + + // method on same class + assertFalse(ConfigurationUtility.isMethodOverwrite(Fixture.class, "publicMethod", new Class[]{double.class}, Fixture.class)); + + // non-existent method + assertFalse(ConfigurationUtility.isMethodOverwrite(Fixture.class, "nonExistentMethod", new Class[]{double.class}, SubFixture.class)); + + // wrong parameters + assertFalse(ConfigurationUtility.isMethodOverwrite(Fixture.class, "publicMethod", new Class[]{int.class}, SubFixture.class)); + } + + protected void runTestGetDeclaredMethod(Class clazz, String methodName, Class paramType) { + Method m = ConfigurationUtility.getDeclaredMethod(clazz, methodName, new Class[]{paramType}); + assertEquals(methodName, m.getName()); + assertEquals(paramType, m.getParameterTypes()[0]); + assertEquals(void.class, m.getReturnType()); + } + + @SuppressWarnings("unused") + private static class Fixture { + private void privateMethod(int i) { + } + + void defaultMethod(long l) { + } + + protected void protectedMethod(float f) { + } + + public void publicMethod(double d) { + } + } + + @SuppressWarnings("unused") + private static class SubFixture extends Fixture { + private void privateSubMethod(int i) { + } + + void defaultSubMethod(long l) { + } + + protected void protectedSubMethod(float f) { + } + + public void publicSubMethod(double d) { + } + + @Override + public void publicMethod(double d) { + // overridden method fixture + super.publicMethod(d); + } + } + + @SuppressWarnings("unused") + private static class SubSubFixture extends SubFixture { + @Override + protected void protectedMethod(float f) { + // overridden method fixture + super.protectedMethod(f); + } + } +} diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/reflect/ConfigurationUtility.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/reflect/ConfigurationUtility.java index 5d33476a5c8..6f0d4fd76ec 100644 --- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/reflect/ConfigurationUtility.java +++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/reflect/ConfigurationUtility.java @@ -9,10 +9,13 @@ */ package org.eclipse.scout.rt.platform.reflect; +import static org.eclipse.scout.rt.platform.util.Assertions.assertNotNull; + import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -28,7 +31,6 @@ import org.eclipse.scout.rt.platform.classid.ClassId; import org.eclipse.scout.rt.platform.exception.PlatformExceptionTranslator; import org.eclipse.scout.rt.platform.extension.InjectFieldTo; -import org.eclipse.scout.rt.platform.util.Assertions; import org.eclipse.scout.rt.platform.util.CollectionUtility; import org.eclipse.scout.rt.platform.util.CompositeObject; import org.eclipse.scout.rt.platform.util.StringUtility; @@ -46,6 +48,11 @@ public final class ConfigurationUtility { */ private static final ConcurrentHashMap declaredPublicClassesCache = new ConcurrentHashMap<>(); + /** + * Cache for declared methods of a class. + */ + private static final Map, Method[]> S_DECLARED_METHOD_CACHE = new ConcurrentHashMap<>(); + private ConfigurationUtility() { } @@ -210,32 +217,44 @@ public static T newInnerInstance(Object instance, Class innerClass) { * @return true if the declared method is overwritten in implementationType */ public static boolean isMethodOverwrite(Class declaringType, String methodName, Class[] parameterTypes, Class implementationType) { - Assertions.assertNotNull(declaringType, "declaringType must not be null"); - Assertions.assertNotNull(methodName, "methodName must not be null"); - Method declaredMethod; - try { - declaredMethod = declaringType.getDeclaredMethod(methodName, parameterTypes); - } - catch (NoSuchMethodException | SecurityException e) { - LOG.error("cannot find declared method {}.{}", declaringType.getName(), methodName, e); + assertNotNull(declaringType, "declaringType must not be null"); + assertNotNull(methodName, "methodName must not be null"); + Method declaredMethod = getDeclaredMethod(declaringType, methodName, parameterTypes); + if (declaredMethod == null) { + LOG.error("cannot find declared method {}.{}", declaringType.getName(), methodName); return false; } Class c = implementationType; while (c != null && c != declaringType) { - try { - //check if method is avaliable - c.getDeclaredMethod(declaredMethod.getName(), declaredMethod.getParameterTypes()); + Method method = getDeclaredMethod(c, declaredMethod.getName(), declaredMethod.getParameterTypes()); + if (method != null) { return true; } - catch (NoSuchMethodException | SecurityException e) { // NOSONAR - //nop - } - //up c = c.getSuperclass(); } return false; } + /** + * @return {@link Method} of class {@code clazz} with given {@code methodName} and {@code parameterTypes} or + * {@code null} if no matching method could be found. + */ + public static Method getDeclaredMethod(Class clazz, String methodName, Class[] parameterTypes) { + for (Method method : getAllDeclaredMethods(clazz)) { + if (method.getName().equals(methodName) && Arrays.equals(method.getParameterTypes(), parameterTypes)) { + return method; + } + } + return null; + } + + /** + * @return array of {@link Method} containing all declared methods of given {@code clazz}. + */ + public static Method[] getAllDeclaredMethods(Class clazz) { + return S_DECLARED_METHOD_CACHE.computeIfAbsent(clazz, Class::getDeclaredMethods); + } + /** * Returns a new array without those classes, that are replaced by another class. The returned array is a new * instance, except there are no replacing classes. Replacing classes are annotated with {@link Replace}. Replacing