Copilot commented on code in PR #16160:
URL: https://github.com/apache/grails-core/pull/16160#discussion_r3792744894


##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/ConfigurationBuilder.groovy:
##########
@@ -463,4 +456,196 @@ abstract class ConfigurationBuilder<B, C> {
     protected void startBuild(Object builder, String configurationPath) {
         // no-op
     }
+    /**
+     * Handle ConversionFailedException - for enums, try case-insensitive 
conversion
+     */
+    private Object handleConversionException(ConversionFailedException e, 
Class argType, String propertyPathForArg, Object fallBackValue) {
+        if (argType.isEnum()) {
+            def value = propertyResolver.getProperty(propertyPathForArg, 
String)
+            if (value != null) {
+                try {
+                    return Enum.valueOf((Class) argType, value.toUpperCase())
+                } catch (IllegalArgumentException e2) {
+                    throw new ConfigurationException("Invalid value for 
setting [$propertyPathForArg]: $e.message", e)
+                }
+            }
+            else {
+                throw new ConfigurationException("Invalid value for setting 
[$propertyPathForArg]: $e.message", e)
+            }
+        }
+        else {
+            ConverterNotFoundException converterNotFoundException = 
findConverterNotFoundException(e)
+            if (converterNotFoundException != null) {
+                return 
handleConverterNotFoundException(converterNotFoundException, argType, 
propertyPathForArg, fallBackValue)
+            }
+            throw new ConfigurationException("Invalid value for setting 
[$propertyPathForArg]: $e.message", e)
+        }
+    }
+
+    private static ConverterNotFoundException 
findConverterNotFoundException(Throwable exception) {
+        Throwable cause = exception
+        while (cause != null) {
+            if (cause instanceof ConverterNotFoundException) {
+                return (ConverterNotFoundException) cause
+            }
+            cause = cause.getCause()
+        }
+        return null
+    }
+
+    /**
+     * Handle ConverterNotFoundException - for nested configuration types,
+     * try to instantiate and populate from Map. This handles Spring 7 
compatibility where
+     * Spring can't auto-convert from LinkedHashMap to these types. This is 
independent of the
+     * Groovy version and is required regardless of @Builder annotation 
retention.
+     */
+    @CompileDynamic
+    private Object handleConverterNotFoundException(ConverterNotFoundException 
e, Class argType, String propertyPathForArg, Object fallBackValue, Object 
rawValue = null) {
+        if (rawValue == null) {
+            try {
+                // Use Object.class to prevent Spring's MapToMapConverter from 
deep-converting values
+                rawValue = propertyResolver.getProperty(propertyPathForArg, 
Object)
+            } catch (ConfigurationException e2) {
+                throw e2
+            } catch (Exception e2) {
+                throw new ConfigurationException("Cannot read configuration 
for path [$propertyPathForArg]: $e2.message", e2)
+            }
+        }
+
+        if (rawValue instanceof Map) {
+            try {
+                Map<String, PropertyDescriptor> writableProperties = [:]
+                Introspector.getBeanInfo(argType).propertyDescriptors.each { 
PropertyDescriptor property ->
+                    if (property.name != 'metaClass' && property.writeMethod 
!= null) {
+                        writableProperties[property.name] = property
+                    }
+                }
+
+                def instance = argType.getDeclaredConstructor().newInstance()
+                if (fallBackValue != null && 
argType.isInstance(fallBackValue)) {
+                    // A map-backed settings type carries arbitrary entries as 
well as declared
+                    // properties, so the inherited entries have to come 
across too or overriding
+                    // one nested value would silently drop the rest.
+                    if (instance instanceof Map && fallBackValue instanceof 
Map) {
+                        ((Map) instance).putAll((Map) fallBackValue)
+                    }
+                    writableProperties.values().each { PropertyDescriptor 
property ->
+                        if (property.readMethod != null && 
property.readMethod.parameterCount == 0) {
+                            Object fallbackPropertyValue = 
property.readMethod.invoke(fallBackValue)
+                            property.writeMethod.invoke(instance, 
[fallbackPropertyValue] as Object[])
+                        }
+                    }
+                }
+
+                boolean mapBacked = instance instanceof Map
+                Set<String> resolvedProperties = [] as Set<String>
+                ((Map) rawValue).each { key, val ->
+                    String propertyName = key.toString()
+                    PropertyDescriptor property = 
writableProperties[propertyName]
+                    if (property != null) {
+                        Object fallBackPropertyValue = 
getFallBackValue(fallBackValue, propertyName)
+                        Object value = resolveMapValue(property.propertyType, 
"$propertyPathForArg.$propertyName", fallBackPropertyValue, val)
+                        property.writeMethod.invoke(instance, [value] as 
Object[])
+                        resolvedProperties.add(propertyName)
+                        return
+                    }
+                    int nestedPropertySeparator = propertyName.indexOf('.')
+                    if (nestedPropertySeparator > 0) {
+                        String nestedPropertyName = propertyName.substring(0, 
nestedPropertySeparator)
+                        PropertyDescriptor nestedProperty = 
writableProperties[nestedPropertyName]
+                        if (nestedProperty != null) {
+                            if (resolvedProperties.add(nestedPropertyName)) {
+                                Object fallBackPropertyValue = 
getFallBackValue(fallBackValue, nestedPropertyName)
+                                Object value = 
resolveMapValue(nestedProperty.propertyType, 
"$propertyPathForArg.$nestedPropertyName", fallBackPropertyValue, val)
+                                nestedProperty.writeMethod.invoke(instance, 
[value] as Object[])
+                            }
+                            return
+                        }
+                    }
+                    // Types that are themselves a Map (HibernateSettings 
extends LinkedHashMap, for
+                    // example) exist precisely to carry arbitrary keys such 
as hibernate.hbm2ddl.auto,
+                    // so an entry that is not a declared bean property 
belongs in the map rather than
+                    // being rejected. Only types with a fixed set of 
properties reject unknown keys.
+                    if (mapBacked) {
+                        ((Map) instance).put(key, val)
+                        return
+                    }

Review Comment:
   When binding unknown keys into a Map-backed settings type, the code stores 
the original Map key object (`key`) instead of the normalized `String 
propertyName`. If the raw map uses non-String keys (e.g. GString/CharSequence), 
the resulting settings map may not behave as expected (lookups by String can 
fail), despite earlier logic converting keys to String for property matching 
and error messages.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to