From d7593b3bd1685980c0695bbb4a286d6179676070 Mon Sep 17 00:00:00 2001 From: Naveed Khan Date: Wed, 29 Jul 2026 23:43:23 +0530 Subject: [PATCH] suppress case variants of mapped names in isPropertySuppressed The check compared the raw expression token, while the mapped-descriptor fallback it guards derives its accessor names from the capitalized property name, so a name differing only in the case of its first character resolved the same accessors and was still readable and writable. --- .../beanutils2/MappedPropertyDescriptor.java | 2 +- .../commons/beanutils2/PropertyUtilsBean.java | 12 +++++++++--- .../commons/beanutils2/PropertyUtilsTest.java | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java index 21d69d464..9a5b07e49 100644 --- a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java +++ b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java @@ -162,7 +162,7 @@ private Class reLoadClass(final String name) { * * @param s The property name */ - private static String capitalizePropertyName(final String s) { + static String capitalizePropertyName(final String s) { if (s.isEmpty()) { return s; } diff --git a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java index be0f30665..b5303fd5f 100644 --- a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java +++ b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java @@ -1068,15 +1068,21 @@ private Object invokeMethod(final Method method, final Object bean, final Object /** * Tests whether a property name has been removed by a registered {@link SuppressPropertiesBeanIntrospector}. The mapped-descriptor fallback in * {@link #getPropertyDescriptor(Object, String)} bypasses the introspection pipeline, so suppressed mapped property names must be filtered explicitly. + * {@link MappedPropertyDescriptor} derives its accessor names from the capitalized property name, so names differing only in the case of their first + * character resolve the same accessors and are compared on that capitalized form here. * * @param name The property name to test. * @return {@code true} if the name is suppressed by an introspector. */ private boolean isPropertySuppressed(final String name) { + final String base = MappedPropertyDescriptor.capitalizePropertyName(name); for (final BeanIntrospector introspector : introspectors) { - if (introspector instanceof SuppressPropertiesBeanIntrospector - && ((SuppressPropertiesBeanIntrospector) introspector).getSuppressedProperties().contains(name)) { - return true; + if (introspector instanceof SuppressPropertiesBeanIntrospector) { + for (final String suppressed : ((SuppressPropertiesBeanIntrospector) introspector).getSuppressedProperties()) { + if (base.equals(MappedPropertyDescriptor.capitalizePropertyName(suppressed))) { + return true; + } + } } } return false; diff --git a/src/test/java/org/apache/commons/beanutils2/PropertyUtilsTest.java b/src/test/java/org/apache/commons/beanutils2/PropertyUtilsTest.java index 683389508..fb6c1aee5 100644 --- a/src/test/java/org/apache/commons/beanutils2/PropertyUtilsTest.java +++ b/src/test/java/org/apache/commons/beanutils2/PropertyUtilsTest.java @@ -315,6 +315,23 @@ void testCustomIntrospectionSuppressedMappedProperty() throws Exception { assertEquals("First Value", unsuppressed.getProperty(bean, "mappedProperty(First Key)"), "Mapped property should be readable when not suppressed"); } + /** + * {@link MappedPropertyDescriptor} capitalizes the first character of the property name to build the accessor names, so {@code MappedProperty(key)} + * resolves the same {@code getMappedProperty}/{@code setMappedProperty} pair as {@code mappedProperty(key)} and must be suppressed with it. + */ + @Test + void testCustomIntrospectionSuppressedMappedPropertyCaseVariant() throws Exception { + final PropertyUtilsBean pub = new PropertyUtilsBean(); + pub.addBeanIntrospector(new SuppressPropertiesBeanIntrospector(Arrays.asList("mappedProperty"))); + + assertNull(pub.getPropertyDescriptor(bean, "MappedProperty"), "Case variant of a suppressed mapped property should have no descriptor"); + assertThrows(NoSuchMethodException.class, () -> pub.getProperty(bean, "MappedProperty(First Key)"), + "Case variant of a suppressed mapped property must not be readable"); + assertThrows(NoSuchMethodException.class, () -> pub.setProperty(bean, "MappedProperty(First Key)", "changed"), + "Case variant of a suppressed mapped property must not be writable"); + assertEquals("First Value", bean.getMappedProperty("First Key"), "Suppressed mapped property must be unchanged"); + } + /** * Test the describe() method. */