You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2016/05/20 10:28:34 UTC
groovy git commit: GROOVY-7837: TupleConstructor should provide pre
and post annotation attributes like MapConstructor
Repository: groovy
Updated Branches:
refs/heads/master 872923f8e -> 7da8b58ca
GROOVY-7837: TupleConstructor should provide pre and post annotation attributes like MapConstructor
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/7da8b58c
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/7da8b58c
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/7da8b58c
Branch: refs/heads/master
Commit: 7da8b58cab2ab070abdd7a8ed7cf6ce4bc989db2
Parents: 872923f
Author: paulk <pa...@asert.com.au>
Authored: Wed May 11 22:04:45 2016 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Fri May 20 20:27:51 2016 +1000
----------------------------------------------------------------------
src/main/groovy/transform/TupleConstructor.java | 36 +++++++---
.../codehaus/groovy/ast/tools/GeneralUtils.java | 31 ++++++++
.../MapConstructorASTTransformation.java | 74 ++++++--------------
.../TupleConstructorASTTransformation.java | 61 ++++++++++++++--
src/spec/doc/core-metaprogramming.adoc | 10 +++
.../test/CodeGenerationASTTransformsTest.groovy | 32 +++++++++
.../TupleConstructorTransformTest.groovy | 37 ++++++++++
7 files changed, 212 insertions(+), 69 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/groovy/blob/7da8b58c/src/main/groovy/transform/TupleConstructor.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/transform/TupleConstructor.java b/src/main/groovy/transform/TupleConstructor.java
index 9ea30df..1a3cca5 100644
--- a/src/main/groovy/transform/TupleConstructor.java
+++ b/src/main/groovy/transform/TupleConstructor.java
@@ -213,8 +213,9 @@ public @interface TupleConstructor {
boolean includeSuperProperties() default false;
/**
- * Should super properties be called within a call to the parent constructor.
- * rather than set as properties
+ * Should super properties be called within a call to the parent constructor
+ * rather than set as properties. Typically used in combination with {@code includeSuperProperties}.
+ * Can't be true if using {@code pre} with a {@code super} first statement.
*/
boolean callSuper() default false;
@@ -251,12 +252,27 @@ public @interface TupleConstructor {
* made null-safe wrt the parameter.
*/
boolean useSetters() default false;
-
- /**
- * Whether to include all fields and/or properties within the constructor, including those with names that are
- * considered internal.
- *
- * @since 2.5.0
- */
- boolean allNames() default false;
+
+ /**
+ * Whether to include all fields and/or properties within the constructor, including those with names that are
+ * considered internal.
+ *
+ * @since 2.5.0
+ */
+ boolean allNames() default false;
+
+ /**
+ * A Closure containing statements which will be prepended to the generated constructor. The first statement
+ * within the Closure may be {@code super(someArgs)} in which case the no-arg super constructor won't be called.
+ *
+ * @since 2.5.0
+ */
+ Class pre();
+
+ /**
+ * A Closure containing statements which will be appended to the end of the generated constructor. Useful for validation steps or tweaking the populated fields/properties.
+ *
+ * @since 2.5.0
+ */
+ Class post();
}
http://git-wip-us.apache.org/repos/asf/groovy/blob/7da8b58c/src/main/org/codehaus/groovy/ast/tools/GeneralUtils.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/ast/tools/GeneralUtils.java b/src/main/org/codehaus/groovy/ast/tools/GeneralUtils.java
index 7fe279b..2d59447 100644
--- a/src/main/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -736,4 +736,35 @@ public class GeneralUtils {
return source;
}
+
+ public static boolean copyStatementsWithSuperAdjustment(ClosureExpression pre, BlockStatement body) {
+ Statement preCode = pre.getCode();
+ boolean changed = false;
+ if (preCode instanceof BlockStatement) {
+ BlockStatement block = (BlockStatement) preCode;
+ List<Statement> statements = block.getStatements();
+ for (int i = 0; i < statements.size(); i++) {
+ Statement statement = statements.get(i);
+ // adjust the first statement if it's a super call
+ if (i == 0 && statement instanceof ExpressionStatement) {
+ ExpressionStatement es = (ExpressionStatement) statement;
+ Expression preExp = es.getExpression();
+ if (preExp instanceof MethodCallExpression) {
+ MethodCallExpression mce = (MethodCallExpression) preExp;
+ String name = mce.getMethodAsString();
+ if ("super".equals(name)) {
+ es.setExpression(new ConstructorCallExpression(ClassNode.SUPER, mce.getArguments()));
+ changed = true;
+ }
+ }
+ }
+ body.addStatement(statement);
+ }
+ }
+ return changed;
+ }
+
+ public static String getSetterName(String name) {
+ return "set" + Verifier.capitalize(name);
+ }
}
http://git-wip-us.apache.org/repos/asf/groovy/blob/7da8b58c/src/main/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/MapConstructorASTTransformation.java b/src/main/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
index ec7865c..69c85f2 100644
--- a/src/main/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
+++ b/src/main/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
@@ -30,15 +30,10 @@ import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
-import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
-import org.codehaus.groovy.ast.stmt.ExpressionStatement;
-import org.codehaus.groovy.ast.stmt.Statement;
-import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
@@ -53,9 +48,11 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.copyStatementsWithSuperAdjustment;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstanceNonPropertyFields;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstancePropertyFields;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSuperPropertyFields;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
@@ -139,10 +136,10 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
Parameter map = param(MAP_TYPE, "args");
final BlockStatement body = new BlockStatement();
- ClassCodeExpressionTransformer transformer = makeTransformer();
+ ClassCodeExpressionTransformer transformer = makeMapTypedArgsTransformer();
if (pre != null) {
ClosureExpression transformed = (ClosureExpression) transformer.transform(pre);
- copyPreStatements(transformed, body);
+ copyStatementsWithSuperAdjustment(transformed, body);
}
for (FieldNode fNode : superList) {
String name = fNode.getName();
@@ -168,56 +165,29 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
assignS(propX(varX("this"), name), callX(varX(map), "get", nameArg))));
}
- private static String getSetterName(String name) {
- return "set" + Verifier.capitalize(name);
- }
-
- private static ClassCodeExpressionTransformer makeTransformer() {
+ private static ClassCodeExpressionTransformer makeMapTypedArgsTransformer() {
return new ClassCodeExpressionTransformer() {
- @Override
- public Expression transform(Expression exp) {
- if (exp instanceof ClosureExpression) {
- ClosureExpression ce = (ClosureExpression) exp;
- ce.getCode().visit(this);
- } else if (exp instanceof VariableExpression) {
- VariableExpression ve = (VariableExpression) exp;
- if (ve.getName().equals("args") && ve.getAccessedVariable() instanceof DynamicVariable) {
- VariableExpression newVe = new VariableExpression(new Parameter(MAP_TYPE, "args"));
- newVe.setSourcePosition(ve);
- return newVe;
- }
+ @Override
+ public Expression transform(Expression exp) {
+ if (exp instanceof ClosureExpression) {
+ ClosureExpression ce = (ClosureExpression) exp;
+ ce.getCode().visit(this);
+ } else if (exp instanceof VariableExpression) {
+ VariableExpression ve = (VariableExpression) exp;
+ if (ve.getName().equals("args") && ve.getAccessedVariable() instanceof DynamicVariable) {
+ VariableExpression newVe = new VariableExpression(new Parameter(MAP_TYPE, "args"));
+ newVe.setSourcePosition(ve);
+ return newVe;
}
- return exp.transformExpression(this);
}
+ return exp.transformExpression(this);
+ }
- @Override
- protected SourceUnit getSourceUnit() {
- return null;
- }
- };
- }
-
- private static void copyPreStatements(ClosureExpression pre, BlockStatement body) {
- Statement preCode = pre.getCode();
- if (preCode instanceof BlockStatement) {
- BlockStatement block = (BlockStatement) preCode;
- List<Statement> statements = block.getStatements();
- for (int i = 0; i < statements.size(); i++) {
- Statement statement = statements.get(i);
- if (i == 0 && statement instanceof ExpressionStatement) {
- ExpressionStatement es = (ExpressionStatement) statement;
- Expression preExp = es.getExpression();
- if (preExp instanceof MethodCallExpression) {
- MethodCallExpression mce = (MethodCallExpression) preExp;
- String name = mce.getMethodAsString();
- if ("super".equals(name)) {
- es.setExpression(new ConstructorCallExpression(ClassNode.SUPER, mce.getArguments()));
- }
- }
- }
- body.addStatement(statement);
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
}
- }
+ };
}
}
http://git-wip-us.apache.org/repos/asf/groovy/blob/7da8b58c/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 b31cc8e..f2d0031 100644
--- a/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
+++ b/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
@@ -28,12 +28,14 @@ import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.Statement;
-import org.codehaus.groovy.classgen.Verifier;
+import org.codehaus.groovy.classgen.VariableScopeVisitor;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
@@ -51,10 +53,12 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.copyStatementsWithSuperAdjustment;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.equalsNullX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstanceNonPropertyFields;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstancePropertyFields;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSuperNonPropertyFields;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSuperPropertyFields;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
@@ -118,7 +122,25 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields)) 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(this, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, defaults, allNames);
+ Expression pre = anno.getMember("pre");
+ if (pre != null && !(pre instanceof ClosureExpression)) {
+ addError("Expected closure value for annotation parameter 'pre'. Found " + pre, cNode);
+ return;
+ }
+ Expression post = anno.getMember("post");
+ if (post != null && !(post instanceof ClosureExpression)) {
+ addError("Expected closure value for annotation parameter 'post'. Found " + post, cNode);
+ return;
+ }
+ createConstructor(this, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties,
+ callSuper, force, excludes, includes, useSetters, defaults, allNames, sourceUnit,
+ (ClosureExpression) pre, (ClosureExpression) post);
+ if (pre != null) {
+ anno.setMember("pre", new ClosureExpression(new Parameter[0], new EmptyStatement()));
+ }
+ if (post != null) {
+ anno.setMember("post", new ClosureExpression(new Parameter[0], new EmptyStatement()));
+ }
}
}
@@ -131,6 +153,16 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
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, boolean allNames) {
+ createConstructor(xform, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties,
+ callSuper, force, excludes, includes, useSetters, defaults, false, null, null, null);
+ }
+
+ 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, boolean allNames, SourceUnit sourceUnit, ClosureExpression
+ pre, ClosureExpression post) {
// no processing if existing constructors found
if (!cNode.getDeclaredConstructors().isEmpty() && !force) return;
@@ -152,6 +184,15 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
final List<Parameter> params = new ArrayList<Parameter>();
final List<Expression> superParams = new ArrayList<Expression>();
+ final BlockStatement preBody = new BlockStatement();
+ boolean superInPre = false;
+ if (pre != null) {
+ superInPre = copyStatementsWithSuperAdjustment(pre, preBody);
+ if (superInPre && callSuper) {
+ xform.addError("Error during " + MY_TYPE_NAME + " processing, can't have a super call in 'pre' " +
+ "closure and also 'callSuper' enabled", cNode);
+ }
+ }
final BlockStatement body = new BlockStatement();
for (FieldNode fNode : superList) {
String name = fNode.getName();
@@ -160,7 +201,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
boolean hasSetter = cNode.getProperty(name) != null && !fNode.isFinal();
if (callSuper) {
superParams.add(varX(name));
- } else {
+ } else if (!superInPre) {
if (useSetters && hasSetter) {
body.addStatement(stmt(callThisX(getSetterName(name), varX(name))));
} else {
@@ -171,6 +212,9 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
if (callSuper) {
body.addStatement(stmt(ctorX(ClassNode.SUPER, args(superParams))));
}
+ if (!preBody.isEmpty()) {
+ body.addStatements(preBody.getStatements());
+ }
for (FieldNode fNode : list) {
String name = fNode.getName();
if (shouldSkipUndefinedAware(name, excludes, includes, allNames)) continue;
@@ -183,7 +227,14 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
body.addStatement(assignS(propX(varX("this"), name), varX(nextParam)));
}
}
+ if (post != null) {
+ body.addStatement(post.getCode());
+ }
cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, params.toArray(new Parameter[params.size()]), ClassNode.EMPTY_ARRAY, body));
+ if (sourceUnit != null && !body.isEmpty()) {
+ VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(sourceUnit);
+ scopeVisitor.visitClass(cNode);
+ }
// 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.isEmpty() && defaults) {
@@ -206,10 +257,6 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
}
- private static String getSetterName(String name) {
- return "set" + Verifier.capitalize(name);
- }
-
private static Parameter createParam(FieldNode fNode, String name, boolean defaults, AbstractASTTransformation xform) {
Parameter param = new Parameter(fNode.getType(), name);
if (defaults) {
http://git-wip-us.apache.org/repos/asf/groovy/blob/7da8b58c/src/spec/doc/core-metaprogramming.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/core-metaprogramming.adoc b/src/spec/doc/core-metaprogramming.adoc
index c9ace20..2e0e47b 100644
--- a/src/spec/doc/core-metaprogramming.adoc
+++ b/src/spec/doc/core-metaprogramming.adoc
@@ -980,6 +980,16 @@ include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=
----
include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_allNames,indent=0]
----
+|pre|empty|A closure containing statements to be inserted at the start of the generated constructor(s)|
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_pre,indent=0]
+----
+|post|empty|A closure containing statements to be inserted at the end of the generated constructor(s)|
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_post,indent=0]
+----
|=======================================================================
Setting the `defaults` annotation attribute to `false` and the `force` annotation attribute to `true` allows
http://git-wip-us.apache.org/repos/asf/groovy/blob/7da8b58c/src/spec/test/CodeGenerationASTTransformsTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/CodeGenerationASTTransformsTest.groovy b/src/spec/test/CodeGenerationASTTransformsTest.groovy
index 5131f24..0c70516 100644
--- a/src/spec/test/CodeGenerationASTTransformsTest.groovy
+++ b/src/spec/test/CodeGenerationASTTransformsTest.groovy
@@ -721,6 +721,38 @@ def p = new Person('Jack')
assert p.$firstName == 'Jack'
// end::tupleconstructor_example_allNames[]
'''
+
+ assertScript '''
+// tag::tupleconstructor_example_pre[]
+import groovy.transform.TupleConstructor
+
+@TupleConstructor(pre={ first = first?.toLowerCase() })
+class Person {
+ String first
+}
+
+def p = new Person('Jack')
+
+assert p.first == 'jack'
+// end::tupleconstructor_example_pre[]
+'''
+
+ assertScript '''
+// tag::tupleconstructor_example_post[]
+import groovy.transform.TupleConstructor
+import static groovy.test.GroovyAssert.shouldFail
+
+@TupleConstructor(post={ assert first })
+class Person {
+ String first
+}
+
+def jack = new Person('Jack')
+shouldFail {
+ def unknown = new Person()
+}
+// end::tupleconstructor_example_post[]
+'''
}
void testMapConstructor() {
http://git-wip-us.apache.org/repos/asf/groovy/blob/7da8b58c/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy b/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
index e4475ef..af5829b 100644
--- a/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
@@ -36,6 +36,43 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
"""
}
+ void testConstructorWithPostAndFields() {
+ assertScript '''
+ import groovy.transform.*
+
+ @ToString(includeFields=true, includeNames=true)
+ @TupleConstructor(post={ full = "$first $last" })
+ class Person {
+ final String first, last
+ private final String full
+ }
+
+ assert new Person('Dierk', 'Koenig').toString() ==
+ 'Person(first:Dierk, last:Koenig, full:Dierk Koenig)'
+ '''
+ }
+
+ void testConstructorWithPreAndPost() {
+ assertScript '''
+ import groovy.transform.*
+
+ @TupleConstructor
+ class Person {
+ String first, last
+ }
+
+ @CompileStatic // optional
+ @ToString(includeSuperProperties=true)
+ @TupleConstructor(includeSuperProperties=true, pre={ super(first, last?.toLowerCase()) }, post = { this.first = this.first?.toUpperCase() })
+ class Author extends Person {
+ String bookName
+ }
+
+ assert new Author('Dierk', 'Koenig', 'ReGinA').toString() == 'Author(ReGinA, DIERK, koenig)'
+ assert new Author().toString() == 'Author(null, null, null)'
+ '''
+ }
+
void testExistingEmptyConstructorTakesPrecedence_groovy7522() {
assertScript """
@groovy.transform.TupleConstructor