GROOVY-8440: Groovy's @Immutable annotation should be re-vamped to be a 
meta-annotation (additional refactoring to remove dup and plumb in pre/post 
conditions)


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

Branch: refs/heads/GROOVY_2_6_X
Commit: 8cd0820f988db59a668daf71af0cf07ade15e8c8
Parents: 4be8c77
Author: paulk <pa...@asert.com.au>
Authored: Mon Jan 29 23:57:26 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Wed Jan 31 22:40:31 2018 +1000

----------------------------------------------------------------------
 .../groovy/groovy/transform/Immutable.groovy    |   4 +-
 .../groovy/groovy/transform/MapConstructor.java |   5 -
 .../groovy/transform/TupleConstructor.java      |   7 --
 .../codehaus/groovy/ast/tools/GeneralUtils.java |  17 +++
 .../transform/ImmutableASTTransformation.java   | 107 +++++++++----------
 .../MapConstructorASTTransformation.java        |  69 +++++++-----
 .../TupleConstructorASTTransformation.java      |  25 +++--
 7 files changed, 127 insertions(+), 107 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/8cd0820f/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 e59342b..3fa518a 100644
--- a/src/main/groovy/groovy/transform/Immutable.groovy
+++ b/src/main/groovy/groovy/transform/Immutable.groovy
@@ -174,8 +174,8 @@ package groovy.transform
 @ToString(cache = true, includeSuperProperties = true)
 @EqualsAndHashCode(cache = true)
 @ImmutableBase
-@TupleConstructor(makeImmutable = true, defaults = false)
-@MapConstructor(makeImmutable = true, noArg = true, includeSuperProperties = 
true)
+@TupleConstructor(defaults = false)
+@MapConstructor(noArg = true, includeSuperProperties = true)
 @KnownImmutable
 @AnnotationCollector(mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED)
 @interface Immutable { }

http://git-wip-us.apache.org/repos/asf/groovy/blob/8cd0820f/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 55dee78..d4d5657 100644
--- a/src/main/groovy/groovy/transform/MapConstructor.java
+++ b/src/main/groovy/groovy/transform/MapConstructor.java
@@ -96,11 +96,6 @@ public @interface MapConstructor {
     boolean includeFields() default false;
 
     /**
-     * Whether immutable pre-cautions (defensive copying, cloning, etc.) 
should be applied to incoming/outgoing properties.
-     */
-    boolean makeImmutable() default false;
-
-    /**
      * Include properties in the constructor.
      */
     boolean includeProperties() default true;

http://git-wip-us.apache.org/repos/asf/groovy/blob/8cd0820f/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 bad5518..eb7cb6e 100644
--- a/src/main/groovy/groovy/transform/TupleConstructor.java
+++ b/src/main/groovy/groovy/transform/TupleConstructor.java
@@ -232,13 +232,6 @@ public @interface TupleConstructor {
     boolean force() default false;
 
     /**
-     * Whether immutable pre-cautions (defensive copying, cloning, etc.) 
should be applied to incoming/outgoing properties.
-     *
-     * @since 2.5.0
-     */
-    boolean makeImmutable() default false;
-
-    /**
      * Used to set whether default value processing is enabled (the default) 
or disabled.
      *
      * By default, every constructor parameter is given a default value. This 
value will

http://git-wip-us.apache.org/repos/asf/groovy/blob/8cd0820f/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 4c55515..8c949e7 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -42,6 +42,7 @@ import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
 import org.codehaus.groovy.ast.expr.DeclarationExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.FieldExpression;
+import org.codehaus.groovy.ast.expr.ListExpression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.expr.NotExpression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
@@ -604,6 +605,22 @@ public class GeneralUtils {
         return new BooleanExpression(new BinaryExpression(expr, EQ, new 
ConstantExpression(0)));
     }
 
+    public static ListExpression list2args(List args) {
+        ListExpression result = new ListExpression();
+        for (Object o : args) {
+            result.addExpression(new ConstantExpression(o));
+        }
+        return result;
+    }
+
+    public static ListExpression classList2args(List<String> args) {
+        ListExpression result = new ListExpression();
+        for (Object o : args) {
+            result.addExpression(new 
ClassExpression(ClassHelper.make(o.toString())));
+        }
+        return result;
+    }
+
     public static BinaryExpression ltX(Expression lhv, Expression rhv) {
         return new BinaryExpression(lhv, LT, rhv);
     }

http://git-wip-us.apache.org/repos/asf/groovy/blob/8cd0820f/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java 
b/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
index dcbe6d0..5536783 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
@@ -73,6 +73,7 @@ import static 
org.codehaus.groovy.ast.tools.GeneralUtils.block;
 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.createConstructorStatementDefault;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
@@ -89,6 +90,7 @@ import static 
org.codehaus.groovy.ast.tools.GeneralUtils.isInstanceOfX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.isOneX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.isTrueX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.list2args;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.neX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.notX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.orX;
@@ -136,7 +138,6 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
     private static final Class MY_CLASS = ImmutableBase.class;
     private static final Class<? extends Annotation> KNOWN_IMMUTABLE_CLASS = 
KnownImmutable.class;
     private static final Class<? extends Annotation> IMMUTABLE_BASE_CLASS = 
ImmutableBase.class;
-    private static final ClassNode IMMUTABLE_BASE_TYPE = 
makeWithoutCaching(IMMUTABLE_BASE_CLASS, false);
     public static final ClassNode MY_TYPE = make(MY_CLASS);
     static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
     private static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = 
"knownImmutableClasses";
@@ -192,10 +193,8 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         for (FieldNode fNode : fList) {
             ensureNotPublic(this, cName, fNode);
         }
-        boolean includeSuperProperties = false;
         if (hasAnnotation(cNode, TupleConstructorASTTransformation.MY_TYPE)) {
             AnnotationNode tupleCons = 
cNode.getAnnotations(TupleConstructorASTTransformation.MY_TYPE).get(0);
-//            includeSuperProperties = memberHasValue(tupleCons, 
"includeSuperProperties", true);
             if (unsupportedTupleAttribute(tupleCons, "excludes")) return;
             if (unsupportedTupleAttribute(tupleCons, "includes")) return;
             if (unsupportedTupleAttribute(tupleCons, "includeFields")) return;
@@ -205,7 +204,6 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
             if (unsupportedTupleAttribute(tupleCons, "force")) return;
         }
         if (!validateConstructors(cNode)) return;
-//        createConstructors(this, cNode, knownImmutableClasses, 
knownImmutables, includeSuperProperties);
         if (memberHasValue(node, MEMBER_ADD_COPY_WITH, true) && 
!pList.isEmpty() &&
                 !hasDeclaredMethod(cNode, COPY_WITH_METHOD, 1)) {
             createCopyWith(cNode, pList);
@@ -255,6 +253,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
     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;
 
@@ -276,6 +275,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
     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;
 
@@ -305,17 +305,6 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         }
     }
 
-    private static void createConstructors(AbstractASTTransformation xform, 
ClassNode cNode, List<String> knownImmutableClasses, List<String> 
knownImmutables, boolean includeSuperProperties, boolean allProperties) {
-        List<PropertyNode> list = getProperties(cNode, includeSuperProperties, 
allProperties);
-        boolean specialHashMapCase = isSpecialHashMapCase(list);
-        if (specialHashMapCase) {
-            createConstructorMapSpecial(cNode, list);
-        } else {
-            createConstructorMap(xform, cNode, list, knownImmutableClasses, 
knownImmutables);
-            createConstructorOrdered(cNode, list);
-        }
-    }
-
     static boolean isSpecialHashMapCase(List<PropertyNode> list) {
         return list.size() == 1 && 
list.get(0).getField().getType().equals(HASHMAP_TYPE);
     }
@@ -388,39 +377,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         return castX(type, smce);
     }
 
-    static void createConstructorMapSpecial(ClassNode cNode, 
List<PropertyNode> list) {
-        final BlockStatement body = new BlockStatement();
-        
body.addStatement(createConstructorStatementMapSpecial(list.get(0).getField()));
-        createConstructorMapCommon(cNode, body);
-    }
-
-    static void createConstructorMap(AbstractASTTransformation xform, 
ClassNode cNode, List<PropertyNode> list) {
-        AnnotationNode annoImmutable = 
cNode.getAnnotations(IMMUTABLE_BASE_TYPE).get(0);
-        final List<String> knownImmutableClasses = 
getKnownImmutableClasses(xform, annoImmutable);
-        final List<String> knownImmutables = getKnownImmutables(xform, 
annoImmutable);
-        createConstructorMap(xform, cNode, list, knownImmutableClasses, 
knownImmutables);
-    }
-
-    private static void createConstructorMap(AbstractASTTransformation xform, 
ClassNode cNode, List<PropertyNode> list, List<String> knownImmutableClasses, 
List<String> knownImmutables) {
-        final BlockStatement body = new BlockStatement();
-        body.addStatement(ifS(equalsNullX(varX("args")), assignS(varX("args"), 
new MapExpression())));
-        for (PropertyNode pNode : list) {
-            body.addStatement(createConstructorStatement(xform, cNode, pNode, 
knownImmutableClasses, knownImmutables));
-        }
-        // check for missing properties
-        body.addStatement(stmt(callX(SELF_TYPE, "checkPropNames", args("this", 
"args"))));
-        createConstructorMapCommon(cNode, body);
-        if (!list.isEmpty()) {
-            createNoArgConstructor(cNode);
-        }
-    }
-
-    private static void createNoArgConstructor(ClassNode cNode) {
-        Statement body = stmt(ctorX(ClassNode.THIS, args(new 
MapExpression())));
-        doAddConstructor(cNode, new ConstructorNode(ACC_PUBLIC, 
Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body));
-    }
-
-    private static void createConstructorMapCommon(ClassNode cNode, 
BlockStatement body) {
+    static void createConstructorMapCommon(ClassNode cNode, BlockStatement 
body) {
         final List<FieldNode> fList = cNode.getFields();
         for (FieldNode fNode : fList) {
             if (fNode.isPublic()) continue; // public fields will be rejected 
elsewhere
@@ -444,7 +401,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
                 )));
     }
 
-    private static Statement createConstructorStatementMapSpecial(FieldNode 
fNode) {
+    static Statement createConstructorStatementMapSpecial(FieldNode fNode) {
         final Expression fieldExpr = varX(fNode);
         final ClassNode fieldType = fieldExpr.getType();
         final Expression initExpr = fNode.getInitialValueExpression();
@@ -505,7 +462,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         return true;
     }
 
-    private static Statement 
createConstructorStatement(AbstractASTTransformation xform, ClassNode cNode, 
PropertyNode pNode, List<String> knownImmutableClasses, List<String> 
knownImmutables) {
+    static Statement createConstructorStatement(AbstractASTTransformation 
xform, ClassNode cNode, PropertyNode pNode, List<String> knownImmutables, 
List<String> knownImmutableClasses) {
         FieldNode fNode = pNode.getField();
         final ClassNode fieldType = fNode.getType();
         Statement statement;
@@ -521,26 +478,26 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
             xform.addError(createErrorMessage(cNode.getName(), 
fNode.getName(), fieldType.getName(), "compiling"), fNode);
             statement = EmptyStatement.INSTANCE;
         } else {
-            statement = createConstructorStatementGuarded(cNode, fNode);
+            statement = createConstructorStatementGuarded(cNode, fNode, 
knownImmutables, knownImmutableClasses);
         }
         return statement;
     }
 
-    private static Statement createConstructorStatementGuarded(ClassNode 
cNode, FieldNode fNode) {
+    private static Statement createConstructorStatementGuarded(ClassNode 
cNode, FieldNode fNode, List<String> knownImmutables, List<String> 
knownImmutableClasses) {
         final Expression fieldExpr = varX(fNode);
         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));
+            assignInit = assignS(fieldExpr, checkUnresolved(fNode, initExpr, 
knownImmutables, knownImmutableClasses));
         }
         Expression unknown = findArg(fNode.getName());
-        return ifElseS(equalsNullX(unknown), assignInit, assignS(fieldExpr, 
checkUnresolved(fNode, unknown)));
+        return ifElseS(equalsNullX(unknown), assignInit, assignS(fieldExpr, 
checkUnresolved(fNode, unknown, knownImmutables, knownImmutableClasses)));
     }
 
-    private static Expression checkUnresolved(FieldNode fNode, Expression 
value) {
-        Expression args = args(callThisX("getClass"), constX(fNode.getName()), 
value);
+    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);
     }
 
@@ -794,6 +751,44 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
     }
 
     @SuppressWarnings("Unchecked")
+    public static Object checkImmutable(Class<?> clazz, String fieldName, 
Object field, List<String> knownImmutableFieldNames, List<Class> 
knownImmutableClasses) {
+        if (field == null || field instanceof Enum || 
knownImmutable(field.getClass()) || 
knownImmutableFieldNames.contains(fieldName) || 
knownImmutableClasses.contains(field.getClass())) {
+            return field;
+        }
+
+        boolean isImmutable = false;
+        for (Annotation an : field.getClass().getAnnotations()) {
+            if 
(an.getClass().getName().startsWith("groovy.transform.Immutable")) {
+                isImmutable = true;
+                break;
+            }
+        }
+        if (isImmutable) return field;
+
+        if (field instanceof Collection) {
+            Field declaredField;
+            try {
+                declaredField = clazz.getDeclaredField(fieldName);
+                Class<?> fieldType = declaredField.getType();
+                if (Collection.class.isAssignableFrom(fieldType)) {
+                    return DefaultGroovyMethods.asImmutable((Collection) 
field);
+                }
+                // potentially allow Collection coercion for a constructor
+                if (knownImmutable(fieldType)) {
+                    return field;
+                }
+            } catch (NoSuchFieldException ignore) {
+                // ignore
+            }
+        }
+        final String typeName = field.getClass().getName();
+        throw new RuntimeException(createErrorMessage(clazz.getName(), 
fieldName, typeName, "constructing"));
+    }
+
+    /*
+     * For compatibility with pre 2.5 compiled classes
+     */
+    @SuppressWarnings("Unchecked")
     public static Object checkImmutable(Class<?> clazz, String fieldName, 
Object field) {
         if (field == null || field instanceof Enum || 
knownImmutable(field.getClass())) {
             return field;

http://git-wip-us.apache.org/repos/asf/groovy/blob/8cd0820f/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
 
b/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
index fb2f94b..8f24342 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
@@ -18,6 +18,7 @@
  */
 package org.codehaus.groovy.transform;
 
+import groovy.transform.ImmutableBase;
 import groovy.transform.MapConstructor;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotatedNode;
@@ -26,7 +27,6 @@ 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.PropertyNode;
 import org.codehaus.groovy.ast.expr.ArgumentListExpression;
@@ -40,6 +40,7 @@ import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.SourceUnit;
 
+import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -65,6 +66,11 @@ 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;
+import static 
org.codehaus.groovy.transform.ImmutableASTTransformation.createConstructorMapCommon;
+import static 
org.codehaus.groovy.transform.ImmutableASTTransformation.createConstructorStatement;
+import static 
org.codehaus.groovy.transform.ImmutableASTTransformation.createConstructorStatementMapSpecial;
+import static 
org.codehaus.groovy.transform.ImmutableASTTransformation.getKnownImmutableClasses;
+import static 
org.codehaus.groovy.transform.ImmutableASTTransformation.getKnownImmutables;
 
 /**
  * Handles generation of code for the @MapConstructor annotation.
@@ -76,6 +82,9 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
     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 Class<? extends Annotation> IMMUTABLE_BASE_CLASS = 
ImmutableBase.class;
+    private static final ClassNode IMMUTABLE_BASE_TYPE = 
makeWithoutCaching(IMMUTABLE_BASE_CLASS, false);
+    private static final ClassNode IMMUTABLE_XFORM_TYPE = 
make(ImmutableASTTransformation.class);
 
     public void visit(ASTNode[] nodes, SourceUnit source) {
         init(nodes, source);
@@ -96,7 +105,6 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
             List<String> excludes = getMemberStringList(anno, "excludes");
             List<String> includes = getMemberStringList(anno, "includes");
             boolean allNames = memberHasValue(anno, "allNames", true);
-            boolean makeImmutable = memberHasValue(anno, "makeImmutable", 
true);
             if (!checkIncludeExcludeUndefinedAware(anno, excludes, includes, 
MY_TYPE_NAME)) return;
             if (!checkPropertyList(cNode, includes, "includes", anno, 
MY_TYPE_NAME, includeFields, includeSuperProperties, false))
                 return;
@@ -114,7 +122,7 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
                 return;
             }
 
-            createConstructors(this, cNode, includeFields, includeProperties, 
includeSuperProperties, includeSuperFields, useSetters, noArg, allNames, 
allProperties, makeImmutable, excludes, includes, (ClosureExpression) pre, 
(ClosureExpression) post, source);
+            createConstructors(this, cNode, includeFields, includeProperties, 
includeSuperProperties, includeSuperFields, useSetters, noArg, allNames, 
allProperties, excludes, includes, (ClosureExpression) pre, (ClosureExpression) 
post, source);
 
             if (pre != null) {
                 anno.setMember("pre", new ClosureExpression(new Parameter[0], 
EmptyStatement.INSTANCE));
@@ -125,7 +133,7 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
         }
     }
 
-    private static void createConstructors(AbstractASTTransformation xform, 
ClassNode cNode, boolean includeFields, boolean includeProperties, boolean 
includeSuperProperties, boolean includeSuperFields, boolean useSetters, boolean 
noArg, boolean allNames, boolean allProperties, boolean makeImmutable, 
List<String> excludes, List<String> includes, ClosureExpression pre, 
ClosureExpression post, SourceUnit source) {
+    private static void createConstructors(AbstractASTTransformation xform, 
ClassNode cNode, boolean includeFields, boolean includeProperties, boolean 
includeSuperProperties, boolean includeSuperFields, boolean useSetters, boolean 
noArg, boolean allNames, boolean allProperties, 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
@@ -140,17 +148,11 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
         }
         List<PropertyNode> list = getAllProperties(names, cNode, true, 
includeFields, allProperties, false, true);
 
-        if (makeImmutable) {
-            boolean specialHashMapCase = 
(ImmutableASTTransformation.isSpecialHashMapCase(list) && superList.isEmpty()) 
||
-                    
(ImmutableASTTransformation.isSpecialHashMapCase(superList) && list.isEmpty());
-            superList.addAll(list);
-            if (specialHashMapCase) {
-                ImmutableASTTransformation.createConstructorMapSpecial(cNode, 
superList);
-            } else {
-                ImmutableASTTransformation.createConstructorMap(xform, cNode, 
superList);
-            }
-            return;
-        }
+        List<AnnotationNode> annotations = 
cNode.getAnnotations(IMMUTABLE_BASE_TYPE);
+        AnnotationNode annoImmutable = annotations.isEmpty() ? null : 
annotations.get(0);
+        boolean makeImmutable = annoImmutable != null;
+        final List<String> knownImmutableClasses = 
getKnownImmutableClasses(xform, annoImmutable);
+        final List<String> knownImmutables = getKnownImmutables(xform, 
annoImmutable);
 
         Parameter map = param(MAP_TYPE, "args");
         final BlockStatement body = new BlockStatement();
@@ -160,27 +162,42 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
             copyStatementsWithSuperAdjustment(transformed, body);
         }
         final BlockStatement inner = new BlockStatement();
-        for (PropertyNode pNode : superList) {
-            String name = pNode.getName();
-            if (shouldSkip(name, excludes, includes, allNames)) continue;
-            assignField(useSetters, map, inner, name);
-        }
-        for (PropertyNode pNode : list) {
-            String name = pNode.getName();
-            if (shouldSkip(name, excludes, includes, allNames)) continue;
-            assignField(useSetters, map, inner, name);
+        superList.addAll(list);
+        boolean specialHashMapCase = 
ImmutableASTTransformation.isSpecialHashMapCase(superList);
+        if (!specialHashMapCase) {
+            processProps(xform, cNode, makeImmutable && !specialHashMapCase, 
useSetters, allNames, excludes, includes, superList, map, inner, 
knownImmutables, knownImmutableClasses);
         }
         body.addStatement(ifS(notNullX(varX("args")), inner));
         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));
-        if (noArg && !(list.isEmpty() && superList.isEmpty()) && 
!hasNoArgConstructor(cNode)) {
+        if (makeImmutable && !specialHashMapCase) {
+            body.addStatement(stmt(callX(IMMUTABLE_XFORM_TYPE, 
"checkPropNames", args("this", "args"))));
+            createConstructorMapCommon(cNode, body);
+        } else if (specialHashMapCase) {
+            
inner.addStatement(createConstructorStatementMapSpecial(superList.get(0).getField()));
+            createConstructorMapCommon(cNode, body);
+        } else {
+            cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, params(map), 
ClassNode.EMPTY_ARRAY, body));
+        }
+        if (noArg && !superList.isEmpty() && !hasNoArgConstructor(cNode) && 
!specialHashMapCase) {
             createNoArgConstructor(cNode);
         }
     }
 
+    private static void processProps(AbstractASTTransformation xform, 
ClassNode cNode, boolean makeImmutable, boolean useSetters, boolean allNames, 
List<String> excludes, List<String> includes, List<PropertyNode> superList, 
Parameter map, BlockStatement inner, List<String> knownImmutables, List<String> 
knownImmutableClasses) {
+        for (PropertyNode pNode : superList) {
+            String name = pNode.getName();
+            if (shouldSkip(name, excludes, includes, allNames)) continue;
+            if (makeImmutable) {
+                inner.addStatement(createConstructorStatement(xform, cNode, 
pNode, knownImmutables, knownImmutableClasses));
+            } else {
+                assignField(useSetters, map, inner, name);
+            }
+        }
+    }
+
     private static void createNoArgConstructor(ClassNode cNode) {
         Statement body = stmt(ctorX(ClassNode.THIS, args(new 
MapExpression())));
         cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, 
Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body));

http://git-wip-us.apache.org/repos/asf/groovy/blob/8cd0820f/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
 
b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
index ec6a4c0..5f0e381 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
@@ -18,7 +18,7 @@
  */
 package org.codehaus.groovy.transform;
 
-import groovy.transform.KnownImmutable;
+import groovy.transform.ImmutableBase;
 import groovy.transform.TupleConstructor;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotatedNode;
@@ -40,6 +40,7 @@ import org.codehaus.groovy.classgen.VariableScopeVisitor;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.SourceUnit;
 
+import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -83,7 +84,8 @@ public class TupleConstructorASTTransformation extends 
AbstractASTTransformation
     private static final ClassNode LHMAP_TYPE = 
makeWithoutCaching(LinkedHashMap.class, false);
     private static final ClassNode HMAP_TYPE = 
makeWithoutCaching(HashMap.class, false);
     private static final ClassNode CHECK_METHOD_TYPE = 
make(ImmutableASTTransformation.class);
-    private static final ClassNode IMMUTABLE_CLASS_TYPE = 
makeWithoutCaching(KnownImmutable.class, false);
+    private static final Class<? extends Annotation> IMMUTABLE_BASE_CLASS = 
ImmutableBase.class;
+    private static final ClassNode IMMUTABLE_BASE_TYPE = 
makeWithoutCaching(IMMUTABLE_BASE_CLASS, false);
     private static final Map<Class<?>, Expression> primitivesInitialValues;
 
     static {
@@ -118,7 +120,6 @@ public class TupleConstructorASTTransformation extends 
AbstractASTTransformation
             boolean defaults = !memberHasValue(anno, "defaults", false);
             boolean useSetters = memberHasValue(anno, "useSetters", true);
             boolean allProperties = memberHasValue(anno, "allProperties", 
true);
-            boolean makeImmutable = memberHasValue(anno, "makeImmutable", 
true);
             List<String> excludes = getMemberStringList(anno, "excludes");
             List<String> includes = getMemberStringList(anno, "includes");
             boolean allNames = memberHasValue(anno, "allNames", true);
@@ -137,7 +138,7 @@ public class TupleConstructorASTTransformation extends 
AbstractASTTransformation
             }
 
             createConstructor(this, cNode, includeFields, includeProperties, 
includeSuperFields, includeSuperProperties,
-                    callSuper, force, excludes, includes, useSetters, 
defaults, allNames, allProperties, makeImmutable,
+                    callSuper, force, excludes, includes, useSetters, 
defaults, allNames, allProperties,
                     sourceUnit, (ClosureExpression) pre, (ClosureExpression) 
post);
 
             if (pre != null) {
@@ -163,18 +164,17 @@ 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, final 
List<String> includes, boolean useSetters, boolean
-                                                 defaults, boolean allNames, 
SourceUnit sourceUnit, ClosureExpression
-                                                 pre, ClosureExpression post) {
-        createConstructor(xform, cNode, includeFields, includeProperties, 
includeSuperFields, includeSuperProperties, callSuper, force, excludes, 
includes, useSetters, defaults, allNames, false, false, sourceUnit, pre, post);
+                                         boolean includeProperties, boolean 
includeSuperFields, boolean includeSuperProperties,
+                                         boolean callSuper, boolean force, 
List<String> excludes, final List<String> includes,
+                                         boolean useSetters, boolean defaults, 
boolean allNames,
+                                         SourceUnit sourceUnit, 
ClosureExpression pre, ClosureExpression post) {
+        createConstructor(xform, cNode, includeFields, includeProperties, 
includeSuperFields, includeSuperProperties, callSuper, force, excludes, 
includes, useSetters, defaults, allNames,false, sourceUnit, pre, post);
     }
 
     public static void createConstructor(AbstractASTTransformation xform, 
ClassNode cNode, boolean includeFields,
                                          boolean includeProperties, boolean 
includeSuperFields, boolean includeSuperProperties,
                                          boolean callSuper, boolean force, 
List<String> excludes, final List<String> includes,
-                                         boolean useSetters, boolean defaults, 
boolean allNames, boolean allProperties, boolean makeImmutable,
+                                         boolean useSetters, boolean defaults, 
boolean allNames, boolean allProperties,
                                          SourceUnit sourceUnit, 
ClosureExpression pre, ClosureExpression post) {
         Set<String> names = new HashSet<String>();
         List<PropertyNode> superList;
@@ -186,6 +186,9 @@ public class TupleConstructorASTTransformation extends 
AbstractASTTransformation
 
         List<PropertyNode> list = getAllProperties(names, cNode, true, 
includeFields, allProperties, false, true);
 
+        List<AnnotationNode> annotations = 
cNode.getAnnotations(IMMUTABLE_BASE_TYPE);
+        AnnotationNode annoImmutable = annotations.isEmpty() ? null : 
annotations.get(0);
+        boolean makeImmutable = annoImmutable != null;
         if (makeImmutable) {
             boolean specialHashMapCase = 
(ImmutableASTTransformation.isSpecialHashMapCase(list) && superList.isEmpty()) 
||
                     
(ImmutableASTTransformation.isSpecialHashMapCase(superList) && list.isEmpty());

Reply via email to