You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by pa...@apache.org on 2015/05/18 13:24:59 UTC
incubator-groovy git commit: GROOVY-7427: TupleConstructor should
allow a mechanism to switch off automatic parameter defaults
Repository: incubator-groovy
Updated Branches:
refs/heads/master 6d81d3e2f -> 02f918286
GROOVY-7427: TupleConstructor should allow a mechanism to switch off automatic parameter defaults
Project: http://git-wip-us.apache.org/repos/asf/incubator-groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-groovy/commit/02f91828
Tree: http://git-wip-us.apache.org/repos/asf/incubator-groovy/tree/02f91828
Diff: http://git-wip-us.apache.org/repos/asf/incubator-groovy/diff/02f91828
Branch: refs/heads/master
Commit: 02f918286016051cacf11a91c841960abfa56cbb
Parents: 6d81d3e
Author: Paul King <pa...@asert.com.au>
Authored: Mon May 18 21:24:45 2015 +1000
Committer: Paul King <pa...@asert.com.au>
Committed: Mon May 18 21:24:45 2015 +1000
----------------------------------------------------------------------
src/main/groovy/transform/TupleConstructor.java | 19 +++++++++++
.../TupleConstructorASTTransformation.java | 31 +++++++++++------
src/spec/doc/core-metaprogramming.adoc | 36 +++++++++++++++-----
.../test/CodeGenerationASTTransformsTest.groovy | 29 ++++++++++++++++
.../CanonicalComponentsTransformTest.groovy | 16 +++++++++
5 files changed, 113 insertions(+), 18 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/02f91828/src/main/groovy/transform/TupleConstructor.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/transform/TupleConstructor.java b/src/main/groovy/transform/TupleConstructor.java
index bc084c7..b809792 100644
--- a/src/main/groovy/transform/TupleConstructor.java
+++ b/src/main/groovy/transform/TupleConstructor.java
@@ -116,6 +116,25 @@ public @interface TupleConstructor {
boolean force() default false;
/**
+ * Used to set whether default value processing is enabled (the default) or disabled.
+ *
+ * By default, every constructor parameter is given a default value. This value will
+ * be Java's default for primitive types (zero or false) and null for Objects, unless
+ * an initial value is given when declaring the property or field. A consequence of
+ * this design is that you can leave off parameters from the right if the default
+ * value will suffice. As far as Java interoperability is concerned, Groovy will
+ * create additional constructors under the covers representing the constructors
+ * with parameters left off, all the way from the constructor with all arguments
+ * to the no-arg constructor.
+ *
+ * However, when set to false, default values are not allowed for properties and fields.
+ * Only the constructor containing all arguments will be provided.
+ * In particular, a no-arg constructor won't be provided and since this is currently
+ * used by Groovy when using named-arguments, the named-argument style won't be available.
+ */
+ boolean defaults() default true;
+
+ /**
* By default, properties are set directly using their respective field.
* By setting {@code useSetters=true} then a writable property will be set using its setter.
* If turning on this flag we recommend that setters that might be called are
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/02f91828/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java b/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
index 23cc2a8..c94d361 100644
--- a/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
+++ b/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
@@ -67,8 +67,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
/**
* Handles generation of code for the @TupleConstructor annotation.
- *
- * @author Paul King
*/
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class TupleConstructorASTTransformation extends AbstractASTTransformation {
@@ -110,6 +108,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
boolean includeSuperProperties = memberHasValue(anno, "includeSuperProperties", true);
boolean callSuper = memberHasValue(anno, "callSuper", true);
boolean force = memberHasValue(anno, "force", true);
+ boolean defaults = !memberHasValue(anno, "defaults", false);
boolean useSetters = memberHasValue(anno, "useSetters", true);
List<String> excludes = getMemberList(anno, "excludes");
List<String> includes = getMemberList(anno, "includes");
@@ -121,11 +120,15 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
if (!checkIncludeExclude(anno, excludes, includes, MY_TYPE_NAME)) return;
// if @Immutable is found, let it pick up options and do work so we'll skip
if (hasAnnotation(cNode, ImmutableASTTransformation.MY_TYPE)) return;
- createConstructor(cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters);
+ createConstructor(this, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, defaults);
}
}
public static void createConstructor(ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties, boolean callSuper, boolean force, List<String> excludes, List<String> includes, boolean useSetters) {
+ createConstructor(null, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, true);
+ }
+
+ public static void createConstructor(AbstractASTTransformation xform, ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties, boolean callSuper, boolean force, List<String> excludes, List<String> includes, boolean useSetters, boolean defaults) {
// no processing if existing constructors found
List<ConstructorNode> constructors = cNode.getDeclaredConstructors();
if (constructors.size() > 1 && !force) return;
@@ -156,7 +159,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
for (FieldNode fNode : superList) {
String name = fNode.getName();
if (shouldSkip(name, excludes, includes)) continue;
- params.add(createParam(fNode, name));
+ params.add(createParam(fNode, name, defaults, xform));
boolean hasSetter = cNode.getProperty(name) != null && !fNode.isFinal();
if (callSuper) {
superParams.add(varX(name));
@@ -174,7 +177,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
for (FieldNode fNode : list) {
String name = fNode.getName();
if (shouldSkip(name, excludes, includes)) continue;
- Parameter nextParam = createParam(fNode, name);
+ Parameter nextParam = createParam(fNode, name, defaults, xform);
params.add(nextParam);
boolean hasSetter = cNode.getProperty(name) != null && !fNode.isFinal();
if (useSetters && hasSetter) {
@@ -186,16 +189,17 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, params.toArray(new Parameter[params.size()]), ClassNode.EMPTY_ARRAY, body));
// add map constructor if needed, don't do it for LinkedHashMap for now (would lead to duplicate signature)
// or if there is only one Map property (for backwards compatibility)
- if (params.size() > 0) {
+ if (params.size() > 0 && defaults) {
ClassNode firstParam = params.get(0).getType();
if (params.size() > 1 || firstParam.equals(ClassHelper.OBJECT_TYPE)) {
+ String message = "The class " + cNode.getName() + " was incorrectly initialized via the map constructor with null.";
if (firstParam.equals(ClassHelper.MAP_TYPE)) {
- addMapConstructors(cNode, true, "The class " + cNode.getName() + " was incorrectly initialized via the map constructor with null.");
+ addMapConstructors(cNode, true, message);
} else {
ClassNode candidate = HMAP_TYPE;
while (candidate != null) {
if (candidate.equals(firstParam)) {
- addMapConstructors(cNode, true, "The class " + cNode.getName() + " was incorrectly initialized via the map constructor with null.");
+ addMapConstructors(cNode, true, message);
break;
}
candidate = candidate.getSuperClass();
@@ -209,9 +213,16 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
return "set" + Verifier.capitalize(name);
}
- private static Parameter createParam(FieldNode fNode, String name) {
+ private static Parameter createParam(FieldNode fNode, String name, boolean defaults, AbstractASTTransformation xform) {
Parameter param = new Parameter(fNode.getType(), name);
- param.setInitialExpression(providedOrDefaultInitialValue(fNode));
+ if (defaults){
+ param.setInitialExpression(providedOrDefaultInitialValue(fNode));
+ } else {
+ if (fNode.getInitialExpression() != null) {
+ xform.addError("Error during " + MY_TYPE_NAME + " processing, default value processing disabled but default value found for '" + fNode.getName() + "'", fNode);
+ }
+
+ }
return param;
}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/02f91828/src/spec/doc/core-metaprogramming.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/core-metaprogramming.adoc b/src/spec/doc/core-metaprogramming.adoc
index 4ad3878..d05fca8 100644
--- a/src/spec/doc/core-metaprogramming.adoc
+++ b/src/spec/doc/core-metaprogramming.adoc
@@ -854,7 +854,11 @@ include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=
===== @groovy.transform.TupleConstructor
The `@TupleConstructor` annotation aims at eliminating boilerplate code by generating constructors for you. A tuple
-constructor is created for each property, with default values (using the Java default values). For example, the
+constructor is created having a parameter for each property (and possibly fields). Each parameter has a default value
+(using the initial value of the property if present or otherwise Java's default value according to the properties type).
+In later compilation phases, the Groovy compiler's standard default value processing behavior is then applied.
+The end result is that multiple constructors are placed within the bytecode of your class. This provides a
+well understood semantics and is also useful for Java integration purposes. As an example, the
following code will generate 3 constructors:
[source,groovy]
@@ -862,12 +866,22 @@ following code will generate 3 constructors:
include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_simple,indent=0]
----
-The first constructor is a no-arg constructor which allows the traditional map-style construction. It is worth noting
-that if the first property (or field) has type LinkedHashMap or if there is a single Map, AbstractMap or HashMap
-property (or field), then the map-style mapping is not available.
+The first constructor is a no-arg constructor which allows the traditional map-style construction so long as
+you don't have final properties. Groovy calls the no-arg constructor and then the relevant setters under the covers.
+It is worth noting that if the first property (or field) has type LinkedHashMap or if there is a single Map,
+AbstractMap or HashMap property (or field), then the map-style named arguments won't be available.
The other constructors are generated by taking the properties in the order they are defined. Groovy will generate as
-many constructors as they are properties (or fields, depending on the options).
+many constructors as there are properties (or fields, depending on the options).
+
+Setting the `defaults` attribute (see the available configuration options table) to `false`, disables the normal default values behavior which means:
+
+* Exactly one constructor will be produced
+* Attempting to use an initial value will produce an error
+* Map-style named arguments won't be available
+
+This attribute is normally only used in situations where another Java framework is
+expecting exactly one constructor, e.g. injection frameworks, JUnit parameterized runners.
The `@TupleConstructor` AST transformation accepts several configuration options:
@@ -909,20 +923,26 @@ include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=
----
include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_callSuper,indent=0]
----
-|force|False|By default, the transformation will do nothing if a constructor is already defined. Setting this property to
+|force|False|By default, the transformation will do nothing if a constructor is already defined. Setting this attribute to
true, the constructor will be generated and it's your responsibility to ensure that no duplicate constructor is defined.|
[source,groovy]
----
include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_force,indent=0]
----
-|useSetters|False|By default, the transformation will include code which directly sets the backing field for each property
-from its corresponding constructor parameter. Setting this property to true, the constructor will instead call setters if
+|useSetters|False|By default, the transformation will directly set the backing field of each property
+from its corresponding constructor parameter. Setting this attribute to true, the constructor will instead call setters if
they exist. It's usually deemed bad style from within a constructor to call setters that can be overridden. It's your
responsibility to avoid such bad style.|
[source,groovy]
----
include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_useSetters,indent=0]
----
+|defaults|True|Indicates that default value processing is enabled for constructor parameters.
+Set to false to obtain exactly one constructor but with initial value support and named-arguments disabled. |
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_defaults_false,indent=0]
+----
|=======================================================================
[[xform-Canonical]]
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/02f91828/src/spec/test/CodeGenerationASTTransformsTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/CodeGenerationASTTransformsTest.groovy b/src/spec/test/CodeGenerationASTTransformsTest.groovy
index 9a75078..5af1bb3 100644
--- a/src/spec/test/CodeGenerationASTTransformsTest.groovy
+++ b/src/spec/test/CodeGenerationASTTransformsTest.groovy
@@ -550,6 +550,35 @@ assert new Foo('cat').toString() == 'Foo(CAT)'
assert new Foo(bar: 'cat').toString() == 'Foo(CAT)'
// end::tupleconstructor_example_useSetters[]
'''
+
+ assertScript '''
+// default/control
+import groovy.transform.*
+
+@ToString
+@TupleConstructor
+class Athlete {
+ String name
+ String sport
+ int age
+}
+
+assert new Athlete('Roger', 'Tennis', 33).toString() == 'Athlete(Roger, Tennis, 33)'
+assert Athlete.constructors.size() == 4
+
+// tag::tupleconstructor_example_defaults_false[]
+@ToString
+@TupleConstructor(defaults=false)
+class Musician {
+ String name
+ String instrument
+ int born
+}
+
+assert new Musician('Jimi', 'Guitar', 1942).toString() == 'Musician(Jimi, Guitar, 1942)'
+assert Musician.constructors.size() == 1
+// end::tupleconstructor_example_defaults_false[]
+'''
}
void testCanonical() {
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/02f91828/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy b/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
index 6e6e1b7..2e644d9 100644
--- a/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
@@ -547,6 +547,22 @@ class CanonicalComponentsTransformTest extends GroovyShellTestCase {
"""
}
+ void testTupleConstructorNoDefaultParameterValues_GROOVY7427() {
+ new GroovyShell().evaluate """
+ // checks special Map behavior isn't added if defaults=false
+ import groovy.transform.*
+
+ @ToString
+ @TupleConstructor(defaults=false)
+ class Person {
+ def name
+ }
+
+ assert new Person('John Smith').toString() == 'Person(John Smith)'
+ assert Person.constructors.size() == 1
+ """
+ }
+
void testNullCloneableField_GROOVY7091() {
new GroovyShell().evaluate """
import groovy.transform.AutoClone