From 94377a8eae28d23af10cad9ba499ff7d66f48275 Mon Sep 17 00:00:00 2001 From: Markus Jung Date: Tue, 30 Dec 2025 13:25:21 +0100 Subject: [PATCH 1/4] XBEAN-453 - honor Option.CASE_INSENSITIVE_PROPERTIES for ctors --- .../org/apache/xbean/recipe/ObjectRecipe.java | 23 ++++- .../apache/xbean/recipe/ReflectionUtil.java | 30 ++++++- .../org/apache/xbean/recipe/XBean353Test.java | 86 +++++++++++++++++++ 3 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 xbean-reflect/src/test/java/org/apache/xbean/recipe/XBean353Test.java diff --git a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java index 499bd198..1bd04c9e 100644 --- a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java +++ b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java @@ -577,18 +577,33 @@ private Factory findFactory(Type expectedType) { return constructor; } - private Object[] extractConstructorArgs(Map propertyValues, Factory factory) { + private Object[] extractConstructorArgs(Map propertyValues, Factory factory) { List parameterNames = factory.getParameterNames(); List parameterTypes = factory.getParameterTypes(); Object[] parameters = new Object[parameterNames.size()]; for (int i = 0; i < parameterNames.size(); i++) { - Property name = new Property(parameterNames.get(i)); Type type = parameterTypes.get(i); + String name = parameterNames.get(i); + + Property property = null; + if (options.contains(Option.CASE_INSENSITIVE_PROPERTIES)) { + for (Property candidate : propertyValues.keySet()) { + if (candidate.name.equalsIgnoreCase(name)) { + property = candidate; + break; + } + } + } else { + Property candidate = new Property(name); + if (propertyValues.containsKey(candidate)) { + property = candidate; + } + } Object value; - if (propertyValues.containsKey(name)) { - value = propertyValues.remove(name); + if (property != null) { + value = propertyValues.remove(property); if (!RecipeHelper.isInstance(type, value) && !RecipeHelper.isConvertable(type, value, registry)) { throw new ConstructionException("Invalid and non-convertable constructor parameter type: " + "name=" + name + ", " + diff --git a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java index ca16f32d..b1d06fb8 100644 --- a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java +++ b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java @@ -656,7 +656,33 @@ public int compare(Constructor constructor1, Constructor constructor2) { // // Only consider methods where we can supply a value for all of the parameters parameterNames = getParameterNames(constructor); - if (parameterNames == null || !availableProperties.containsAll(parameterNames)) { + if (parameterNames == null) { + continue; + } + + boolean parametersMatch; + if (options.contains(Option.CASE_INSENSITIVE_PROPERTIES)) { + parametersMatch = true; + + for (String parameterName : parameterNames) { + boolean found = false; + for (String propertyName : availableProperties) { + if (parameterName.equalsIgnoreCase(propertyName)) { + found = true; + break; + } + } + + if (!found) { + parametersMatch = false; + break; + } + } + } else { + parametersMatch = availableProperties.containsAll(parameterNames); + } + + if (!parametersMatch) { continue; } } @@ -1039,7 +1065,7 @@ private static String toParameterList(List> parameterTypes) { for (int i = 0; i < parameterTypes.size(); i++) { Class type = parameterTypes.get(i); if (i > 0) buffer.append(", "); - buffer.append(type.getName()); + buffer.append(type != null ? type.getName() : "..."); } } else { buffer.append("..."); diff --git a/xbean-reflect/src/test/java/org/apache/xbean/recipe/XBean353Test.java b/xbean-reflect/src/test/java/org/apache/xbean/recipe/XBean353Test.java new file mode 100644 index 00000000..4b8ff0c3 --- /dev/null +++ b/xbean-reflect/src/test/java/org/apache/xbean/recipe/XBean353Test.java @@ -0,0 +1,86 @@ +/** + * + * 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.xbean.recipe; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class XBean353Test { + + @Test + public void testFindConstructorWithParameterNames() { + + final Set availableProperties = new HashSet<>(); + Collections.addAll(availableProperties, + "PoolSize" + ); + + final List parameterNames = new ArrayList<>(); + Collections.addAll(parameterNames, + "poolSize" + ); + + final ReflectionUtil.ConstructorFactory constructor = ReflectionUtil.findConstructor( + Constructor.class, + parameterNames, + null, + availableProperties, + EnumSet.of(Option.CASE_INSENSITIVE_PROPERTIES)); + + assertNotNull(constructor); + assertEquals(parameterNames, constructor.getParameterNames()); + assertEquals(int.class, constructor.getParameterTypes().get(0)); + } + + @Test + public void testCreateObjectCaseInsensitive() { + final Map availableProperties = new HashMap<>(); + availableProperties.put("PoolSize", 10); + + final String[] paramNames = new String[]{"poolSize"}; + ObjectRecipe recipe = new ObjectRecipe(Constructor.class, paramNames); + recipe.setAllProperties(availableProperties); + recipe.allow(Option.CASE_INSENSITIVE_PROPERTIES); + + final Object o = recipe.create(); + assertNotNull(o); + assertTrue(o instanceof Constructor); + assertEquals(10, ((Constructor) o).poolSize); + } + + public static class Constructor { + + int poolSize; + + public Constructor(int poolSize) { + this.poolSize = poolSize; + } + } +} \ No newline at end of file From e79eca2f1e011d1cc6e31452490d5eea51733ae1 Mon Sep 17 00:00:00 2001 From: Markus Jung Date: Tue, 30 Dec 2025 20:55:05 +0100 Subject: [PATCH 2/4] use case-insensitive collections --- .../org/apache/xbean/recipe/ObjectRecipe.java | 41 +++++++++---------- .../apache/xbean/recipe/ReflectionUtil.java | 28 +------------ .../org/apache/xbean/recipe/XBean353Test.java | 31 -------------- 3 files changed, 21 insertions(+), 79 deletions(-) diff --git a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java index 1bd04c9e..8c384af6 100644 --- a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java +++ b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java @@ -24,11 +24,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.apache.xbean.propertyeditor.PropertyEditorRegistry; import org.apache.xbean.recipe.ReflectionUtil.*; @@ -270,7 +273,11 @@ protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throw // // clone the properties so they can be used again - Map propertyValues = new LinkedHashMap(properties); + Map propertyValues = options.contains(Option.CASE_INSENSITIVE_PROPERTIES) + ? new TreeMap<>(Comparator.comparing(property -> property.name, String.CASE_INSENSITIVE_ORDER)) + : new LinkedHashMap<>(); + + propertyValues.putAll(properties); // // create the instance @@ -567,43 +574,35 @@ private Factory findFactory(Type expectedType) { consturctorClass = type; } + Set availableProperties = getProperties().keySet(); + if (options.contains(Option.CASE_INSENSITIVE_PROPERTIES)) { + Set caseInsensitiveProperties = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + caseInsensitiveProperties.addAll(availableProperties); + availableProperties = caseInsensitiveProperties; + } + ConstructorFactory constructor = ReflectionUtil.findConstructor( consturctorClass, constructorArgNames, constructorArgTypes, - getProperties().keySet(), + availableProperties, options); return constructor; } - private Object[] extractConstructorArgs(Map propertyValues, Factory factory) { + private Object[] extractConstructorArgs(Map propertyValues, Factory factory) { List parameterNames = factory.getParameterNames(); List parameterTypes = factory.getParameterTypes(); Object[] parameters = new Object[parameterNames.size()]; for (int i = 0; i < parameterNames.size(); i++) { + Property name = new Property(parameterNames.get(i)); Type type = parameterTypes.get(i); - String name = parameterNames.get(i); - - Property property = null; - if (options.contains(Option.CASE_INSENSITIVE_PROPERTIES)) { - for (Property candidate : propertyValues.keySet()) { - if (candidate.name.equalsIgnoreCase(name)) { - property = candidate; - break; - } - } - } else { - Property candidate = new Property(name); - if (propertyValues.containsKey(candidate)) { - property = candidate; - } - } Object value; - if (property != null) { - value = propertyValues.remove(property); + if (propertyValues.containsKey(name)) { + value = propertyValues.remove(name); if (!RecipeHelper.isInstance(type, value) && !RecipeHelper.isConvertable(type, value, registry)) { throw new ConstructionException("Invalid and non-convertable constructor parameter type: " + "name=" + name + ", " + diff --git a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java index b1d06fb8..5a3f9503 100644 --- a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java +++ b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java @@ -656,33 +656,7 @@ public int compare(Constructor constructor1, Constructor constructor2) { // // Only consider methods where we can supply a value for all of the parameters parameterNames = getParameterNames(constructor); - if (parameterNames == null) { - continue; - } - - boolean parametersMatch; - if (options.contains(Option.CASE_INSENSITIVE_PROPERTIES)) { - parametersMatch = true; - - for (String parameterName : parameterNames) { - boolean found = false; - for (String propertyName : availableProperties) { - if (parameterName.equalsIgnoreCase(propertyName)) { - found = true; - break; - } - } - - if (!found) { - parametersMatch = false; - break; - } - } - } else { - parametersMatch = availableProperties.containsAll(parameterNames); - } - - if (!parametersMatch) { + if (parameterNames == null || !availableProperties.containsAll(parameterNames)) { continue; } } diff --git a/xbean-reflect/src/test/java/org/apache/xbean/recipe/XBean353Test.java b/xbean-reflect/src/test/java/org/apache/xbean/recipe/XBean353Test.java index 4b8ff0c3..3ad31fa5 100644 --- a/xbean-reflect/src/test/java/org/apache/xbean/recipe/XBean353Test.java +++ b/xbean-reflect/src/test/java/org/apache/xbean/recipe/XBean353Test.java @@ -19,14 +19,8 @@ import org.junit.Test; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -34,31 +28,6 @@ public class XBean353Test { - @Test - public void testFindConstructorWithParameterNames() { - - final Set availableProperties = new HashSet<>(); - Collections.addAll(availableProperties, - "PoolSize" - ); - - final List parameterNames = new ArrayList<>(); - Collections.addAll(parameterNames, - "poolSize" - ); - - final ReflectionUtil.ConstructorFactory constructor = ReflectionUtil.findConstructor( - Constructor.class, - parameterNames, - null, - availableProperties, - EnumSet.of(Option.CASE_INSENSITIVE_PROPERTIES)); - - assertNotNull(constructor); - assertEquals(parameterNames, constructor.getParameterNames()); - assertEquals(int.class, constructor.getParameterTypes().get(0)); - } - @Test public void testCreateObjectCaseInsensitive() { final Map availableProperties = new HashMap<>(); From fab6bc6a104f94a1b69532156f3264b9d9d39b22 Mon Sep 17 00:00:00 2001 From: Markus Jung Date: Sat, 3 Jan 2026 21:45:12 +0100 Subject: [PATCH 3/4] use case-insensitive collections for findFactory as well --- .../org/apache/xbean/recipe/ObjectRecipe.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java index 8c384af6..a93dec60 100644 --- a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java +++ b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java @@ -543,6 +543,13 @@ private void setProperty(Object instance, Class clazz, Property propertyName, Ob } private Factory findFactory(Type expectedType) { + Set availableProperties = getProperties().keySet(); + if (options.contains(Option.CASE_INSENSITIVE_PROPERTIES)) { + Set caseInsensitiveProperties = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + caseInsensitiveProperties.addAll(availableProperties); + availableProperties = caseInsensitiveProperties; + } + Class type = getType(); // @@ -554,7 +561,7 @@ private Factory findFactory(Type expectedType) { factoryMethod, constructorArgNames, constructorArgTypes, - getProperties().keySet(), + availableProperties, options); return staticFactory; } catch (MissingFactoryMethodException ignored) { @@ -574,13 +581,6 @@ private Factory findFactory(Type expectedType) { consturctorClass = type; } - Set availableProperties = getProperties().keySet(); - if (options.contains(Option.CASE_INSENSITIVE_PROPERTIES)) { - Set caseInsensitiveProperties = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - caseInsensitiveProperties.addAll(availableProperties); - availableProperties = caseInsensitiveProperties; - } - ConstructorFactory constructor = ReflectionUtil.findConstructor( consturctorClass, constructorArgNames, From 8154edd22c2b2c4a807b6b4d13cd52fe81fbd7d4 Mon Sep 17 00:00:00 2001 From: Richard Zowalla Date: Sun, 4 Jan 2026 08:51:17 +0100 Subject: [PATCH 4/4] Revert null check in favour of other PR --- .../src/main/java/org/apache/xbean/recipe/ReflectionUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java index 5a3f9503..ca16f32d 100644 --- a/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java +++ b/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java @@ -1039,7 +1039,7 @@ private static String toParameterList(List> parameterTypes) { for (int i = 0; i < parameterTypes.size(); i++) { Class type = parameterTypes.get(i); if (i > 0) buffer.append(", "); - buffer.append(type != null ? type.getName() : "..."); + buffer.append(type.getName()); } } else { buffer.append("...");