GROOVY-8477: @Immutable-related transformations should be more configurable


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/69fc7ab3
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/69fc7ab3
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/69fc7ab3

Branch: refs/heads/master
Commit: 69fc7ab3c4055192fce096f69f8d4a90768863ac
Parents: 011b98e
Author: paulk <pa...@asert.com.au>
Authored: Tue Feb 13 15:06:07 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Thu Feb 15 07:37:12 2018 +1000

----------------------------------------------------------------------
 .../groovy/groovy/transform/Immutable.groovy    |   6 +-
 .../groovy/groovy/transform/MapConstructor.java |  26 +-
 .../groovy/transform/TupleConstructor.java      |   9 +
 .../construction/DefaultPropertyHandler.java    | 103 +++++
 .../construction/ImmutablePropertyHandler.java  | 295 ++++++++++++
 .../LegacyHashMapPropertyHandler.java           |  99 ++++
 .../transform/construction/PropertyHandler.java |  76 ++++
 .../ast/tools/ImmutablePropertyUtils.java       | 233 ++++++++++
 .../codehaus/groovy/ast/tools/GeneralUtils.java |  18 +-
 .../transform/AbstractASTTransformation.java    |  15 +-
 .../transform/ImmutableASTTransformation.java   | 449 +------------------
 .../MapConstructorASTTransformation.java        |  91 ++--
 .../transform/ToStringASTTransformation.java    |   4 +-
 .../TupleConstructorASTTransformation.java      | 104 ++---
 .../transform/ImmutableTransformTest.groovy     |   6 +-
 15 files changed, 979 insertions(+), 555 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/groovy/groovy/transform/Immutable.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/Immutable.groovy 
b/src/main/groovy/groovy/transform/Immutable.groovy
index 3fa518a..cfbcc30 100644
--- a/src/main/groovy/groovy/transform/Immutable.groovy
+++ b/src/main/groovy/groovy/transform/Immutable.groovy
@@ -18,6 +18,8 @@
  */
 package groovy.transform
 
+import groovy.transform.construction.ImmutablePropertyHandler
+
 /**
  * Meta annotation used when defining immutable classes.
  * <p>
@@ -174,8 +176,8 @@ package groovy.transform
 @ToString(cache = true, includeSuperProperties = true)
 @EqualsAndHashCode(cache = true)
 @ImmutableBase
-@TupleConstructor(defaults = false)
-@MapConstructor(noArg = true, includeSuperProperties = true)
+@TupleConstructor(defaults = false, propertyHandler = ImmutablePropertyHandler)
+@MapConstructor(noArg = true, includeSuperProperties = true, includeFields = 
true, propertyHandler = ImmutablePropertyHandler)
 @KnownImmutable
 @AnnotationCollector(mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED)
 @interface Immutable { }

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/groovy/groovy/transform/MapConstructor.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/MapConstructor.java 
b/src/main/groovy/groovy/transform/MapConstructor.java
index e59ce75..2cdbf94 100644
--- a/src/main/groovy/groovy/transform/MapConstructor.java
+++ b/src/main/groovy/groovy/transform/MapConstructor.java
@@ -18,6 +18,8 @@
  */
 package groovy.transform;
 
+import groovy.transform.construction.DefaultPropertyHandler;
+import groovy.transform.construction.PropertyHandler;
 import org.codehaus.groovy.transform.GroovyASTTransformationClass;
 
 import java.lang.annotation.ElementType;
@@ -74,7 +76,12 @@ import java.lang.annotation.Target;
  * is included and the type of that property (or field) is Object, 
AbstractMap, Map or HashMap.
  * In this case, the generated constructor will be of type {@code 
LinkedHashMap}.
  * This allows the possibility of also adding a tuple constructor without 
conflict, although
- * no such constructor is added automatically.
+ * no such constructor is added automatically. You can disable this behavior 
by setting
+ * the specialNamedArgHandling annotation attribute to false. This means that 
for the special
+ * case mentioned above, you will not be able to also add a tuple constructor 
with a single Map
+ * argument but you can supply any kind of map as your argument. We'd also 
recommend not
+ * having both a map constructor and a tuple constructor with a single Object, 
AbstractMap or
+ * HashMap since it can cause confusion as to which will be called.
  * </li>
  * </ul>
  *
@@ -143,6 +150,11 @@ public @interface MapConstructor {
     boolean useSetters() default false;
 
     /**
+     * Whether to include static properties in the constructor.
+     */
+    boolean includeStatic() default false;
+
+    /**
      * Whether to include all fields and/or properties within the constructor, 
including those with names that are considered internal.
      */
     boolean allNames() default false;
@@ -153,6 +165,18 @@ public @interface MapConstructor {
     boolean noArg() default false;
 
     /**
+     * A class defining the property handler
+     */
+    Class<? extends PropertyHandler> propertyHandler() default 
DefaultPropertyHandler.class;
+
+    /**
+     * If true, change the type of the map constructor argument from Map to 
LinkedHashMap only for the case where
+     * the class has a single property (or field) with a Map-like type. This 
allows both a map and a tuple constructor
+     * to be used side-by-side so long as care is taken about the types used 
when calling.
+     */
+    boolean specialNamedArgHandling() default true;
+
+    /**
      * 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() default Undefined.CLASS.class;

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/groovy/groovy/transform/TupleConstructor.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/TupleConstructor.java 
b/src/main/groovy/groovy/transform/TupleConstructor.java
index e611cb5..a162f0f 100644
--- a/src/main/groovy/groovy/transform/TupleConstructor.java
+++ b/src/main/groovy/groovy/transform/TupleConstructor.java
@@ -18,6 +18,8 @@
  */
 package groovy.transform;
 
+import groovy.transform.construction.DefaultPropertyHandler;
+import groovy.transform.construction.PropertyHandler;
 import org.codehaus.groovy.transform.GroovyASTTransformationClass;
 
 import java.lang.annotation.ElementType;
@@ -298,6 +300,13 @@ public @interface TupleConstructor {
     boolean allProperties() default false;
 
     /**
+     * A class defining the property handler
+     *
+     * @since 2.5.0
+     */
+    Class<? extends PropertyHandler> propertyHandler() default 
DefaultPropertyHandler.class;
+
+    /**
      * 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.
      *

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java
----------------------------------------------------------------------
diff --git 
a/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java 
b/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java
new file mode 100644
index 0000000..198e69d
--- /dev/null
+++ b/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java
@@ -0,0 +1,103 @@
+/*
+ *  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.construction;
+
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.transform.AbstractASTTransformation;
+import org.codehaus.groovy.transform.ImmutableASTTransformation;
+import org.codehaus.groovy.transform.MapConstructorASTTransformation;
+
+import java.util.List;
+
+import static org.codehaus.groovy.ast.ClassHelper.make;
+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.equalsNullX;
+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.propX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+public class DefaultPropertyHandler extends PropertyHandler {
+    private static final ClassNode IMMUTABLE_XFORM_TYPE = 
make(ImmutableASTTransformation.class);
+
+    @Override
+    public boolean validateAttributes(AbstractASTTransformation xform, 
AnnotationNode anno) {
+        boolean success = true;
+        success |= isValidAttribute(xform, anno, "");
+        return success;
+    }
+
+    @Override
+    public boolean validateProperties(AbstractASTTransformation xform, 
BlockStatement body, ClassNode cNode, List<PropertyNode> props) {
+        if (xform instanceof MapConstructorASTTransformation) {
+            body.addStatement(ifS(equalsNullX(varX("args")), 
assignS(varX("args"), new MapExpression())));
+            body.addStatement(stmt(callX(IMMUTABLE_XFORM_TYPE, 
"checkPropNames", args("this", "args"))));
+        }
+        return super.validateProperties(xform, body, cNode, props);
+    }
+
+    @Override
+    public void createStatement(AbstractASTTransformation xform, 
AnnotationNode anno, BlockStatement body, ClassNode cNode, PropertyNode pNode, 
Parameter namedArgsMap) {
+        String name = pNode.getName();
+        FieldNode fNode = pNode.getField();
+        boolean useSetters = xform.memberHasValue(anno, "useSetters", true);
+        boolean hasSetter = cNode.getProperty(name) != null && 
!fNode.isFinal();
+        if (namedArgsMap != null) {
+            assignField(useSetters, namedArgsMap, body, name);
+        } else {
+            Expression var = varX(name);
+            if (useSetters && hasSetter) {
+                body.addStatement(setViaSetter(name, var));
+            } else {
+                body.addStatement(assignToField(name, var));
+            }
+        }
+
+    }
+
+    private static Statement assignToField(String name, Expression var) {
+        return assignS(propX(varX("this"), name), var);
+    }
+
+    private static Statement setViaSetter(String name, Expression var) {
+        return stmt(callThisX(getSetterName(name), var));
+    }
+
+    private static void assignField(boolean useSetters, Parameter map, 
BlockStatement body, String name) {
+        ArgumentListExpression nameArg = args(constX(name));
+        Expression var = callX(varX(map), "get", nameArg);
+        body.addStatement(ifS(callX(varX(map), "containsKey", nameArg), 
useSetters ?
+                setViaSetter(name, var) :
+                assignToField(name, var)));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java
----------------------------------------------------------------------
diff --git 
a/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java 
b/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java
new file mode 100644
index 0000000..09e9126
--- /dev/null
+++ 
b/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java
@@ -0,0 +1,295 @@
+/*
+ *  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.construction;
+
+import groovy.lang.ReadOnlyPropertyException;
+import org.apache.groovy.ast.tools.ImmutablePropertyUtils;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+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.runtime.DefaultGroovyMethods;
+import org.codehaus.groovy.transform.AbstractASTTransformation;
+import org.codehaus.groovy.transform.ImmutableASTTransformation;
+import org.codehaus.groovy.transform.MapConstructorASTTransformation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+
+import static 
org.apache.groovy.ast.tools.ImmutablePropertyUtils.cloneArrayOrCloneableExpr;
+import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.cloneDateExpr;
+import static 
org.apache.groovy.ast.tools.ImmutablePropertyUtils.derivesFromDate;
+import static 
org.apache.groovy.ast.tools.ImmutablePropertyUtils.implementsCloneable;
+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.castX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classList2args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+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.findArg;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isInstanceOfX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.list2args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.notX;
+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.ternaryX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+public class ImmutablePropertyHandler extends PropertyHandler {
+    private static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = 
"knownImmutableClasses";
+    private static final ClassNode CLONEABLE_TYPE = make(Cloneable.class);
+    private static final ClassNode COLLECTION_TYPE = 
makeWithoutCaching(Collection.class, false);
+    private static final ClassNode DGM_TYPE = make(DefaultGroovyMethods.class);
+    private static final ClassNode SELF_TYPE = 
make(ImmutableASTTransformation.class);
+    private static final ClassNode MAP_TYPE = makeWithoutCaching(Map.class, 
false);
+    private static final ClassNode SORTEDSET_CLASSNODE = make(SortedSet.class);
+    private static final ClassNode SORTEDMAP_CLASSNODE = make(SortedMap.class);
+    private static final ClassNode SET_CLASSNODE = make(Set.class);
+    private static final ClassNode MAP_CLASSNODE = make(Map.class);
+    private static final ClassNode READONLYEXCEPTION_TYPE = 
make(ReadOnlyPropertyException.class);
+    private static final ClassNode IMMUTABLE_XFORM_TYPE = 
make(ImmutableASTTransformation.class);
+
+    private static List<String> 
getKnownImmutableClasses(AbstractASTTransformation xform, AnnotationNode node) {
+        final List<String> immutableClasses = new ArrayList<String>();
+
+        if (node == null) return immutableClasses;
+        final Expression expression = 
node.getMember(MEMBER_KNOWN_IMMUTABLE_CLASSES);
+        if (expression == null) return immutableClasses;
+
+        if (!(expression instanceof ListExpression)) {
+            xform.addError("Use the Groovy list notation [el1, el2] to specify 
known immutable classes via \"" + MEMBER_KNOWN_IMMUTABLE_CLASSES + "\"", node);
+            return immutableClasses;
+        }
+
+        final ListExpression listExpression = (ListExpression) expression;
+        for (Expression listItemExpression : listExpression.getExpressions()) {
+            if (listItemExpression instanceof ClassExpression) {
+                immutableClasses.add(listItemExpression.getType().getName());
+            }
+        }
+
+        return immutableClasses;
+    }
+
+    protected Expression cloneCollectionExpr(Expression fieldExpr, ClassNode 
type) {
+        return castX(type, createIfInstanceOfAsImmutableS(fieldExpr, 
SORTEDSET_CLASSNODE,
+                createIfInstanceOfAsImmutableS(fieldExpr, SORTEDMAP_CLASSNODE,
+                        createIfInstanceOfAsImmutableS(fieldExpr, 
SET_CLASSNODE,
+                                createIfInstanceOfAsImmutableS(fieldExpr, 
MAP_CLASSNODE,
+                                        
createIfInstanceOfAsImmutableS(fieldExpr, ClassHelper.LIST_TYPE,
+                                                createAsImmutableX(fieldExpr, 
COLLECTION_TYPE))
+                                )
+                        )
+                )
+        ));
+    }
+
+    private Expression createIfInstanceOfAsImmutableS(Expression expr, 
ClassNode type, Expression elseStatement) {
+        return ternaryX(isInstanceOfX(expr, type), createAsImmutableX(expr, 
type), elseStatement);
+    }
+
+    protected Expression createAsImmutableX(final Expression expr, final 
ClassNode type) {
+        return callX(DGM_TYPE, "asImmutable", castX(type, expr));
+    }
+
+    protected Statement createConstructorStatement(AbstractASTTransformation 
xform, ClassNode cNode, PropertyNode pNode, boolean namedArgs) {
+        List<AnnotationNode> annotations = 
cNode.getAnnotations(ImmutablePropertyUtils.IMMUTABLE_BASE_TYPE);
+        AnnotationNode annoImmutable = annotations.isEmpty() ? null : 
annotations.get(0);
+        final List<String> knownImmutableClasses = 
getKnownImmutableClasses(xform, annoImmutable);
+        final List<String> knownImmutables = 
ImmutablePropertyUtils.getKnownImmutables(xform, annoImmutable);
+        FieldNode fNode = pNode.getField();
+        final ClassNode fType = fNode.getType();
+        Statement statement;
+        if (ImmutablePropertyUtils.isKnownImmutableType(fType, 
knownImmutableClasses) || isKnownImmutable(pNode.getName(), knownImmutables)) {
+            statement = createConstructorStatementDefault(fNode, namedArgs);
+        } else if (fType.isArray() || implementsCloneable(fType)) {
+            statement = createConstructorStatementArrayOrCloneable(fNode, 
namedArgs);
+        } else if (derivesFromDate(fType)) {
+            statement = createConstructorStatementDate(fNode, namedArgs);
+        } else if (isOrImplements(fType, COLLECTION_TYPE) || 
fType.isDerivedFrom(COLLECTION_TYPE) || isOrImplements(fType, MAP_TYPE) || 
fType.isDerivedFrom(MAP_TYPE)) {
+            statement = createConstructorStatementCollection(fNode, namedArgs);
+        } else if (fType.isResolved()) {
+            
xform.addError(ImmutablePropertyUtils.createErrorMessage(cNode.getName(), 
fNode.getName(), fType.getName(), "compiling"), fNode);
+            statement = EmptyStatement.INSTANCE;
+        } else {
+            statement = createConstructorStatementGuarded(cNode, fNode, 
namedArgs, knownImmutables, knownImmutableClasses);
+        }
+        return statement;
+    }
+
+    private static Statement createConstructorStatementDefault(FieldNode 
fNode, boolean namedArgs) {
+        final ClassNode fType = fNode.getType();
+        final Expression fieldExpr = propX(varX("this"), fNode.getName());
+        Expression initExpr = fNode.getInitialValueExpression();
+        Statement assignInit;
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression)initExpr).isNullExpression())) {
+            if (ClassHelper.isPrimitiveType(fType)) {
+                assignInit = EmptyStatement.INSTANCE;
+            } else {
+                assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
+            }
+        } else {
+            assignInit = assignS(fieldExpr, initExpr);
+        }
+        fNode.setInitialValueExpression(null);
+        Expression param = getParam(fNode, namedArgs);
+        Statement assignStmt = assignS(fieldExpr, castX(fType, param));
+        return assignWithDefault(namedArgs, assignInit, param, assignStmt);
+    }
+
+    private static Statement assignWithDefault(boolean namedArgs, Statement 
assignInit, Expression param, Statement assignStmt) {
+        if (!namedArgs) {
+            return assignStmt;
+        }
+        return ifElseS(equalsNullX(param), assignInit, assignStmt);
+    }
+
+    private static Statement createConstructorStatementGuarded(ClassNode 
cNode, FieldNode fNode, boolean namedArgs, List<String> knownImmutables, 
List<String> knownImmutableClasses) {
+        final Expression fieldExpr = propX(varX("this"), fNode.getName());
+        Expression initExpr = fNode.getInitialValueExpression();
+        final Statement assignInit;
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
+            assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
+        } else {
+            assignInit = assignS(fieldExpr, checkUnresolved(fNode, initExpr, 
knownImmutables, knownImmutableClasses));
+        }
+        Expression param = getParam(fNode, namedArgs);
+        Statement assignStmt = assignS(fieldExpr, checkUnresolved(fNode, 
param, knownImmutables, knownImmutableClasses));
+        return assignWithDefault(namedArgs, assignInit, param, assignStmt);
+    }
+
+    private static Expression checkUnresolved(FieldNode fNode, Expression 
value, List<String> knownImmutables, List<String> knownImmutableClasses) {
+        Expression args = args(callThisX("getClass"), constX(fNode.getName()), 
value, list2args(knownImmutables), classList2args(knownImmutableClasses));
+        return callX(SELF_TYPE, "checkImmutable", args);
+    }
+
+    private Statement createConstructorStatementCollection(FieldNode fNode, 
boolean namedArgs) {
+        final Expression fieldExpr = propX(varX("this"), fNode.getName());
+        ClassNode fieldType = fieldExpr.getType();
+        Expression initExpr = fNode.getInitialValueExpression();
+        final Statement assignInit;
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
+            assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
+        } else {
+            assignInit = assignS(fieldExpr, cloneCollectionExpr(initExpr, 
fieldType));
+        }
+        Expression param = getParam(fNode, namedArgs);
+        Statement assignStmt = ifElseS(
+                isInstanceOfX(param, CLONEABLE_TYPE),
+                assignS(fieldExpr, 
cloneCollectionExpr(cloneArrayOrCloneableExpr(param, fieldType), fieldType)),
+                assignS(fieldExpr, cloneCollectionExpr(param, fieldType)));
+        return assignWithDefault(namedArgs, assignInit, param, assignStmt);
+    }
+
+    private static Statement 
createConstructorStatementArrayOrCloneable(FieldNode fNode, boolean namedArgs) {
+        final Expression fieldExpr = propX(varX("this"), fNode.getName());
+        final Expression initExpr = fNode.getInitialValueExpression();
+        final ClassNode fieldType = fNode.getType();
+        final Expression param = getParam(fNode, namedArgs);
+        final Statement assignInit;
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
+            assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
+        } else {
+            assignInit = assignS(fieldExpr, 
cloneArrayOrCloneableExpr(initExpr, fieldType));
+        }
+        Statement assignStmt = assignS(fieldExpr, 
cloneArrayOrCloneableExpr(param, fieldType));
+        return assignWithDefault(namedArgs, assignInit, param, assignStmt);
+    }
+
+    private static Expression getParam(FieldNode fNode, boolean namedArgs) {
+        return namedArgs ? findArg(fNode.getName()) : varX(fNode.getName(), 
fNode.getType());
+    }
+
+    private static Statement createConstructorStatementDate(FieldNode fNode, 
boolean namedArgs) {
+        final Expression fieldExpr = propX(varX("this"), fNode.getName());
+        Expression initExpr = fNode.getInitialValueExpression();
+        final Statement assignInit;
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
+            assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
+        } else {
+            assignInit = assignS(fieldExpr, cloneDateExpr(initExpr));
+        }
+        final Expression param = getParam(fNode, namedArgs);
+        Statement assignStmt = assignS(fieldExpr, cloneDateExpr(param));
+        return assignWithDefault(namedArgs, assignInit, param, assignStmt);
+    }
+
+    private static boolean isKnownImmutable(String fieldName, List<String> 
knownImmutables) {
+        return knownImmutables.contains(fieldName);
+    }
+
+    protected Statement checkFinalArgNotOverridden(ClassNode cNode, FieldNode 
fNode) {
+        final String name = fNode.getName();
+        Expression value = findArg(name);
+        return ifS(
+                notX(equalsNullX(value)),
+                throwS(ctorX(READONLYEXCEPTION_TYPE,
+                        args(constX(name), constX(cNode.getName()))
+                )));
+    }
+
+    @Override
+    public boolean validateAttributes(AbstractASTTransformation xform, 
AnnotationNode anno) {
+        boolean success = isValidAttribute(xform, anno, "useSuper");
+        return success;
+    }
+
+    @Override
+    public boolean validateProperties(AbstractASTTransformation xform, 
BlockStatement body, ClassNode cNode, List<PropertyNode> props) {
+        if (xform instanceof MapConstructorASTTransformation) {
+            body.addStatement(ifS(equalsNullX(varX("args")), 
assignS(varX("args"), new MapExpression())));
+            body.addStatement(stmt(callX(IMMUTABLE_XFORM_TYPE, 
"checkPropNames", args("this", "args"))));
+        }
+        return super.validateProperties(xform, body, cNode, props);
+    }
+
+    @Override
+    public void createStatement(AbstractASTTransformation xform, 
AnnotationNode anno, BlockStatement body, ClassNode cNode, PropertyNode pNode, 
Parameter namedArgsMap) {
+        FieldNode fNode = pNode.getField();
+        if (fNode.isFinal() && fNode.isStatic()) return;
+        if (fNode.isFinal() && fNode.getInitialExpression() != null) {
+            body.addStatement(checkFinalArgNotOverridden(cNode, fNode));
+        }
+        body.addStatement(createConstructorStatement(xform, cNode, pNode, 
namedArgsMap != null));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java
----------------------------------------------------------------------
diff --git 
a/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java
 
b/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java
new file mode 100644
index 0000000..5991f68
--- /dev/null
+++ 
b/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java
@@ -0,0 +1,99 @@
+/*
+ *  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.construction;
+
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.transform.AbstractASTTransformation;
+import org.codehaus.groovy.transform.TupleConstructorASTTransformation;
+
+import java.util.HashMap;
+import java.util.List;
+
+import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+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.equalsNullX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.findArg;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isOneX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isTrueX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+public class LegacyHashMapPropertyHandler extends ImmutablePropertyHandler {
+    private static final ClassNode HMAP_TYPE = 
makeWithoutCaching(HashMap.class, false);
+
+    private Statement createLegacyConstructorStatementMapSpecial(FieldNode 
fNode) {
+        final Expression fieldExpr = varX(fNode);
+        final ClassNode fieldType = fieldExpr.getType();
+        final Expression initExpr = fNode.getInitialValueExpression();
+        final Statement assignInit;
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
+            assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
+        } else {
+            assignInit = assignS(fieldExpr, cloneCollectionExpr(initExpr, 
fieldType));
+        }
+        Expression namedArgs = findArg(fNode.getName());
+        Expression baseArgs = varX("args");
+        Statement assignStmt = ifElseS(
+                equalsNullX(namedArgs),
+                ifElseS(
+                        isTrueX(callX(baseArgs, "containsKey", 
constX(fNode.getName()))),
+                        assignS(fieldExpr, namedArgs),
+                        assignS(fieldExpr, cloneCollectionExpr(baseArgs, 
fieldType))),
+                ifElseS(
+                        isOneX(callX(baseArgs, "size")),
+                        assignS(fieldExpr, cloneCollectionExpr(namedArgs, 
fieldType)),
+                        assignS(fieldExpr, cloneCollectionExpr(baseArgs, 
fieldType)))
+        );
+        return ifElseS(equalsNullX(baseArgs), assignInit, assignStmt);
+    }
+
+    @Override
+    public boolean validateAttributes(AbstractASTTransformation xform, 
AnnotationNode anno) {
+        return !(xform instanceof TupleConstructorASTTransformation) && 
super.validateAttributes(xform, anno);
+    }
+
+    @Override
+    public boolean validateProperties(AbstractASTTransformation xform, 
BlockStatement body, ClassNode cNode, List<PropertyNode> props) {
+        if (!(props.size() == 1 && props.get(0).getType().equals(HMAP_TYPE))) {
+            xform.addError("Error during " + xform.getAnnotationName() + " 
processing. Property handler " + getClass().getName() + " only accepts a single 
HashMap property", props.size() == 1 ? props.get(0) : cNode);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void createStatement(AbstractASTTransformation xform, 
AnnotationNode anno, BlockStatement body, ClassNode cNode, PropertyNode pNode, 
Parameter namedArgsMap) {
+        FieldNode fNode = pNode.getField();
+        if (fNode.isFinal() && fNode.isStatic()) return;
+        if (fNode.isFinal() && fNode.getInitialExpression() != null) {
+            body.addStatement(checkFinalArgNotOverridden(cNode, fNode));
+        }
+        body.addStatement(createLegacyConstructorStatementMapSpecial(fNode));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/groovy/groovy/transform/construction/PropertyHandler.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/construction/PropertyHandler.java 
b/src/main/groovy/groovy/transform/construction/PropertyHandler.java
new file mode 100644
index 0000000..f3170c2
--- /dev/null
+++ b/src/main/groovy/groovy/transform/construction/PropertyHandler.java
@@ -0,0 +1,76 @@
+/*
+ *  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.construction;
+
+import groovy.lang.GroovyClassLoader;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.transform.AbstractASTTransformation;
+
+import java.util.List;
+
+public abstract class PropertyHandler {
+    public abstract boolean validateAttributes(AbstractASTTransformation 
xform, AnnotationNode anno);
+
+    public boolean validateProperties(AbstractASTTransformation xform, 
BlockStatement body, ClassNode cNode, List<PropertyNode> props) {
+        return true;
+    }
+
+    public abstract void createStatement(AbstractASTTransformation xform, 
AnnotationNode anno, BlockStatement body, ClassNode cNode, PropertyNode pNode, 
Parameter namedArgMap);
+
+    protected boolean isValidAttribute(AbstractASTTransformation xform, 
AnnotationNode anno, String memberName) {
+        if (xform.getMemberValue(anno, memberName) != null) {
+            xform.addError("Error during " + xform.getAnnotationName() + " 
processing: Annotation attribute '" + memberName +
+                    "' not supported for property handler " + 
getClass().getSimpleName(), anno);
+            return false;
+        }
+        return true;
+    }
+
+    public static PropertyHandler 
createPropertyHandler(AbstractASTTransformation xform, AnnotationNode anno, 
GroovyClassLoader loader) {
+        ClassNode handlerClass = xform.getMemberClassValue(anno, 
"propertyHandler", ClassHelper.make(DefaultPropertyHandler.class));
+
+        if (handlerClass == null) {
+            xform.addError("Couldn't determine propertyHandler class", anno);
+            return null;
+        }
+
+        String className = handlerClass.getName();
+        try {
+            Object instance = loader.loadClass(className).newInstance();
+            if (instance == null) {
+                xform.addError("Can't load propertyHandler '" + className + 
"'", anno);
+                return null;
+            }
+            if (!PropertyHandler.class.isAssignableFrom(instance.getClass())) {
+                xform.addError("The propertyHandler class '" + 
handlerClass.getName() + "' on " + xform.getAnnotationName() + " is not a 
propertyHandler", anno);
+                return null;
+            }
+
+            return (PropertyHandler) instance;
+        } catch (Exception e) {
+            xform.addError("Can't load propertyHandler '" + className + "' " + 
e, anno);
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java 
b/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java
new file mode 100644
index 0000000..e263f33
--- /dev/null
+++ b/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java
@@ -0,0 +1,233 @@
+/*
+ *  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.apache.groovy.ast.tools;
+
+import groovy.transform.ImmutableBase;
+import groovy.transform.KnownImmutable;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.expr.ArrayExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.runtime.ReflectionMethodInvoker;
+import org.codehaus.groovy.transform.AbstractASTTransformation;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+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.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
+
+public class ImmutablePropertyUtils {
+    private static final ClassNode CLONEABLE_TYPE = make(Cloneable.class);
+    private static final ClassNode DATE_TYPE = make(Date.class);
+    private static final ClassNode REFLECTION_INVOKER_TYPE = 
make(ReflectionMethodInvoker.class);
+    private static final String KNOWN_IMMUTABLE_NAME = 
KnownImmutable.class.getName();
+    private static final Class<? extends Annotation> MY_CLASS = 
ImmutableBase.class;
+    public static final ClassNode IMMUTABLE_BASE_TYPE = 
makeWithoutCaching(MY_CLASS, false);
+    private static final String MEMBER_KNOWN_IMMUTABLES = "knownImmutables";
+    /*
+              Currently leaving BigInteger and BigDecimal in list but see:
+              http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6348370
+
+              Also, Color is not final so while not normally used with child
+              classes, it isn't strictly immutable. Use at your own risk.
+
+              This list can by extended by providing "known immutable" classes
+              via Immutable.knownImmutableClasses
+             */
+    private static Set<String> builtinImmutables = new 
HashSet<String>(Arrays.asList(
+            "java.lang.Class",
+            "java.lang.Boolean",
+            "java.lang.Byte",
+            "java.lang.Character",
+            "java.lang.Double",
+            "java.lang.Float",
+            "java.lang.Integer",
+            "java.lang.Long",
+            "java.lang.Short",
+            "java.lang.String",
+            "java.math.BigInteger",
+            "java.math.BigDecimal",
+            "java.awt.Color",
+            "java.net.URI",
+            "java.util.UUID",
+            "java.time.DayOfWeek",
+            "java.time.Duration",
+            "java.time.Instant",
+            "java.time.LocalDate",
+            "java.time.LocalDateTime",
+            "java.time.LocalTime",
+            "java.time.Month",
+            "java.time.MonthDay",
+            "java.time.OffsetDateTime",
+            "java.time.OffsetTime",
+            "java.time.Period",
+            "java.time.Year",
+            "java.time.YearMonth",
+            "java.time.ZonedDateTime",
+            "java.time.ZoneOffset",
+            "java.time.ZoneRegion",
+            "java.time.chrono.ChronoLocalDate",
+            "java.time.chrono.ChronoLocalDateTime",
+            "java.time.chrono.Chronology",
+            "java.time.chrono.ChronoPeriod",
+            "java.time.chrono.ChronoZonedDateTime",
+            "java.time.chrono.Era",
+            "java.time.format.DecimalStyle",
+            "java.time.format.FormatStyle",
+            "java.time.format.ResolverStyle",
+            "java.time.format.SignStyle",
+            "java.time.format.TextStyle",
+            "java.time.temporal.IsoFields",
+            "java.time.temporal.JulianFields",
+            "java.time.temporal.ValueRange",
+            "java.time.temporal.WeekFields"
+    ));
+
+    private ImmutablePropertyUtils() { }
+
+    public static Expression cloneArrayOrCloneableExpr(Expression fieldExpr, 
ClassNode type) {
+        Expression smce = callX(
+                REFLECTION_INVOKER_TYPE,
+                "invoke",
+                args(
+                        fieldExpr,
+                        constX("clone"),
+                        new 
ArrayExpression(ClassHelper.OBJECT_TYPE.makeArray(), 
Collections.<Expression>emptyList())
+                )
+        );
+        return castX(type, smce);
+    }
+
+    public static boolean implementsCloneable(ClassNode fieldType) {
+        return isOrImplements(fieldType, CLONEABLE_TYPE);
+    }
+
+    public static Expression cloneDateExpr(Expression origDate) {
+        return ctorX(DATE_TYPE, callX(origDate, "getTime"));
+    }
+
+    public static boolean derivesFromDate(ClassNode fieldType) {
+        return fieldType.isDerivedFrom(DATE_TYPE);
+    }
+
+    public static String createErrorMessage(String className, String 
fieldName, String typeName, String mode) {
+        return "Unsupported type (" + prettyTypeName(typeName) + ") found for 
field '" + fieldName + "' while " + mode + " immutable class " + className + 
".\n" +
+                "Immutable classes only support properties with effectively 
immutable types including:\n" +
+                "- Strings, primitive types, wrapper types, Class, BigInteger 
and BigDecimal, enums\n" +
+                "- classes annotated with @KnownImmutable and known immutables 
(java.awt.Color, java.net.URI)\n" +
+                "- Cloneable classes, collections, maps and arrays, and other 
classes with special handling\n" +
+                "  (java.util.Date and various java.time.* classes and 
interfaces)\n" +
+                "Other restrictions apply, please see the groovydoc for " + 
IMMUTABLE_BASE_TYPE.getNameWithoutPackage() + " for further details";
+    }
+
+    private static String prettyTypeName(String name) {
+        return name.equals("java.lang.Object") ? name + " or def" : name;
+    }
+
+    public static boolean isKnownImmutableType(ClassNode fieldType, 
List<String> knownImmutableClasses) {
+        if (builtinOrDeemedType(fieldType, knownImmutableClasses))
+            return true;
+        if (!fieldType.isResolved())
+            return false;
+        if ("java.util.Optional".equals(fieldType.getName()) && 
fieldType.getGenericsTypes() != null && fieldType.getGenericsTypes().length == 
1) {
+            GenericsType optionalType = fieldType.getGenericsTypes()[0];
+            if (optionalType.isResolved() && !optionalType.isPlaceholder() && 
!optionalType.isWildcard()) {
+                ClassNode valueType = optionalType.getType();
+                if (builtinOrDeemedType(valueType, knownImmutableClasses)) 
return true;
+                if (valueType.isEnum()) return true;
+            }
+        }
+        return fieldType.isEnum() ||
+                ClassHelper.isPrimitiveType(fieldType) ||
+                hasImmutableAnnotation(fieldType);
+    }
+
+    private static boolean builtinOrDeemedType(ClassNode fieldType, 
List<String> knownImmutableClasses) {
+        return isBuiltinImmutable(fieldType.getName()) || 
knownImmutableClasses.contains(fieldType.getName()) || 
hasImmutableAnnotation(fieldType);
+    }
+
+    private static boolean hasImmutableAnnotation(ClassNode type) {
+        List<AnnotationNode> annotations = type.getAnnotations();
+        for (AnnotationNode next : annotations) {
+            String name = next.getClassNode().getName();
+            if (matchingMarkerName(name)) return true;
+        }
+        return false;
+    }
+
+    private static boolean matchingMarkerName(String name) {
+        return name.equals("groovy.transform.Immutable") || 
name.equals(KNOWN_IMMUTABLE_NAME);
+    }
+
+    public static boolean isBuiltinImmutable(String typeName) {
+        return builtinImmutables.contains(typeName);
+    }
+
+    private static boolean hasImmutableAnnotation(Class clazz) {
+        Annotation[] annotations = clazz.getAnnotations();
+        for (Annotation next : annotations) {
+            String name = next.annotationType().getName();
+            if (matchingMarkerName(name)) return true;
+        }
+        return false;
+    }
+
+    public static boolean builtinOrMarkedImmutableClass(Class<?> clazz) {
+        return isBuiltinImmutable(clazz.getName()) || 
hasImmutableAnnotation(clazz);
+    }
+
+    public static List<String> getKnownImmutables(AbstractASTTransformation 
xform, AnnotationNode node) {
+        final List<String> immutables = new ArrayList<String>();
+
+        if (node == null) return immutables;
+        final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLES);
+        if (expression == null) return immutables;
+
+        if (!(expression instanceof ListExpression)) {
+            xform.addError("Use the Groovy list notation [el1, el2] to specify 
known immutable property names via \"" + MEMBER_KNOWN_IMMUTABLES + "\"", node);
+            return immutables;
+        }
+
+        final ListExpression listExpression = (ListExpression) expression;
+        for (Expression listItemExpression : listExpression.getExpressions()) {
+            if (listItemExpression instanceof ConstantExpression) {
+                immutables.add((String) ((ConstantExpression) 
listItemExpression).getValue());
+            }
+        }
+
+        return immutables;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java 
b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
index e4ea892..875cc48 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -472,30 +472,38 @@ public class GeneralUtils {
     }
 
     public static List<PropertyNode> getAllProperties(Set<String> names, 
ClassNode origType, ClassNode cNode, boolean includeProperties, boolean 
includeFields, boolean includePseudoGetters, boolean includePseudoSetters, 
boolean traverseSuperClasses, boolean skipReadonly) {
-        return getAllProperties(names, origType, cNode, includeProperties, 
includeFields, includePseudoGetters, includePseudoSetters, 
traverseSuperClasses, skipReadonly, false);
+        return getAllProperties(names, origType, cNode, includeProperties, 
includeFields, includePseudoGetters, includePseudoSetters, 
traverseSuperClasses, skipReadonly, false, false, false);
     }
 
-    public static List<PropertyNode> getAllProperties(Set<String> names, 
ClassNode origType, ClassNode cNode, boolean includeProperties, boolean 
includeFields, boolean includePseudoGetters, boolean includePseudoSetters, 
boolean traverseSuperClasses, boolean skipReadonly, boolean reverse) {
+    public static List<PropertyNode> getAllProperties(Set<String> names, 
ClassNode origType, ClassNode cNode, boolean includeProperties,
+                                                      boolean includeFields, 
boolean includePseudoGetters, boolean includePseudoSetters,
+                                                      boolean 
traverseSuperClasses, boolean skipReadonly, boolean reverse, boolean allNames, 
boolean includeStatic) {
         final List<PropertyNode> result = new ArrayList<PropertyNode>();
         if (cNode != ClassHelper.OBJECT_TYPE && traverseSuperClasses && 
!reverse) {
             result.addAll(getAllProperties(names, origType, 
cNode.getSuperClass(), includeProperties, includeFields, includePseudoGetters, 
includePseudoSetters, true, skipReadonly));
         }
         if (includeProperties) {
             for (PropertyNode pNode : cNode.getProperties()) {
-                if (!pNode.isStatic() && !names.contains(pNode.getName())) {
+                if ((!pNode.isStatic() || includeStatic) && 
!names.contains(pNode.getName())) {
                     result.add(pNode);
                     names.add(pNode.getName());
                 }
             }
             if (includePseudoGetters || includePseudoSetters) {
-                BeanUtils.addPseudoProperties(origType, cNode, result, names, 
false, includePseudoGetters, includePseudoSetters);
+                BeanUtils.addPseudoProperties(origType, cNode, result, names, 
includeStatic, includePseudoGetters, includePseudoSetters);
             }
         }
         if (includeFields) {
             for (FieldNode fNode : cNode.getFields()) {
-                if (fNode.isStatic() || cNode.getProperty(fNode.getName()) != 
null || names.contains(fNode.getName())) {
+                if ((fNode.isStatic() && !includeStatic) || 
fNode.isSynthetic() || cNode.getProperty(fNode.getName()) != null || 
names.contains(fNode.getName())) {
                     continue;
                 }
+
+                // internal field
+                if (fNode.getName().contains("$") && !allNames) {
+                    continue;
+                }
+
                 if (fNode.isPrivate() && !cNode.equals(origType)) {
                     continue;
                 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/69fc7ab3/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java 
b/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java
index 4498e95..23cadd6 100644
--- a/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java
@@ -75,6 +75,15 @@ public abstract class AbstractASTTransformation implements 
Opcodes, ASTTransform
         return copiedAnnotations;
     }
 
+    /**
+     * If the transform is associated with a single annotation, returns a name 
suitable for displaying in error messages.
+     *
+     * @return The simple name of the annotation including the "@" or null if 
no such name is defined
+     */
+    public String getAnnotationName() {
+        return null;
+    }
+
     protected void init(ASTNode[] nodes, SourceUnit sourceUnit) {
         if (nodes == null || nodes.length != 2 || !(nodes[0] instanceof 
AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
             throw new GroovyBugError("Internal error: expecting 
[AnnotationNode, AnnotatedNode] but got: " + (nodes == null ? null : 
Arrays.asList(nodes)));
@@ -434,15 +443,15 @@ public abstract class AbstractASTTransformation 
implements Opcodes, ASTTransform
     }
 
     protected boolean checkPropertyList(ClassNode cNode, List<String> 
propertyNameList, String listName, AnnotationNode anno, String typeName, 
boolean includeFields, boolean includeSuperProperties, boolean allProperties) {
-        return checkPropertyList(cNode, propertyNameList, listName, anno, 
typeName, includeFields, includeSuperProperties, allProperties, false);
+        return checkPropertyList(cNode, propertyNameList, listName, anno, 
typeName, includeFields, includeSuperProperties, allProperties, false, false);
     }
 
-    protected boolean checkPropertyList(ClassNode cNode, List<String> 
propertyNameList, String listName, AnnotationNode anno, String typeName, 
boolean includeFields, boolean includeSuperProperties, boolean allProperties, 
boolean includeSuperFields) {
+    protected boolean checkPropertyList(ClassNode cNode, List<String> 
propertyNameList, String listName, AnnotationNode anno, String typeName, 
boolean includeFields, boolean includeSuperProperties, boolean allProperties, 
boolean includeSuperFields, boolean includeStatic) {
         if (propertyNameList == null || propertyNameList.isEmpty()) {
             return true;
         }
         final List<String> pNames = new ArrayList<String>();
-        for (PropertyNode pNode : BeanUtils.getAllProperties(cNode, 
includeSuperProperties, false, allProperties)) {
+        for (PropertyNode pNode : BeanUtils.getAllProperties(cNode, 
includeSuperProperties, includeStatic, allProperties)) {
             pNames.add(pNode.getField().getName());
         }
         boolean result = true;

Reply via email to