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/23 03:15:35 UTC
[1/2] incubator-groovy git commit: GROOVY-7353: Groovy should provide
a MapConstructor AST transform
Repository: incubator-groovy
Updated Branches:
refs/heads/master 14a3a6700 -> 569d68a9b
GROOVY-7353: Groovy should provide a MapConstructor AST transform
Project: http://git-wip-us.apache.org/repos/asf/incubator-groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-groovy/commit/87b6633f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-groovy/tree/87b6633f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-groovy/diff/87b6633f
Branch: refs/heads/master
Commit: 87b6633f53a8a7575dba9b0cd27a691cb38e320a
Parents: 14a3a67
Author: Paul King <pa...@asert.com.au>
Authored: Wed May 20 12:10:46 2015 +1000
Committer: Paul King <pa...@asert.com.au>
Committed: Sat May 23 10:07:03 2015 +1000
----------------------------------------------------------------------
src/main/groovy/transform/MapConstructor.java | 121 ++++++++++
.../groovy/antlr/AntlrParserPlugin.java | 7 +-
.../MapConstructorASTTransformation.java | 225 +++++++++++++++++++
.../MapConstructorTransformTest.groovy | 139 ++++++++++++
4 files changed, 491 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/87b6633f/src/main/groovy/transform/MapConstructor.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/transform/MapConstructor.java b/src/main/groovy/transform/MapConstructor.java
new file mode 100644
index 0000000..adcab87
--- /dev/null
+++ b/src/main/groovy/transform/MapConstructor.java
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Class annotation used to assist in the creation of map constructors in classes.
+ * <p>
+ * It allows you to write classes in this shortened form:
+ * <pre>
+ * import groovy.transform.*
+ *
+ * {@code @TupleConstructor}
+ * class Person {
+ * String first, last
+ * }
+ *
+ * {@code @CompileStatic // optional
+ * {@code @ToString(includeSuperProperties=true)}
+ * {@code @MapConstructor}(pre={ super(args?.first, args?.last); args = args ?: [:] }, post = { first = first?.toUpperCase() })
+ * class Author extends Person {
+ * String bookName
+ * }
+ *
+ * assert new Author(first: 'Dierk', last: 'Koenig', bookName: 'ReGinA').toString() == 'Author(ReGinA, DIERK, Koenig)'
+ * assert new Author().toString() == 'Author(null, null, null)'
+ * </pre>
+ * The {@code @MapConstructor} annotation instructs the compiler to execute an
+ * AST transformation which adds the necessary constructor method to your class.
+ * <p>
+ * A map constructor is created which sets properties, and optionally fields and
+ * super properties if the property/field name is a key within the map.
+ * <p>
+ * For the above example, the generated constructor will be something like:
+ * <pre>
+ * public Author(java.util.Map args) {
+ * super(args?.first, args?.last)
+ * args = args ? args : [:]
+ * if (args.containsKey('bookName')) {
+ * this.bookName = args['bookName']
+ * }
+ * first = first?.toUpperCase()
+ * }
+ * </pre>
+ *
+ * @since 2.5.0
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE})
+@GroovyASTTransformationClass("org.codehaus.groovy.transform.MapConstructorASTTransformation")
+public @interface MapConstructor {
+ /**
+ * List of field and/or property names to exclude from the constructor.
+ * Must not be used if 'includes' is used. For convenience, a String with comma separated names
+ * can be used in addition to an array (using Groovy's literal list notation) of String values.
+ */
+ String[] excludes() default {};
+
+ /**
+ * List of field and/or property names to include within the constructor.
+ * Must not be used if 'excludes' is used. For convenience, a String with comma separated names
+ * can be used in addition to an array (using Groovy's literal list notation) of String values.
+ */
+ String[] includes() default {};
+
+ /**
+ * Include fields in the constructor.
+ */
+ boolean includeFields() default false;
+
+ /**
+ * Include properties in the constructor.
+ */
+ boolean includeProperties() default true;
+
+ /**
+ * Include properties from super classes in the constructor.
+ */
+ boolean includeSuperProperties() default false;
+
+ /**
+ * 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
+ * made null-safe wrt the parameter.
+ */
+ boolean useSetters() default false;
+
+ /**
+ * A Closure containing statements which will be prepended to the generated constructor. The first statement within the Closure may be "super(someArgs)" in which case the no-arg super constructor won't be called.
+ */
+ 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.
+ */
+ Class post();
+}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/87b6633f/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java b/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java
index 0527e16..d225ee6 100644
--- a/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java
+++ b/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java
@@ -104,6 +104,7 @@ public class AntlrParserPlugin extends ASTHelper implements ParserPlugin, Groovy
private int innerClassCounter = 1;
private boolean enumConstantBeingDef = false;
private boolean forStatementBeingDef = false;
+ private boolean annotationBeingDef = false;
private boolean firstParamIsVarArg = false;
private boolean firstParam = false;
@@ -1226,6 +1227,7 @@ public class AntlrParserPlugin extends ASTHelper implements ParserPlugin, Groovy
}
protected AnnotationNode annotation(AST annotationNode) {
+ annotationBeingDef = true;
AST node = annotationNode.getFirstChild();
String name = qualifiedName(node);
AnnotationNode annotatedNode = new AnnotationNode(ClassHelper.make(name));
@@ -1244,6 +1246,7 @@ public class AntlrParserPlugin extends ASTHelper implements ParserPlugin, Groovy
break;
}
}
+ annotationBeingDef = false;
return annotatedNode;
}
@@ -2490,7 +2493,9 @@ public class AntlrParserPlugin extends ASTHelper implements ParserPlugin, Groovy
// if node text is found to be "super"/"this" when a method call is being processed, it is a
// call like this(..)/super(..) after the first statement, which shouldn't be allowed. GROOVY-2836
if (selector.getText().equals("this") || selector.getText().equals("super")) {
- throw new ASTRuntimeException(elist, "Constructor call must be the first statement in a constructor.");
+ if (!(annotationBeingDef && selector.getText().equals("super"))) {
+ throw new ASTRuntimeException(elist, "Constructor call must be the first statement in a constructor.");
+ }
}
Expression arguments = arguments(elist);
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/87b6633f/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
new file mode 100644
index 0000000..ecbbd9c
--- /dev/null
+++ b/src/main/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
@@ -0,0 +1,225 @@
+/*
+ * 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 org.codehaus.groovy.transform;
+
+import groovy.transform.MapConstructor;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.DynamicVariable;
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.codehaus.groovy.ast.ClassHelper.make;
+import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+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.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.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+/**
+ * Handles generation of code for the @MapConstructor annotation.
+ */
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+public class MapConstructorASTTransformation extends AbstractASTTransformation {
+
+ static final Class MY_CLASS = MapConstructor.class;
+ static final ClassNode MY_TYPE = make(MY_CLASS);
+ static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
+ private static final ClassNode MAP_TYPE = makeWithoutCaching(Map.class, false);
+// private static final ClassNode CHECK_METHOD_TYPE = make(ImmutableASTTransformation.class);
+
+ public void visit(ASTNode[] nodes, SourceUnit source) {
+ init(nodes, source);
+ AnnotatedNode parent = (AnnotatedNode) nodes[1];
+ AnnotationNode anno = (AnnotationNode) nodes[0];
+ if (!MY_TYPE.equals(anno.getClassNode())) return;
+
+ if (parent instanceof ClassNode) {
+ ClassNode cNode = (ClassNode) parent;
+ if (!checkNotInterface(cNode, MY_TYPE_NAME)) return;
+ boolean includeFields = memberHasValue(anno, "includeFields", true);
+ boolean includeProperties = !memberHasValue(anno, "includeProperties", false);
+ boolean includeSuperProperties = memberHasValue(anno, "includeSuperProperties", true);
+ boolean useSetters = memberHasValue(anno, "useSetters", true);
+ List<String> excludes = getMemberList(anno, "excludes");
+ List<String> includes = getMemberList(anno, "includes");
+ if (hasAnnotation(cNode, CanonicalASTTransformation.MY_TYPE)) {
+ AnnotationNode canonical = cNode.getAnnotations(CanonicalASTTransformation.MY_TYPE).get(0);
+ if (excludes == null || excludes.isEmpty()) excludes = getMemberList(canonical, "excludes");
+ if (includes == null || includes.isEmpty()) includes = getMemberList(canonical, "includes");
+ }
+ 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;
+
+ 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(cNode, includeFields, includeProperties, includeSuperProperties, useSetters, excludes, includes, (ClosureExpression) pre, (ClosureExpression) post, source);
+ 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()));
+ }
+ }
+ }
+
+ public static void createConstructor(ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperProperties, boolean useSetters, List<String> excludes, List<String> includes, ClosureExpression pre, ClosureExpression post, SourceUnit source) {
+ List<ConstructorNode> constructors = cNode.getDeclaredConstructors();
+ boolean foundEmpty = constructors.size() == 1 && constructors.get(0).getFirstStatement() == null;
+ // HACK: JavaStubGenerator could have snuck in a constructor we don't want
+ if (foundEmpty) constructors.remove(0);
+
+ List<FieldNode> superList = new ArrayList<FieldNode>();
+ if (includeSuperProperties) {
+ superList.addAll(getSuperPropertyFields(cNode.getSuperClass()));
+ }
+
+ List<FieldNode> list = new ArrayList<FieldNode>();
+ if (includeProperties) {
+ list.addAll(getInstancePropertyFields(cNode));
+ }
+ if (includeFields) {
+ list.addAll(getInstanceNonPropertyFields(cNode));
+ }
+
+ Parameter map = param(MAP_TYPE, "args");
+ final BlockStatement body = new BlockStatement();
+ ClassCodeExpressionTransformer transformer = makeTransformer();
+ if (pre != null) {
+ ClosureExpression transformed = (ClosureExpression) transformer.transform(pre);
+ copyPreStatements(transformed, body);
+ }
+ for (FieldNode fNode : superList) {
+ String name = fNode.getName();
+ if (shouldSkip(name, excludes, includes)) continue;
+ assignField(useSetters, map, body, name);
+ }
+ for (FieldNode fNode : list) {
+ String name = fNode.getName();
+ if (shouldSkip(name, excludes, includes)) continue;
+ assignField(useSetters, map, body, name);
+ }
+ if (post != null) {
+ ClosureExpression transformed = (ClosureExpression) transformer.transform(post);
+ body.addStatement(transformed.getCode());
+ }
+ cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, params(map), ClassNode.EMPTY_ARRAY, body));
+ }
+
+ private static void assignField(boolean useSetters, Parameter map, BlockStatement body, String name) {
+ ArgumentListExpression nameArg = args(constX(name));
+ body.addStatement(ifS(callX(varX(map), "containsKey", nameArg), useSetters ?
+ stmt(callThisX(getSetterName(name), callX(varX(map), "get", nameArg))) :
+ 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() {
+ 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;
+ }
+ }
+ 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);
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/87b6633f/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy b/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy
new file mode 100644
index 0000000..22cd930
--- /dev/null
+++ b/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy
@@ -0,0 +1,139 @@
+/*
+ * 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 org.codehaus.groovy.transform
+
+class MapConstructorTransformTest extends GroovyTestCase {
+ void testMapConstructorWithFinalFields() {
+ assertScript '''
+ import groovy.transform.*
+
+ @ToString
+ @MapConstructor
+ class Person {
+ final String first, last
+ }
+
+ assert new Person(first: 'Dierk', last: 'Koenig').toString() == 'Person(Dierk, Koenig)'
+ '''
+ }
+
+ void testMapConstructorWithSetters() {
+ assertScript '''
+ import groovy.transform.*
+
+ @ToString
+ @MapConstructor(useSetters=true)
+ class Person {
+ String first, last
+ void setFirst(String first) {
+ this.first = first?.toUpperCase()
+ }
+ }
+
+ assert new Person(first: 'Dierk', last: 'Koenig').toString() == 'Person(DIERK, Koenig)'
+ '''
+ }
+
+ void testMapConstructorWithIncludesAndExcludes() {
+ assertScript '''
+ import groovy.transform.*
+
+ @ToString(includes='first')
+ @MapConstructor(includes='first')
+ class Person {
+ String first, last
+ }
+
+ assert new Person(first: 'Dierk').toString() == 'Person(Dierk)'
+ '''
+ assertScript '''
+ import groovy.transform.*
+
+ @ToString @MapConstructor(includes='first')
+ class Person {
+ String first, last
+ }
+
+ assert new Person(first: 'Dierk', last: 'Koenig').toString() == 'Person(Dierk, null)'
+ '''
+ assertScript '''
+ import groovy.transform.*
+
+ @ToString @MapConstructor(excludes='last')
+ class Person {
+ String first, last
+ }
+
+ assert new Person(first: 'Dierk', last: 'Koenig').toString() == 'Person(Dierk, null)'
+ '''
+ }
+
+ void testMapConstructorWithPost() {
+ def msg = shouldFail(MissingPropertyException, '''
+ import groovy.transform.*
+ import org.codehaus.groovy.transform.ImmutableASTTransformation
+
+ @ToString
+ @MapConstructor(post={ ImmutableASTTransformation.checkPropNames(this, args) })
+ class Person {
+ String first, last
+ }
+
+ new Person(last: 'Koenig', nickname: 'mittie')
+ ''')
+ assert msg.contains('No such property: nickname for class: Person')
+ }
+
+ void testMapConstructorWithPostAndFields() {
+ assertScript '''
+ import groovy.transform.*
+
+ @ToString(includeFields=true, includeNames=true)
+ @MapConstructor(includeFields=true, post={ full = "$first $last" })
+ class Person {
+ final String first, last
+ private final String full
+ }
+
+ assert new Person(first: 'Dierk', last: 'Koenig').toString() ==
+ 'Person(first:Dierk, last:Koenig, full:Dierk Koenig)'
+ '''
+ }
+
+ void testMapConstructorWithPreAndPost() {
+ assertScript '''
+ import groovy.transform.*
+
+ @TupleConstructor
+ class Person {
+ String first, last
+ }
+
+ @CompileStatic // optional
+ @ToString(includeSuperProperties=true)
+ @MapConstructor(pre={ super(args?.first, args?.last); args = args ?: [:] }, post = { first = first?.toUpperCase() })
+ class Author extends Person {
+ String bookName
+ }
+
+ assert new Author(first: 'Dierk', last: 'Koenig', bookName: 'ReGinA').toString() == 'Author(ReGinA, DIERK, Koenig)'
+ assert new Author().toString() == 'Author(null, null, null)'
+ '''
+ }
+}
[2/2] incubator-groovy git commit: GROOVY-7353: Groovy should provide
a MapConstructor AST transform - additional tests and mods for recent master
changes (closes #20)
Posted by pa...@apache.org.
GROOVY-7353: Groovy should provide a MapConstructor AST transform - additional tests and mods for recent master changes (closes #20)
Project: http://git-wip-us.apache.org/repos/asf/incubator-groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-groovy/commit/569d68a9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-groovy/tree/569d68a9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-groovy/diff/569d68a9
Branch: refs/heads/master
Commit: 569d68a9ba5ef7ff6cea683203a70646211fdee9
Parents: 87b6633
Author: Paul King <pa...@asert.com.au>
Authored: Sat May 23 11:15:01 2015 +1000
Committer: Paul King <pa...@asert.com.au>
Committed: Sat May 23 11:15:01 2015 +1000
----------------------------------------------------------------------
.../MapConstructorASTTransformation.java | 7 +--
.../MapConstructorTransformTest.groovy | 52 +++++++++++++++++++-
2 files changed, 53 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/569d68a9/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 ecbbd9c..c968a0c 100644
--- a/src/main/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
+++ b/src/main/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
@@ -90,12 +90,9 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation {
boolean useSetters = memberHasValue(anno, "useSetters", true);
List<String> excludes = getMemberList(anno, "excludes");
List<String> includes = getMemberList(anno, "includes");
- if (hasAnnotation(cNode, CanonicalASTTransformation.MY_TYPE)) {
- AnnotationNode canonical = cNode.getAnnotations(CanonicalASTTransformation.MY_TYPE).get(0);
- if (excludes == null || excludes.isEmpty()) excludes = getMemberList(canonical, "excludes");
- if (includes == null || includes.isEmpty()) includes = getMemberList(canonical, "includes");
- }
if (!checkIncludeExclude(anno, excludes, includes, MY_TYPE_NAME)) return;
+ if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields)) return;
+ 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;
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/569d68a9/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy b/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy
index 22cd930..f59c26b 100644
--- a/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy
@@ -18,7 +18,7 @@
*/
package org.codehaus.groovy.transform
-class MapConstructorTransformTest extends GroovyTestCase {
+class MapConstructorTransformTest extends GroovyShellTestCase {
void testMapConstructorWithFinalFields() {
assertScript '''
import groovy.transform.*
@@ -136,4 +136,54 @@ class MapConstructorTransformTest extends GroovyTestCase {
assert new Author().toString() == 'Author(null, null, null)'
'''
}
+
+ void testIncludesAndExcludesTogetherResultsInError() {
+ def message = shouldFail {
+ evaluate """
+ import groovy.transform.MapConstructor
+
+ @MapConstructor(includes='surName', excludes='surName')
+ class Person {
+ String surName
+ }
+
+ new Person()
+ """
+ }
+ assert message.contains("Error during @MapConstructor processing: Only one of 'includes' and 'excludes' should be supplied not both.")
+ }
+
+ void testIncludesWithInvalidPropertyNameResultsInError() {
+ def message = shouldFail {
+ evaluate """
+ import groovy.transform.MapConstructor
+
+ @MapConstructor(includes='sirName')
+ class Person {
+ String firstName
+ String surName
+ }
+
+ new Person(surname: "Doe")
+ """
+ }
+ assert message.contains("Error during @MapConstructor processing: 'includes' property 'sirName' does not exist.")
+ }
+
+ void testExcludesWithInvalidPropertyNameResultsInError() {
+ def message = shouldFail {
+ evaluate """
+ import groovy.transform.MapConstructor
+
+ @MapConstructor(excludes='sirName')
+ class Person {
+ String firstName
+ String surName
+ }
+
+ new Person(surname: "Doe")
+ """
+ }
+ assert message.contains("Error during @MapConstructor processing: 'excludes' property 'sirName' does not exist.")
+ }
}