This is an automated email from the ASF dual-hosted git repository.

daniellansun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 121c8605f7 GROOVY-12030: Graduate PropertyHandler from incubating to 
stable
121c8605f7 is described below

commit 121c8605f7994fcefb4f13f21b5a4a46b95ee84a
Author: Paul King <[email protected]>
AuthorDate: Sat May 23 09:45:44 2026 +1000

    GROOVY-12030: Graduate PropertyHandler from incubating to stable
---
 .../transform/options/DefaultPropertyHandler.java  |   4 +-
 .../groovy/transform/options/PropertyHandler.java  |  45 +++++--
 .../transform/options/PropertyHandlerTest.groovy   | 133 +++++++++++++++++++++
 3 files changed, 168 insertions(+), 14 deletions(-)

diff --git a/src/main/java/groovy/transform/options/DefaultPropertyHandler.java 
b/src/main/java/groovy/transform/options/DefaultPropertyHandler.java
index 433d6159e9..6d60861d68 100644
--- a/src/main/java/groovy/transform/options/DefaultPropertyHandler.java
+++ b/src/main/java/groovy/transform/options/DefaultPropertyHandler.java
@@ -62,9 +62,7 @@ public class DefaultPropertyHandler extends PropertyHandler {
      */
     @Override
     public boolean validateAttributes(final AbstractASTTransformation xform, 
final AnnotationNode anno) {
-        boolean success = true;
-      //success |= isValidAttribute(xform, anno, "");
-        return success;
+        return true;
     }
 
     /**
diff --git a/src/main/java/groovy/transform/options/PropertyHandler.java 
b/src/main/java/groovy/transform/options/PropertyHandler.java
index 6fe7d4362f..b50e3a98be 100644
--- a/src/main/java/groovy/transform/options/PropertyHandler.java
+++ b/src/main/java/groovy/transform/options/PropertyHandler.java
@@ -20,7 +20,6 @@ package groovy.transform.options;
 
 import groovy.lang.GroovyClassLoader;
 import groovy.transform.PropertyOptions;
-import org.apache.groovy.lang.annotation.Incubating;
 import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
@@ -37,10 +36,21 @@ import static 
org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
 
 /**
  * Used to provide custom property handling when getting, setting or 
initializing properties.
+ * <p>
+ * Subclasses are plugged in via the {@code propertyHandler} attribute on the
+ * {@link PropertyOptions} annotation. Implementations must declare a public 
no-argument
+ * constructor; the handler is instantiated via {@link #createPropertyHandler} 
using
+ * reflection at compile time.
+ * <p>
+ * During each transform, methods are invoked in a fixed order:
+ * {@link #validateAttributes} first; if it returns {@code true}, then
+ * {@link #validateProperties}; if that also returns {@code true}, then
+ * {@link #createPropInit}, {@link #createPropGetter}, and {@link 
#createPropSetter}
+ * are called per property as required by the host transform. Returning {@code 
false}
+ * from a validation method short-circuits processing.
  *
  * @since 2.5.0
  */
-@Incubating
 public abstract class PropertyHandler {
     private static final Class<? extends Annotation> PROPERTY_OPTIONS_CLASS = 
PropertyOptions.class;
 
@@ -72,15 +82,18 @@ public abstract class PropertyHandler {
     }
 
     /**
-     * Create a statement that will initialize the property including any 
defensive copying. Null if no statement should be added.
+     * Create a statement that will initialize the property including any 
defensive copying.
+     * Return {@code null} to indicate that no initialization statement should 
be emitted
+     * for this property; this is distinct from returning an empty block and 
skips the
+     * corresponding entry in the generated constructor body altogether.
      *
      * @param xform the transform being processed
-     * @param anno the '@ImmutableBase' annotation node
+     * @param anno the annotation node
      * @param cNode the classnode containing the property
      * @param pNode the property node to initialize
-     * @param namedArgMap an "args" Map if the property value should come from 
a named arg map or null if not
+     * @param namedArgsMap an "args" Map if the property value should come 
from a named arg map or null if not
      */
-    public abstract Statement createPropInit(AbstractASTTransformation xform, 
AnnotationNode anno, ClassNode cNode, PropertyNode pNode, Parameter 
namedArgMap);
+    public abstract Statement createPropInit(AbstractASTTransformation xform, 
AnnotationNode anno, ClassNode cNode, PropertyNode pNode, Parameter 
namedArgsMap);
 
     /**
      * Create the getter block used when reading the property including any 
defensive copying.
@@ -101,15 +114,20 @@ public abstract class PropertyHandler {
     }
 
     /**
-     * Checks whether the named annotation attribute is unsupported for this 
handler.
+     * Helper for use from {@link #validateAttributes}: confirms that the 
named attribute is
+     * <em>absent</em> from the host annotation. Despite the positive name, 
this returns
+     * {@code false} <em>and</em> records a compile error when the attribute 
is set, so
+     * subclasses can chain calls in the style {@code return 
isValidAttribute(xform, anno, "useSuper");}
+     * to reject attributes that the host transform supports but this handler 
does not.
      *
      * @param xform the active transform
      * @param anno the annotation being processed
-     * @param memberName the attribute name to validate
-     * @return {@code true} if the attribute is valid for this handler
+     * @param memberName the attribute name expected to be absent
+     * @return {@code true} if the attribute is absent (i.e. valid for this 
handler);
+     *         {@code false} if present (a compile error is added as a side 
effect)
      */
     protected boolean isValidAttribute(final AbstractASTTransformation xform, 
final AnnotationNode anno, final String memberName) {
-        if (xform.getMemberValue(anno, memberName) != null) {
+        if (anno.getMember(memberName) != null) {
             xform.addError("Error during " + xform.getAnnotationName() + " 
processing: Annotation attribute '" + memberName +
                     "' not supported for property handler " + 
getClass().getSimpleName(), anno);
             return false;
@@ -118,7 +136,12 @@ public abstract class PropertyHandler {
     }
 
     /**
-     * Creates the property handler configured for the supplied class.
+     * Creates the property handler configured for the supplied class. If the 
class carries
+     * a {@link PropertyOptions} annotation, the {@code propertyHandler} 
attribute is read
+     * and the named handler class is instantiated via its public no-argument 
constructor;
+     * otherwise a {@link DefaultPropertyHandler} is returned. A compile error 
is recorded
+     * (and {@code null} returned) if the handler cannot be loaded, lacks a 
no-arg
+     * constructor, or is not a {@code PropertyHandler} subtype.
      *
      * @param xform the active transform
      * @param loader the class loader used to instantiate custom handlers
diff --git 
a/src/test/groovy/groovy/transform/options/PropertyHandlerTest.groovy 
b/src/test/groovy/groovy/transform/options/PropertyHandlerTest.groovy
new file mode 100644
index 0000000000..eeda442da0
--- /dev/null
+++ b/src/test/groovy/groovy/transform/options/PropertyHandlerTest.groovy
@@ -0,0 +1,133 @@
+/*
+ *  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 groovy.transform.options
+
+import org.codehaus.groovy.ast.AnnotationNode
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.Parameter
+import org.codehaus.groovy.ast.PropertyNode
+import org.codehaus.groovy.ast.stmt.Statement
+import org.codehaus.groovy.transform.AbstractASTTransformation
+import org.junit.jupiter.api.Test
+
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.plusX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX
+
+/**
+ * Locks in the public contract of {@link PropertyHandler}: that user-supplied
+ * handlers wired in via {@code @PropertyOptions(propertyHandler = …)} are
+ * loaded, validated and consulted at compile time.
+ */
+final class PropertyHandlerTest {
+
+    @Test
+    void testCustomHandlerControlsPropertyInitialisation() {
+        assertScript '''
+            import groovy.transform.PropertyOptions
+            import groovy.transform.TupleConstructor
+            import 
groovy.transform.options.PropertyHandlerTest.PrefixingPropertyHandler
+
+            @PropertyOptions(propertyHandler = PrefixingPropertyHandler)
+            @TupleConstructor
+            class Foo {
+                String name
+            }
+
+            assert new Foo('hello').name == 'X_hello'
+        '''
+    }
+
+    @Test
+    void testHandlerValidateAttributesCanRejectAnAttribute() {
+        // Exercises a list-valued attribute (`includes = [...]`)
+        def err = shouldFail '''
+            import groovy.transform.PropertyOptions
+            import groovy.transform.TupleConstructor
+            import 
groovy.transform.options.PropertyHandlerTest.RejectsIncludesPropertyHandler
+
+            @PropertyOptions(propertyHandler = RejectsIncludesPropertyHandler)
+            @TupleConstructor(includes = ['name'])
+            class Bar {
+                String name
+                String tag
+            }
+        '''
+        assert err.message.contains("Annotation attribute 'includes' not 
supported for property handler RejectsIncludesPropertyHandler")
+    }
+
+    @Test
+    void testNoPropertyOptionsFallsBackToDefaultPropertyHandler() {
+        assertScript '''
+            import groovy.transform.TupleConstructor
+
+            @TupleConstructor
+            class Baz {
+                String name
+            }
+
+            // unchanged value confirms DefaultPropertyHandler was used, not 
PrefixingPropertyHandler
+            assert new Baz('plain').name == 'plain'
+        '''
+    }
+
+    /**
+     * Custom handler that rewrites every property initialisation to prefix the
+     * incoming constructor argument with {@code 'X_'}, demonstrating that
+     * {@link PropertyHandler#createPropInit} fully controls the emitted code.
+     */
+    static class PrefixingPropertyHandler extends PropertyHandler {
+        @Override
+        boolean validateAttributes(AbstractASTTransformation xform, 
AnnotationNode anno) {
+            true
+        }
+
+        @Override
+        Statement createPropInit(AbstractASTTransformation xform, 
AnnotationNode anno,
+                                 ClassNode cNode, PropertyNode pNode, 
Parameter namedArgsMap) {
+            def name = pNode.name
+            // Generated: this.<name> = 'X_' + <name>
+            return assignS(propX(varX('this'), name), plusX(constX('X_'), 
varX(name)))
+        }
+    }
+
+    /**
+     * Custom handler that demonstrates rejecting a host-transform attribute 
via
+     * {@link PropertyHandler#isValidAttribute}; combining this handler with 
the
+     * {@code includes} attribute on {@code @TupleConstructor} produces a 
compile
+     * error rather than silently ignoring the attribute. The list-valued shape
+     * also exercises that detection is not restricted to scalar constant 
attributes.
+     */
+    static class RejectsIncludesPropertyHandler extends PropertyHandler {
+        @Override
+        boolean validateAttributes(AbstractASTTransformation xform, 
AnnotationNode anno) {
+            isValidAttribute(xform, anno, 'includes')
+        }
+
+        @Override
+        Statement createPropInit(AbstractASTTransformation xform, 
AnnotationNode anno,
+                                 ClassNode cNode, PropertyNode pNode, 
Parameter namedArgsMap) {
+            null // never reached: validateAttributes returns false above
+        }
+    }
+}

Reply via email to