jdaugherty commented on code in PR #16586:
URL: https://github.com/apache/grails-core/pull/16586#discussion_r4257623422


##########
grails-core/src/test/groovy/org/grails/spring/context/annotation/ClosureClassIgnoringComponentScanBeanDefinitionParserSpec.groovy:
##########
@@ -0,0 +1,129 @@
+/*
+ *  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
+ *
+ *    https://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.grails.spring.context.annotation
+
+import java.nio.charset.StandardCharsets
+
+import org.springframework.beans.factory.support.DefaultListableBeanFactory
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader
+import org.springframework.context.annotation.ClassPathBeanDefinitionScanner
+import org.springframework.core.io.ByteArrayResource
+import org.springframework.core.type.filter.AssignableTypeFilter
+import spock.lang.Specification
+
+import grails.plugins.GrailsPluginManager
+import org.grails.spring.context.annotation.fixture.ScanFixtureNestedHolder
+import org.grails.spring.context.annotation.fixture.ScanFixtureUnannotated
+
+/**
+ * Exercises the parser the way an application does: through a {@code 
<grails:component-scan>}
+ * element parsed by {@link XmlBeanDefinitionReader}, which resolves the 
Grails namespace via
+ * {@code META-INF/spring.handlers} to {@link GrailsContextNamespaceHandler} 
and from there to this parser.
+ */
+class ClosureClassIgnoringComponentScanBeanDefinitionParserSpec extends 
Specification {
+
+    static final String FIXTURE_PACKAGE = 
'org.grails.spring.context.annotation.fixture'
+
+    static DefaultListableBeanFactory scanWith(DefaultListableBeanFactory 
registry) {
+        String xml = """\
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans";
+       xmlns:grails="http://grails.org/schema/context";>
+    <grails:component-scan base-package="${FIXTURE_PACKAGE}"/>
+</beans>
+"""
+        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry)
+        // the Grails schema is not on the classpath, so parse namespace-aware 
without XSD validation
+        reader.validating = false

Review Comment:
   Not introduced by this PR, but recorded here because the spec has to work 
around it: `META-INF/spring.schemas` maps 
`http://grails.org/schema/context/grails-context.xsd` to 
`org/grails/spring/context/annotation/grails-context.xsd`, and that file lives 
under `src/main/groovy`, which the build does not treat as a resource 
directory. It is missing from the published grails-core jar (checked 
`grails-core-8.1.0-SNAPSHOT.jar`: only `spring.schemas` is there), so an 
XSD-validating parse of `<grails:component-scan>` cannot resolve its schema. 
That is why this spec parses namespace-aware with validation off.
   
   Packaging the XSD (for example `processResources { from('src/main/groovy') { 
include '**/*.xsd' } }`) is a separate fix and worth its own issue.



##########
grails-core/src/main/groovy/org/apache/grails/core/internal/util/TypeConverters.groovy:
##########
@@ -0,0 +1,272 @@
+/*
+ *  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
+ *
+ *    https://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.grails.core.internal.util
+
+import java.text.ParseException
+import java.text.SimpleDateFormat
+
+import groovy.transform.CompileStatic
+
+import grails.util.GrailsStringUtils
+
+/**
+ * Internal helpers that convert a raw {@code Object} value to a target type 
without allocating
+ * an intermediate map. These power the type conversion methods on {@code 
TypeConvertingMap} and
+ * the servlet attribute extensions, keeping the conversion logic in a single 
place.
+ *
+ * <p>This is an internal class and is not part of the public Grails API.
+ *
+ * @since 8.0
+ */
+@CompileStatic
+final class TypeConverters {
+
+    public static final String DEFAULT_DATE_FORMAT = 'yyyy-MM-dd HH:mm:ss.S'
+
+    private TypeConverters() {
+    }
+
+    static Byte toByte(Object o) {
+        if (o instanceof Number) {
+            return ((Number) o).byteValue()
+        }
+        if (o != null) {
+            try {
+                String string = o.toString()
+                if (string != null && string.length() > 0) {
+                    return Byte.parseByte(string)
+                }
+            }
+            catch (NumberFormatException ignored) {}
+        }
+        return null
+    }
+
+    static Character toCharacter(Object o) {
+        if (o instanceof Character) {
+            return (Character) o
+        }
+        if (o != null) {
+            String string = o.toString()
+            if (string != null && string.length() == 1) {
+                return string.charAt(0)
+            }
+        }
+        return null
+    }
+
+    static Integer toInteger(Object o) {
+        if (o instanceof Number) {
+            return ((Number) o).intValue()
+        }
+        if (o != null) {
+            try {
+                String string = o.toString()
+                if (string != null) {
+                    return Integer.parseInt(string)
+                }
+            }
+            catch (NumberFormatException ignored) {}
+        }
+        return null
+    }
+
+    static Long toLong(Object o) {
+        if (o instanceof Number) {
+            return ((Number) o).longValue()
+        }
+        if (o != null) {
+            try {
+                return Long.parseLong(o.toString())
+            }
+            catch (NumberFormatException ignored) {}
+        }
+        return null
+    }
+
+    static Short toShort(Object o) {
+        if (o instanceof Number) {
+            return ((Number) o).shortValue()
+        }
+        if (o != null) {
+            try {
+                String string = o.toString()
+                if (string != null) {
+                    return Short.parseShort(string)
+                }
+            }
+            catch (NumberFormatException ignored) {}
+        }
+        return null
+    }
+
+    static Double toDouble(Object o) {
+        if (o instanceof Number) {
+            return ((Number) o).doubleValue()
+        }
+        if (o != null) {
+            try {
+                String string = o.toString()
+                if (string != null) {
+                    return Double.parseDouble(string)
+                }
+            }
+            catch (NumberFormatException ignored) {}
+        }
+        return null
+    }
+
+    static Float toFloat(Object o) {
+        if (o instanceof Number) {
+            return ((Number) o).floatValue()
+        }
+        if (o != null) {
+            try {
+                String string = o.toString()
+                if (string != null) {
+                    return Float.parseFloat(string)
+                }
+            }
+            catch (NumberFormatException ignored) {}
+        }
+        return null
+    }
+
+    static Boolean toBoolean(Object o) {
+        if (o instanceof Boolean) {
+            return (Boolean) o
+        }
+        if (o != null) {
+            try {
+                String string = o.toString()
+                if (string != null) {
+                    return GrailsStringUtils.toBoolean(string)
+                }
+            }
+            catch (Exception ignored) {}
+        }
+        return null
+    }
+
+    static String toStringValue(Object o) {
+        if (o == null) {
+            return null
+        }
+        if (o instanceof Object[]) {
+            Object[] array = (Object[]) o
+            return array.length > 0 && array[0] != null ? array[0].toString() 
: null
+        }
+        return o.toString()
+    }
+
+    static Date toDate(Object value) {
+        return toDate(value, DEFAULT_DATE_FORMAT)
+    }
+
+    static Date toDate(Object value, String format) {
+        if (value instanceof Date) {
+            return (Date) value
+        }
+        if (value != null) {
+            String string = value.toString()
+            if (string != null) {
+                try {
+                    return new SimpleDateFormat(format).parse(string)
+                } catch (ParseException ignored) {}
+            }
+        }
+        return null
+    }
+
+    static Date toDate(Object value, Collection<String> formats) {
+        for (String format in formats) {
+            Date date = toDate(value, format)
+            if (date != null) return date
+        }
+        return null
+    }
+
+    static List<Object> toList(Object paramValues) {
+        if (paramValues == null) {
+            return Collections.emptyList()
+        }
+        if (paramValues.getClass().isArray()) {
+            return Arrays.asList((Object[]) paramValues)

Review Comment:
   `(Object[]) paramValues` behaves differently here than it did in Java. The 
static type of `paramValues` is `Object`, so Groovy routes the cast through its 
coercion rules rather than a plain `checkcast`: a primitive array such as 
`int[]` is boxed element by element into an `Object[]` and returned as a list, 
where the Java version threw `ClassCastException`. Verified with Groovy 5.1.3 
and 6.0.0: `toList(new int[] {1, 2})` returns `[1, 2]`.
   
   The more lenient result seems fine for a parameter-value helper, so no 
change requested; this is just to make the divergence a deliberate one. If the 
Java behaviour should be kept exactly, `Object[].class.cast(paramValues)` 
restores the `ClassCastException`.



-- 
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