You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2018/02/16 10:28:00 UTC

[1/2] groovy git commit: GROOVY-8477: @Immutable-related transformations should be more configurable (further refactoring)

Repository: groovy
Updated Branches:
  refs/heads/master 122dc3057 -> e2f4ceb50


http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java b/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java
index 5d286c7..cba687e 100644
--- a/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java
+++ b/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java
@@ -144,8 +144,8 @@ public class EnumCompletionVisitor extends ClassCodeVisitorSupport {
     }
 
     private static void addMapConstructors(ClassNode enumClass) {
-        TupleConstructorASTTransformation.addSpecialMapConstructors(enumClass, true, "One of the enum constants for enum " + enumClass.getName() +
-                " was initialized with null. Please use a non-null value or define your own constructor.");
+        TupleConstructorASTTransformation.addSpecialMapConstructors(enumClass, "One of the enum constants for enum " + enumClass.getName() +
+                " was initialized with null. Please use a non-null value or define your own constructor.", true);
     }
 
     private String getUniqueVariableName(final String name, Statement code) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/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 23cadd6..8c1de6c 100644
--- a/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java
@@ -438,15 +438,15 @@ public abstract class AbstractASTTransformation implements Opcodes, ASTTransform
         }
     }
 
-    protected boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields) {
+    public boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields) {
         return checkPropertyList(cNode, propertyNameList, listName, anno, typeName, includeFields, false, false);
     }
 
-    protected boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields, boolean includeSuperProperties, boolean allProperties) {
+    public 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, false);
     }
 
-    protected boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields, boolean includeSuperProperties, boolean allProperties, boolean includeSuperFields, boolean includeStatic) {
+    public 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;
         }

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/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 ad4001f..1cd79c6 100644
--- a/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
@@ -18,14 +18,16 @@
  */
 package org.codehaus.groovy.transform;
 
+import groovy.lang.GroovyClassLoader;
 import groovy.lang.MetaClass;
 import groovy.lang.MissingPropertyException;
+import groovy.transform.CompilationUnitAware;
 import groovy.transform.ImmutableBase;
+import groovy.transform.options.PropertyHandler;
 import org.apache.groovy.ast.tools.ImmutablePropertyUtils;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
-import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
 import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.ConstructorNode;
@@ -34,11 +36,9 @@ import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.PropertyNode;
 import org.codehaus.groovy.ast.VariableScope;
 import org.codehaus.groovy.ast.expr.ConstantExpression;
-import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.MapExpression;
-import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.SourceUnit;
 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
@@ -53,19 +53,13 @@ import java.util.List;
 import java.util.Map;
 
 import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.builtinOrMarkedImmutableClass;
-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.createErrorMessage;
-import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.derivesFromDate;
-import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.getKnownImmutables;
-import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.implementsCloneable;
 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.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.constX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
@@ -81,7 +75,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.neX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.orX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.safeExpression;
 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.varX;
@@ -90,7 +83,9 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
  * Handles generation of code for the @Immutable annotation.
  */
 @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
-public class ImmutableASTTransformation extends AbstractASTTransformation {
+public class ImmutableASTTransformation extends AbstractASTTransformation implements CompilationUnitAware {
+    private CompilationUnit compilationUnit;
+
     private static final Class<? extends Annotation> MY_CLASS = ImmutableBase.class;
     public static final ClassNode MY_TYPE = makeWithoutCaching(MY_CLASS, false);
     private static final String MY_TYPE_NAME = MY_TYPE.getNameWithoutPackage();
@@ -101,29 +96,36 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
     private static final ClassNode HMAP_TYPE = makeWithoutCaching(HashMap.class, false);
     public static final String IMMUTABLE_SAFE_FLAG = "Immutable.Safe";
 
+    @Override
+    public String getAnnotationName() {
+        return MY_TYPE_NAME;
+    }
+
     public void visit(ASTNode[] nodes, SourceUnit source) {
         init(nodes, source);
         AnnotatedNode parent = (AnnotatedNode) nodes[1];
-        AnnotationNode node = (AnnotationNode) nodes[0];
-        if (!MY_TYPE.equals(node.getClassNode())) return;
+        AnnotationNode anno = (AnnotationNode) nodes[0];
+        if (!MY_TYPE.equals(anno.getClassNode())) return;
 
         if (parent instanceof ClassNode) {
-            doMakeImmutable((ClassNode) parent, node);
+            final GroovyClassLoader classLoader = compilationUnit != null ? compilationUnit.getTransformLoader() : source.getClassLoader();
+            final PropertyHandler handler = PropertyHandler.createPropertyHandler(this, classLoader, (ClassNode) parent);
+            if (handler == null) return;
+            if (!handler.validateAttributes(this, anno)) return;
+            doMakeImmutable((ClassNode) parent, anno, handler);
         }
     }
 
-    private void doMakeImmutable(ClassNode cNode, AnnotationNode node) {
+    private void doMakeImmutable(ClassNode cNode, AnnotationNode node, PropertyHandler handler) {
         List<PropertyNode> newProperties = new ArrayList<PropertyNode>();
-        final List<String> knownImmutables = getKnownImmutables(this, node);
 
         String cName = cNode.getName();
         if (!checkNotInterface(cNode, MY_TYPE_NAME)) return;
-        if (!checkPropertyList(cNode, knownImmutables, "knownImmutables", node, "immutable class", false)) return;
         makeClassFinal(this, cNode);
 
         final List<PropertyNode> pList = getInstanceProperties(cNode);
         for (PropertyNode pNode : pList) {
-            adjustPropertyForImmutability(pNode, newProperties);
+            adjustPropertyForImmutability(pNode, newProperties, handler);
         }
         for (PropertyNode pNode : newProperties) {
             cNode.getProperties().remove(pNode);
@@ -162,36 +164,6 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
         return false;
     }
 
-    static void doAddConstructor(final ClassNode cNode, final ConstructorNode constructorNode) {
-        cNode.addConstructor(constructorNode);
-        // GROOVY-5814: Immutable is not compatible with @CompileStatic
-        Parameter argsParam = null;
-        for (Parameter p : constructorNode.getParameters()) {
-            if ("args".equals(p.getName())) {
-                argsParam = p;
-                break;
-            }
-        }
-        if (argsParam != null) {
-            final Parameter arg = argsParam;
-            ClassCodeVisitorSupport variableExpressionFix = new ClassCodeVisitorSupport() {
-                @Override
-                protected SourceUnit getSourceUnit() {
-                    return cNode.getModule().getContext();
-                }
-
-                @Override
-                public void visitVariableExpression(final VariableExpression expression) {
-                    super.visitVariableExpression(expression);
-                    if ("args".equals(expression.getName())) {
-                        expression.setAccessedVariable(arg);
-                    }
-                }
-            };
-            variableExpressionFix.visitConstructor(constructorNode);
-        }
-    }
-
     private static void makeClassFinal(AbstractASTTransformation xform, ClassNode cNode) {
         int modifiers = cNode.getModifiers();
         if ((modifiers & ACC_FINAL) == 0) {
@@ -220,41 +192,6 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
         return false;
     }
 
-    @Deprecated
-    static List<PropertyNode> getProperties(ClassNode cNode, boolean includeSuperProperties, boolean allProperties) {
-        List<PropertyNode> list = getInstanceProperties(cNode);
-        if (includeSuperProperties) {
-            ClassNode next = cNode.getSuperClass();
-            while (next != null) {
-                List<PropertyNode> tail = list;
-                list = getInstanceProperties(next);
-                list.addAll(tail);
-                next = next.getSuperClass();
-            }
-        }
-        return list;
-    }
-
-    @Deprecated
-    static void createConstructorOrdered(ClassNode cNode, List<PropertyNode> list) {
-        final MapExpression argMap = new MapExpression();
-        final Parameter[] orderedParams = new Parameter[list.size()];
-        int index = 0;
-        for (PropertyNode pNode : list) {
-            Parameter param = new Parameter(pNode.getField().getType(), pNode.getField().getName());
-            orderedParams[index++] = param;
-            argMap.addMapEntryExpression(constX(pNode.getName()), varX(pNode.getName()));
-        }
-        final BlockStatement orderedBody = new BlockStatement();
-        orderedBody.addStatement(stmt(ctorX(ClassNode.THIS, args(castX(HMAP_TYPE, argMap)))));
-        doAddConstructor(cNode, new ConstructorNode(ACC_PUBLIC, orderedParams, ClassNode.EMPTY_ARRAY, orderedBody));
-    }
-
-    private static Statement createGetterBodyDefault(FieldNode fNode) {
-        final Expression fieldExpr = varX(fNode);
-        return stmt(fieldExpr);
-    }
-
     private static void ensureNotPublic(AbstractASTTransformation xform, String cNode, FieldNode fNode) {
         String fName = fNode.getName();
         // TODO: do we need to lock down things like: $ownClass
@@ -288,48 +225,20 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
     }
 
     static boolean makeImmutable(ClassNode cNode) {
-        List<AnnotationNode> annotations = cNode.getAnnotations(ImmutablePropertyUtils.IMMUTABLE_BASE_TYPE);
+        List<AnnotationNode> annotations = cNode.getAnnotations(ImmutablePropertyUtils.IMMUTABLE_OPTIONS_TYPE);
         AnnotationNode annoImmutable = annotations.isEmpty() ? null : annotations.get(0);
         return annoImmutable != null;
     }
 
-    private static void adjustPropertyForImmutability(PropertyNode pNode, List<PropertyNode> newNodes) {
+    private static void adjustPropertyForImmutability(PropertyNode pNode, List<PropertyNode> newNodes, PropertyHandler handler) {
         final FieldNode fNode = pNode.getField();
         fNode.setModifiers((pNode.getModifiers() & (~ACC_PUBLIC)) | ACC_FINAL | ACC_PRIVATE);
-        adjustPropertyNode(pNode, createGetterBody(fNode));
-        newNodes.add(pNode);
-    }
-
-    private static void adjustPropertyNode(PropertyNode pNode, Statement getterBody) {
         pNode.setSetterBlock(null);
-        pNode.setGetterBlock(getterBody);
-    }
-
-    private static Statement createGetterBody(FieldNode fNode) {
-        BlockStatement body = new BlockStatement();
-        final ClassNode fieldType = fNode.getType();
-        final Statement statement;
-        if (fieldType.isArray() || implementsCloneable(fieldType)) {
-            statement = createGetterBodyArrayOrCloneable(fNode);
-        } else if (derivesFromDate(fieldType)) {
-            statement = createGetterBodyDate(fNode);
-        } else {
-            statement = createGetterBodyDefault(fNode);
+        Statement getter = handler.createPropGetter(pNode);
+        if (getter != null) {
+            pNode.setGetterBlock(getter);
         }
-        body.addStatement(statement);
-        return body;
-    }
-
-    private static Statement createGetterBodyArrayOrCloneable(FieldNode fNode) {
-        final Expression fieldExpr = varX(fNode);
-        final Expression expression = cloneArrayOrCloneableExpr(fieldExpr, fNode.getType());
-        return safeExpression(fieldExpr, expression);
-    }
-
-    private static Statement createGetterBodyDate(FieldNode fNode) {
-        final Expression fieldExpr = varX(fNode);
-        final Expression expression = cloneDateExpr(fieldExpr);
-        return safeExpression(fieldExpr, expression);
+        newNodes.add(pNode);
     }
 
     private static Statement createCheckForProperty(final PropertyNode pNode) {
@@ -530,4 +439,9 @@ public class ImmutableASTTransformation extends AbstractASTTransformation {
                 throw new MissingPropertyException(k, instance.getClass());
         }
     }
+
+    @Override
+    public void setCompilationUnit(CompilationUnit unit) {
+        this.compilationUnit = unit;
+    }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/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 3859f72..aa995ef 100644
--- a/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
@@ -21,11 +21,12 @@ package org.codehaus.groovy.transform;
 import groovy.lang.GroovyClassLoader;
 import groovy.transform.CompilationUnitAware;
 import groovy.transform.MapConstructor;
-import groovy.transform.construction.PropertyHandler;
+import groovy.transform.options.PropertyHandler;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.ConstructorNode;
 import org.codehaus.groovy.ast.DynamicVariable;
@@ -59,7 +60,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.getAllProperties;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
-import static org.codehaus.groovy.transform.ImmutableASTTransformation.doAddConstructor;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 
 /**
  * Handles generation of code for the @MapConstructor annotation.
@@ -93,7 +94,6 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation i
             boolean includeProperties = !memberHasValue(anno, "includeProperties", false);
             boolean includeSuperProperties = memberHasValue(anno, "includeSuperProperties", true);
             boolean includeSuperFields = memberHasValue(anno, "includeSuperFields", true);
-//            boolean useSetters = memberHasValue(anno, "useSetters", true);
             boolean includeStatic = memberHasValue(anno, "includeStatic", true);
             boolean allProperties = memberHasValue(anno, "allProperties", true);
             boolean noArg = memberHasValue(anno, "noArg", true);
@@ -102,12 +102,12 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation i
             List<String> includes = getMemberStringList(anno, "includes");
             boolean allNames = memberHasValue(anno, "allNames", true);
             if (!checkIncludeExcludeUndefinedAware(anno, excludes, includes, MY_TYPE_NAME)) return;
-            if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, false))
+            if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, allProperties))
                 return;
-            if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, false))
+            if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, allProperties))
                 return;
             final GroovyClassLoader classLoader = compilationUnit != null ? compilationUnit.getTransformLoader() : source.getClassLoader();
-            final PropertyHandler handler = PropertyHandler.createPropertyHandler(this, anno, classLoader);
+            final PropertyHandler handler = PropertyHandler.createPropertyHandler(this, classLoader, cNode);
             if (handler == null) return;
             if (!handler.validateAttributes(this, anno)) return;
 
@@ -179,11 +179,44 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation i
         }
     }
 
+    private static void doAddConstructor(final ClassNode cNode, final ConstructorNode constructorNode) {
+        cNode.addConstructor(constructorNode);
+        // GROOVY-5814: Immutable is not compatible with @CompileStatic
+        Parameter argsParam = null;
+        for (Parameter p : constructorNode.getParameters()) {
+            if ("args".equals(p.getName())) {
+                argsParam = p;
+                break;
+            }
+        }
+        if (argsParam != null) {
+            final Parameter arg = argsParam;
+            ClassCodeVisitorSupport variableExpressionFix = new ClassCodeVisitorSupport() {
+                @Override
+                protected SourceUnit getSourceUnit() {
+                    return cNode.getModule().getContext();
+                }
+
+                @Override
+                public void visitVariableExpression(final VariableExpression expression) {
+                    super.visitVariableExpression(expression);
+                    if ("args".equals(expression.getName())) {
+                        expression.setAccessedVariable(arg);
+                    }
+                }
+            };
+            variableExpressionFix.visitConstructor(constructorNode);
+        }
+    }
+
     private static void processProps(AbstractASTTransformation xform, AnnotationNode anno, ClassNode cNode, PropertyHandler handler, boolean allNames, List<String> excludes, List<String> includes, List<PropertyNode> superList, Parameter map, BlockStatement inner) {
         for (PropertyNode pNode : superList) {
             String name = pNode.getName();
             if (shouldSkipUndefinedAware(name, excludes, includes, allNames)) continue;
-            handler.createStatement(xform, anno, inner, cNode, pNode, map);
+            Statement propInit = handler.createPropInit(xform, anno, cNode, pNode, map);
+            if (propInit != null) {
+                inner.addStatement(propInit);
+            }
         }
     }
 
@@ -201,8 +234,8 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation i
                     ce.getCode().visit(this);
                 } else if (exp instanceof VariableExpression) {
                     VariableExpression ve = (VariableExpression) exp;
-                    if (ve.getName().equals("args") && ve.getAccessedVariable() instanceof DynamicVariable) {
-                        VariableExpression newVe = new VariableExpression(new Parameter(MAP_TYPE, "args"));
+                    if ("args".equals(ve.getName()) && ve.getAccessedVariable() instanceof DynamicVariable) {
+                        VariableExpression newVe = varX(param(MAP_TYPE, "args"));
                         newVe.setSourcePosition(ve);
                         return newVe;
                     }

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/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 13decb9..d225dfb 100644
--- a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
@@ -22,7 +22,7 @@ import groovy.lang.GroovyClassLoader;
 import groovy.transform.CompilationUnitAware;
 import groovy.transform.MapConstructor;
 import groovy.transform.TupleConstructor;
-import groovy.transform.construction.PropertyHandler;
+import groovy.transform.options.PropertyHandler;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
@@ -60,14 +60,12 @@ 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.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.constX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.copyStatementsWithSuperAdjustment;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.equalsNullX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.getAllProperties;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
 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.params;
@@ -133,7 +131,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
             if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, allProperties, includeSuperFields, false)) return;
             if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields, includeSuperProperties, allProperties, includeSuperFields, false)) return;
             final GroovyClassLoader classLoader = compilationUnit != null ? compilationUnit.getTransformLoader() : source.getClassLoader();
-            final PropertyHandler handler = PropertyHandler.createPropertyHandler(this, anno, classLoader);
+            final PropertyHandler handler = PropertyHandler.createPropertyHandler(this, classLoader, cNode);
             if (handler == null) return;
             if (!handler.validateAttributes(this, anno)) return;
 
@@ -211,11 +209,13 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
             FieldNode fNode = pNode.getField();
             if (shouldSkipUndefinedAware(name, excludes, includes, allNames)) continue;
             params.add(createParam(fNode, name, defaults, xform, makeImmutable));
-            boolean hasSetter = cNode.getProperty(name) != null && !fNode.isFinal();
             if (callSuper) {
                 superParams.add(varX(name));
             } else if (!superInPre && !specialNamedArgCase) {
-                handler.createStatement(xform, anno, body, cNode, pNode, null);
+                Statement propInit = handler.createPropInit(xform, anno, cNode, pNode, null);
+                if (propInit != null) {
+                    body.addStatement(propInit);
+                }
             }
         }
         if (callSuper) {
@@ -231,7 +231,10 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
             if (shouldSkipUndefinedAware(name, excludes, includes, allNames)) continue;
             Parameter nextParam = createParam(fNode, name, defaults, xform, makeImmutable);
             params.add(nextParam);
-            handler.createStatement(xform, anno, body, cNode, pNode, null);
+            Statement propInit = handler.createPropInit(xform, anno, cNode, pNode, null);
+            if (propInit != null) {
+                body.addStatement(propInit);
+            }
         }
 
         if (post != null) {
@@ -263,7 +266,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
             ClassNode firstParamType = params.get(0).getType();
             if (params.size() > 1 || firstParamType.equals(ClassHelper.OBJECT_TYPE)) {
                 String message = "The class " + cNode.getName() + " was incorrectly initialized via the map constructor with null.";
-                addSpecialMapConstructors(cNode, false, message);
+                addSpecialMapConstructors(cNode, message, false);
             }
         }
     }
@@ -290,7 +293,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
         return initialExp;
     }
 
-    public static void addSpecialMapConstructors(ClassNode cNode, boolean addNoArg, String message) {
+    public static void addSpecialMapConstructors(ClassNode cNode, String message, boolean addNoArg) {
         Parameter[] parameters = params(new Parameter(LHMAP_TYPE, "__namedArgs"));
         BlockStatement code = new BlockStatement();
         VariableExpression namedArgs = varX("__namedArgs");

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/spec/doc/core-metaprogramming.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/core-metaprogramming.adoc b/src/spec/doc/core-metaprogramming.adoc
index 57f726f..7230e11 100644
--- a/src/spec/doc/core-metaprogramming.adoc
+++ b/src/spec/doc/core-metaprogramming.adoc
@@ -942,9 +942,13 @@ expecting exactly one constructor, e.g. injection frameworks or JUnit parameteri
 
 ===== Immutability support
 
-If the `@ImmutableBase` annotation (normally added by the `@Immutable` meta-annotation) is also found on the class with the `@TupleConstructor` annotation,
-then the generated constructor will have all the necessary logic required for immutable classes (defensive copy in, cloning, etc.). Some of the
-annotation attributes won't be supported in this case.
+If the `@PropertyOptions` annotation is also found on the class with the `@TupleConstructor` annotation,
+then the generated constructor may contain custom property handling logic.
+The `propertyHandler` attribute on the `@PropertyOptions` annotation could for instance be set to
+`ImmutablePropertyHandler` which will result in the addition of the necessary logic for immutable classes
+(defensive copy in, cloning, etc.). This normally would happen automatically behind the scenes when you use
+the `@Immutable` meta-annotation.
+Some of the annotation attributes might not be supported by all property handlers.
 
 ===== Customization options
 
@@ -1702,9 +1706,11 @@ The `@Immutable` meta-annotation combines the following annotations:
 
 * <<x...@ToString>>
 * <<x...@EqualsAndHashCode>>
-* <<x...@ImmutableBase>>
 * <<x...@TupleConstructor>>
 * <<x...@MapConstructor>>
+* <<x...@ImmutableBase>>
+* <<x...@ImmutableOptions>>
+* <<x...@PropertyOptions>>
 * <<x...@KnownImmutable>>
 
 The `@Immutable` meta-annotation simplifies the creation of immutable classes. Immutable classes are useful
@@ -1725,7 +1731,7 @@ such as defensive copy in and defensive copy out for any mutable properties with
 and property getters. Between `@ImmutableBase`, `@MapConstructor` and `@TupleConstructor` properties
 are either identified as immutable or the special coding for numerous known cases is handled automatically.
 Various mechanisms are provided for you to extend the handled property types which are allowed. See
-`@ImmutableBase` and `@KnownImmutable` for details.
+`@ImmutableOptions` and `@KnownImmutable` for details.
 
 The results of applying `@Immutable` to a class are pretty similar to those of
 applying the <<x...@Canonical>> meta-annotation but the generated class will have extra
@@ -1740,15 +1746,35 @@ it aggregates. See those annotations for more details.
 ===== `@groovy.transform.ImmutableBase`
 
 Immutable classes generated with `@ImmutableBase` are automatically made final. Also, the type of each property is checked
-and various checks are made on the class, for example, public instance fields currently aren't allowed.
+and various checks are made on the class, for example, public instance fields currently aren't allowed. It also generates
+a `copyWith` constructor if desired.
 
-For a class to be immutable, you have to
-make sure that properties are of an immutable type (primitive or boxed types), of a known-immutable type or another
-class annotated with `@KnownImmutable` (which includes those annotated with the `@Immutable` meta-annotation).
+The following annotation attribute is supported:
 
-Since `@ImmutableBase` relies on a predefined list of known immutable classes (like `java.net.URI` or `java.lang.String`
-and fails if you use a type which is not in that list, you are allowed to instruct the transformation that some types
-are deemed immutable thanks to the following annotation attributes:
+[cols="1,1,2,3a",options="header"]
+|=======================================================================
+|Attribute|Default value|Description|Example
+|copyWith|false|A boolean whether to generate a `copyWith( Map )` method.|
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=immutable_example_copyWith,indent=0]
+----
+|=======================================================================
+
+[[xform-PropertyOptions]]
+===== `@groovy.transform.PropertyOptions`
+
+This annotation allows you to specify a custom property handler to be used by transformations
+during class construction. It is ignored by the main Groovy compiler but is referenced by other transformations
+like `@TupleConstructor`, `@MapConstructor`, and `@ImmutableBase`. It is frequently used behind the
+scenes by the `@Immutable` meta-annotation.
+
+[[xform-ImumtableOptions]]
+===== `@groovy.transform.ImmutableOptions`
+
+Groovy's immutability support relies on a predefined list of known immutable classes (like `java.net.URI` or `java.lang.String`
+and fails if you use a type which is not in that list, you are allowed to add to the list of known immutable types
+thanks to the following annotation attributes of the `@ImmutableOptions` annotation:
 
 [cols="1,1,2,3a",options="header"]
 |=======================================================================
@@ -1763,12 +1789,6 @@ include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=imm
 ----
 include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=immutable_example_knownimmutables,indent=0]
 ----
-----
-|copyWith|false|A boolean whether to generate a `copyWith( Map )` method.|
-[source,groovy]
-----
-include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=immutable_example_copyWith,indent=0]
-----
 |=======================================================================
 
 If you deem a type as immutable and it isn't one of the ones automatically handled, then it is up to you
@@ -1780,7 +1800,7 @@ to correctly code that class to ensure immutability.
 The `@KnownImmutable` annotation isn't actually one that triggers any AST transformations. It is simply
 a marker annotation. You can annotate your classes with the annotation (including Java classes) and they
 will be recognized as acceptable types for members within an immutable class. This saves you having to
-explicitly use the `knownImmutables` or `knownImmutableClasses` annotation attributes from `@ImmutableBase`.
+explicitly use the `knownImmutables` or `knownImmutableClasses` annotation attributes from `@ImmutableOptions`.
 
 [[xform-Memoized]]
 ===== `@groovy.transform.Memoized`

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
index 2d817e5..e8a6ddf 100644
--- a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
@@ -230,7 +230,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
     void testImmutableWithHashMap() {
         assertScript """
             import groovy.transform.Immutable
-            @Immutable(propertyHandler = groovy.transform.construction.LegacyHashMapPropertyHandler, noArg = false)
+            import groovy.transform.options.LegacyHashMapPropertyHandler
+            @Immutable(propertyHandler = LegacyHashMapPropertyHandler, noArg = false)
             final class HasHashMap {
                 HashMap map = [d:4]
             }


[2/2] groovy git commit: GROOVY-8477: @Immutable-related transformations should be more configurable (further refactoring)

Posted by pa...@apache.org.
GROOVY-8477: @Immutable-related transformations should be more configurable (further refactoring)

Split ImmutableBase into two (ImmutableOptions now just has the known immutables config information)
and generalise the property handling facility a bit more plus mark it as @Incubating.


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

Branch: refs/heads/master
Commit: e2f4ceb508f99f53037b4c31bfb4a2ae480c0fda
Parents: 122dc30
Author: paulk <pa...@asert.com.au>
Authored: Fri Feb 16 20:27:47 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Fri Feb 16 20:27:47 2018 +1000

----------------------------------------------------------------------
 .../groovy/groovy/transform/Immutable.groovy    |   8 +-
 .../groovy/groovy/transform/ImmutableBase.java  |  51 +---
 .../groovy/transform/ImmutableOptions.java      |  80 +++++
 .../groovy/groovy/transform/MapConstructor.java |  16 +-
 .../groovy/transform/PropertyOptions.java       |  46 +++
 .../groovy/transform/TupleConstructor.java      |  22 +-
 .../construction/DefaultPropertyHandler.java    | 103 -------
 .../construction/ImmutablePropertyHandler.java  | 295 ------------------
 .../LegacyHashMapPropertyHandler.java           |  99 ------
 .../transform/construction/PropertyHandler.java |  76 -----
 .../options/DefaultPropertyHandler.java         | 102 +++++++
 .../options/ImmutablePropertyHandler.java       | 306 +++++++++++++++++++
 .../options/LegacyHashMapPropertyHandler.java   |  99 ++++++
 .../transform/options/PropertyHandler.java      | 116 +++++++
 .../ast/tools/ImmutablePropertyUtils.java       |  45 ++-
 .../groovy/classgen/EnumCompletionVisitor.java  |   4 +-
 .../transform/AbstractASTTransformation.java    |   6 +-
 .../transform/ImmutableASTTransformation.java   | 150 ++-------
 .../MapConstructorASTTransformation.java        |  51 +++-
 .../TupleConstructorASTTransformation.java      |  21 +-
 src/spec/doc/core-metaprogramming.adoc          |  58 ++--
 .../transform/ImmutableTransformTest.groovy     |   3 +-
 22 files changed, 953 insertions(+), 804 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/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 cfbcc30..638a9db 100644
--- a/src/main/groovy/groovy/transform/Immutable.groovy
+++ b/src/main/groovy/groovy/transform/Immutable.groovy
@@ -18,7 +18,7 @@
  */
 package groovy.transform
 
-import groovy.transform.construction.ImmutablePropertyHandler
+import groovy.transform.options.ImmutablePropertyHandler
 
 /**
  * Meta annotation used when defining immutable classes.
@@ -176,8 +176,10 @@ import groovy.transform.construction.ImmutablePropertyHandler
 @ToString(cache = true, includeSuperProperties = true)
 @EqualsAndHashCode(cache = true)
 @ImmutableBase
-@TupleConstructor(defaults = false, propertyHandler = ImmutablePropertyHandler)
-@MapConstructor(noArg = true, includeSuperProperties = true, includeFields = true, propertyHandler = ImmutablePropertyHandler)
+@ImmutableOptions
+@PropertyOptions(propertyHandler = ImmutablePropertyHandler)
+@TupleConstructor(defaults = false)
+@MapConstructor(noArg = true, includeSuperProperties = true, includeFields = true)
 @KnownImmutable
 @AnnotationCollector(mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED)
 @interface Immutable { }

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/main/groovy/groovy/transform/ImmutableBase.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/ImmutableBase.java b/src/main/groovy/groovy/transform/ImmutableBase.java
index e6689da..21156b5 100644
--- a/src/main/groovy/groovy/transform/ImmutableBase.java
+++ b/src/main/groovy/groovy/transform/ImmutableBase.java
@@ -29,10 +29,20 @@ import java.lang.annotation.Target;
  * Class annotation used to assist in the creation of immutable classes.
  * Checks on the validity of an immutable class and makes some preliminary changes to the class.
  * Usually used via the {@code @Immutable} meta annotation.
+ * <p>
+ * Custom property handling:
+ * <ul>
+ * <li>The {@code @ImmutableBase} annotation supports customization using {@code @PropertyOptions}
+ * which allows a custom property handler to be defined. This is most typically used behind the scenes
+ * by the {@code @Immutable} meta-annotation but you can also define your own handler. If a custom
+ * handler is present, it will determine the code generated for the getters and setters of any property.</li>
+ * </ul>
  *
  * @see Immutable
+ * @see ImmutableOptions
  * @see MapConstructor
  * @see TupleConstructor
+ * @see PropertyOptions
  * @since 2.5
  */
 @java.lang.annotation.Documented
@@ -41,47 +51,6 @@ import java.lang.annotation.Target;
 @GroovyASTTransformationClass("org.codehaus.groovy.transform.ImmutableASTTransformation")
 public @interface ImmutableBase {
     /**
-     * Allows you to provide {@code @Immutable} with a list of classes which
-     * are deemed immutable. By supplying a class in this list, you are vouching
-     * for its immutability and {@code @Immutable} will do no further checks.
-     * Example:
-     * <pre>
-     * import groovy.transform.*
-     * {@code @Immutable}(knownImmutableClasses = [Address])
-     * class Person {
-     *     String first, last
-     *     Address address
-     * }
-     *
-     * {@code @TupleConstructor}
-     * class Address {
-     *     final String street
-     * }
-     * </pre>
-     *
-     * @since 1.8.7
-     */
-    Class[] knownImmutableClasses() default {};
-
-    /**
-     * Allows you to provide {@code @Immutable} with a list of property names which
-     * are deemed immutable. By supplying a property's name in this list, you are vouching
-     * for its immutability and {@code @Immutable} will do no further checks.
-     * Example:
-     * <pre>
-     * {@code @groovy.transform.Immutable}(knownImmutables = ['address'])
-     * class Person {
-     *     String first, last
-     *     Address address
-     * }
-     * ...
-     * </pre>
-     *
-     * @since 2.1.0
-     */
-    String[] knownImmutables() default {};
-
-    /**
      * If {@code true}, this adds a method {@code copyWith} which takes a Map of
      * new property values and returns a new instance of the Immutable class with
      * these values set.

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/main/groovy/groovy/transform/ImmutableOptions.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/ImmutableOptions.java b/src/main/groovy/groovy/transform/ImmutableOptions.java
new file mode 100644
index 0000000..6c1c200
--- /dev/null
+++ b/src/main/groovy/groovy/transform/ImmutableOptions.java
@@ -0,0 +1,80 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.transform;
+
+import groovy.transform.options.ImmutablePropertyHandler;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Class annotation used to assist in the creation of immutable classes.
+ * Defines any known immutable properties (or fields) or known immutable classes.
+ *
+ * @see Immutable
+ * @see ImmutablePropertyHandler
+ * @since 2.5
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE})
+public @interface ImmutableOptions {
+    /**
+     * Allows you to provide {@code @Immutable} with a list of classes which
+     * are deemed immutable. By supplying a class in this list, you are vouching
+     * for its immutability and {@code @Immutable} will do no further checks.
+     * Example:
+     * <pre>
+     * import groovy.transform.*
+     * {@code @Immutable}(knownImmutableClasses = [Address])
+     * class Person {
+     *     String first, last
+     *     Address address
+     * }
+     *
+     * {@code @TupleConstructor}
+     * class Address {
+     *     final String street
+     * }
+     * </pre>
+     *
+     * @since 1.8.7
+     */
+    Class[] knownImmutableClasses() default {};
+
+    /**
+     * Allows you to provide {@code @Immutable} with a list of property names which
+     * are deemed immutable. By supplying a property's name in this list, you are vouching
+     * for its immutability and {@code @Immutable} will do no further checks.
+     * Example:
+     * <pre>
+     * {@code @groovy.transform.Immutable}(knownImmutables = ['address'])
+     * class Person {
+     *     String first, last
+     *     Address address
+     * }
+     * ...
+     * </pre>
+     *
+     * @since 2.1.0
+     */
+    String[] knownImmutables() default {};
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/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 2cdbf94..d6aec1c 100644
--- a/src/main/groovy/groovy/transform/MapConstructor.java
+++ b/src/main/groovy/groovy/transform/MapConstructor.java
@@ -18,8 +18,6 @@
  */
 package groovy.transform;
 
-import groovy.transform.construction.DefaultPropertyHandler;
-import groovy.transform.construction.PropertyHandler;
 import org.codehaus.groovy.transform.GroovyASTTransformationClass;
 
 import java.lang.annotation.ElementType;
@@ -69,6 +67,14 @@ import java.lang.annotation.Target;
  * }
  * </pre>
  * <p>
+ * Custom property handling:
+ * <ul>
+ * <li>The {@code @MapConstructor} annotation supports customization using {@code @PropertyOptions}
+ * which allows a custom property handler to be defined. This is most typically used behind the scenes
+ * by the {@code @Immutable} meta-annotation but you can also define your own handler. If a custom
+ * handler is present, it will determine the code generated when initializing any property (or field).</li>
+ * </ul>
+ * <p>
  * Known limitations/special cases:
  * <ul>
  * <li>
@@ -85,6 +91,7 @@ import java.lang.annotation.Target;
  * </li>
  * </ul>
  *
+ * @see PropertyOptions
  * @since 2.5.0
  */
 @java.lang.annotation.Documented
@@ -165,11 +172,6 @@ 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.

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/main/groovy/groovy/transform/PropertyOptions.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/PropertyOptions.java b/src/main/groovy/groovy/transform/PropertyOptions.java
new file mode 100644
index 0000000..12069c5
--- /dev/null
+++ b/src/main/groovy/groovy/transform/PropertyOptions.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.transform;
+
+import groovy.transform.options.DefaultPropertyHandler;
+import groovy.transform.options.PropertyHandler;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marker annotation used to indicate that special property handling code will be generated for this class.
+ *
+ * @see Immutable
+ * @see ImmutableBase
+ * @see MapConstructor
+ * @see TupleConstructor
+ * @since 2.5
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE})
+public @interface PropertyOptions {
+    /**
+     * The property handler class which creates the necessary code for getting, setting or initializing properties.
+     */
+    Class<? extends PropertyHandler> propertyHandler() default DefaultPropertyHandler.class;
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/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 a162f0f..32af45f 100644
--- a/src/main/groovy/groovy/transform/TupleConstructor.java
+++ b/src/main/groovy/groovy/transform/TupleConstructor.java
@@ -18,8 +18,6 @@
  */
 package groovy.transform;
 
-import groovy.transform.construction.DefaultPropertyHandler;
-import groovy.transform.construction.PropertyHandler;
 import org.codehaus.groovy.transform.GroovyASTTransformationClass;
 
 import java.lang.annotation.ElementType;
@@ -58,8 +56,8 @@ import java.lang.annotation.Target;
  * Groovy's normal conventions then allows any number of parameters to be left off the end of the parameter list
  * including all of the parameters - giving a no-arg constructor which can be used with the map-style naming conventions.
  * <p>
- * The order of parameters is given by the properties of any super classes with most super first
- * (if {@code includeSuperProperties} is set) followed by the properties of the class followed
+ * The order of parameters is given by the properties of any super classes (if {@code includeSuperProperties} is set)
+ * with the most super first followed by the properties of the class followed
  * by the fields of the class (if {@code includeFields} is set). Within each grouping the order
  * is as attributes appear within the respective class.
  * <p>More examples:</p>
@@ -160,6 +158,14 @@ import java.lang.annotation.Target;
  * assert student.courses == ['IT']
  * </pre>
  * <p>
+ * Custom property handling:
+ * <ul>
+ * <li>The {@code @TupleConstructor} annotation supports customization using {@code @PropertyOptions}
+ * which allows a custom property handler to be defined. This is most typically used behind the scenes
+ * by the {@code @Immutable} meta-annotation but you can also define your own handler. If a custom
+ * handler is present, it will determine the code generated when initializing any property (or field).</li>
+ * </ul>
+ * <p>
  * Named-argument support:
  * <ul>
  * <li>Groovy supports named-arguments for classes with a no-arg constructor or a constructor
@@ -190,6 +196,7 @@ import java.lang.annotation.Target;
  * See the {@code defaults} attribute for further details about customizing this behavior.</li>
  * </ul>
  *
+ * @see PropertyOptions
  * @since 1.8.0
  */
 @java.lang.annotation.Documented
@@ -300,13 +307,6 @@ 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/e2f4ceb5/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
deleted file mode 100644
index 198e69d..0000000
--- a/src/main/groovy/groovy/transform/construction/DefaultPropertyHandler.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- *  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/e2f4ceb5/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
deleted file mode 100644
index 09e9126..0000000
--- a/src/main/groovy/groovy/transform/construction/ImmutablePropertyHandler.java
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- *  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/e2f4ceb5/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
deleted file mode 100644
index 5991f68..0000000
--- a/src/main/groovy/groovy/transform/construction/LegacyHashMapPropertyHandler.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- *  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/e2f4ceb5/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
deleted file mode 100644
index f3170c2..0000000
--- a/src/main/groovy/groovy/transform/construction/PropertyHandler.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- *  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/e2f4ceb5/src/main/groovy/groovy/transform/options/DefaultPropertyHandler.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/options/DefaultPropertyHandler.java b/src/main/groovy/groovy/transform/options/DefaultPropertyHandler.java
new file mode 100644
index 0000000..c38bc68
--- /dev/null
+++ b/src/main/groovy/groovy/transform/options/DefaultPropertyHandler.java
@@ -0,0 +1,102 @@
+/*
+ *  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.options;
+
+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 Statement createPropInit(AbstractASTTransformation xform, AnnotationNode anno, 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) {
+            return assignFieldS(useSetters, namedArgsMap, name);
+        } else {
+            Expression var = varX(name);
+            if (useSetters && hasSetter) {
+                return setViaSetterS(name, var);
+            } else {
+                return assignToFieldS(name, var);
+            }
+        }
+    }
+
+    private static Statement assignToFieldS(String name, Expression var) {
+        return assignS(propX(varX("this"), name), var);
+    }
+
+    private static Statement setViaSetterS(String name, Expression var) {
+        return stmt(callThisX(getSetterName(name), var));
+    }
+
+    private static Statement assignFieldS(boolean useSetters, Parameter map, String name) {
+        ArgumentListExpression nameArg = args(constX(name));
+        Expression var = callX(varX(map), "get", nameArg);
+        return ifS(callX(varX(map), "containsKey", nameArg), useSetters ?
+                setViaSetterS(name, var) :
+                assignToFieldS(name, var));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/main/groovy/groovy/transform/options/ImmutablePropertyHandler.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/options/ImmutablePropertyHandler.java b/src/main/groovy/groovy/transform/options/ImmutablePropertyHandler.java
new file mode 100644
index 0000000..f815942
--- /dev/null
+++ b/src/main/groovy/groovy/transform/options/ImmutablePropertyHandler.java
@@ -0,0 +1,306 @@
+/*
+ *  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.options;
+
+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.ConstantExpression;
+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.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.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.safeExpression;
+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 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);
+
+    @Override
+    public Statement createPropGetter(PropertyNode pNode) {
+        FieldNode fNode = pNode.getField();
+        BlockStatement body = new BlockStatement();
+        final ClassNode fieldType = fNode.getType();
+        final Statement statement;
+        if (fieldType.isArray() || implementsCloneable(fieldType)) {
+            statement = createGetterBodyArrayOrCloneable(fNode);
+        } else if (derivesFromDate(fieldType)) {
+            statement = createGetterBodyDate(fNode);
+        } else {
+            statement = createGetterBodyDefault(fNode);
+        }
+        body.addStatement(statement);
+        return body;
+    }
+
+    @Override
+    public Statement createPropSetter(PropertyNode pNode) {
+        return null;
+    }
+
+    @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(SELF_TYPE, "checkPropNames", args("this", "args"))));
+        }
+        return super.validateProperties(xform, body, cNode, props);
+    }
+
+    @Override
+    public Statement createPropInit(AbstractASTTransformation xform, AnnotationNode anno, ClassNode cNode, PropertyNode pNode, Parameter namedArgsMap) {
+        FieldNode fNode = pNode.getField();
+        if (fNode.isFinal() && fNode.isStatic()) return null;
+        if (fNode.isFinal() && fNode.getInitialExpression() != null) {
+            return checkFinalArgNotOverridden(cNode, fNode);
+        }
+        return createConstructorStatement(xform, cNode, pNode, namedArgsMap != null);
+    }
+
+    private static Statement createGetterBodyDefault(FieldNode fNode) {
+        final Expression fieldExpr = varX(fNode);
+        return stmt(fieldExpr);
+    }
+
+    private Statement createGetterBodyArrayOrCloneable(FieldNode fNode) {
+        final Expression fieldExpr = varX(fNode);
+        final Expression expression = cloneArrayOrCloneableExpr(fieldExpr, fNode.getType());
+        return safeExpression(fieldExpr, expression);
+    }
+
+    private Statement createGetterBodyDate(FieldNode fNode) {
+        final Expression fieldExpr = varX(fNode);
+        final Expression expression = cloneDateExpr(fieldExpr);
+        return safeExpression(fieldExpr, expression);
+    }
+
+    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) {
+        final List<String> knownImmutableClasses = ImmutablePropertyUtils.getKnownImmutableClasses(xform, cNode);
+        final List<String> knownImmutables = ImmutablePropertyUtils.getKnownImmutables(xform, cNode);
+        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()))
+                )));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/main/groovy/groovy/transform/options/LegacyHashMapPropertyHandler.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/options/LegacyHashMapPropertyHandler.java b/src/main/groovy/groovy/transform/options/LegacyHashMapPropertyHandler.java
new file mode 100644
index 0000000..ac3c364
--- /dev/null
+++ b/src/main/groovy/groovy/transform/options/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.options;
+
+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 Statement createPropInit(AbstractASTTransformation xform, AnnotationNode anno, ClassNode cNode, PropertyNode pNode, Parameter namedArgsMap) {
+        FieldNode fNode = pNode.getField();
+        if (fNode.isFinal() && fNode.isStatic()) return null;
+        if (fNode.isFinal() && fNode.getInitialExpression() != null) {
+            return checkFinalArgNotOverridden(cNode, fNode);
+        }
+        return createLegacyConstructorStatementMapSpecial(fNode);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/e2f4ceb5/src/main/groovy/groovy/transform/options/PropertyHandler.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/options/PropertyHandler.java b/src/main/groovy/groovy/transform/options/PropertyHandler.java
new file mode 100644
index 0000000..1a4acc0
--- /dev/null
+++ b/src/main/groovy/groovy/transform/options/PropertyHandler.java
@@ -0,0 +1,116 @@
+/*
+ *  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.options;
+
+import groovy.lang.GroovyClassLoader;
+import groovy.transform.PropertyOptions;
+import org.apache.groovy.lang.annotation.Incubating;
+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.ast.stmt.Statement;
+import org.codehaus.groovy.transform.AbstractASTTransformation;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
+
+@Incubating
+public abstract class PropertyHandler {
+    private static final Class<? extends Annotation> PROPERTY_OPTIONS_CLASS = PropertyOptions.class;
+    public static final ClassNode PROPERTY_OPTIONS_TYPE = makeWithoutCaching(PROPERTY_OPTIONS_CLASS, false);
+    public abstract boolean validateAttributes(AbstractASTTransformation xform, AnnotationNode anno);
+
+    public boolean validateProperties(AbstractASTTransformation xform, BlockStatement body, ClassNode cNode, List<PropertyNode> props) {
+        return true;
+    }
+
+    /**
+     * Create a statement that will initialize the property including any defensive copying. Null if no statement should be added.
+     *
+     * @param xform the transform being processed
+     * @param anno the '@ImmutableBase' annotation node
+     * @param cNode the classnode containing the property
+     * @param pNode the property node to initialize
+     * @param namedArgMap an "args" Map if the property value should come from a named arg map or null if not
+     */
+    public abstract Statement createPropInit(AbstractASTTransformation xform, AnnotationNode anno, ClassNode cNode, PropertyNode pNode, Parameter namedArgMap);
+
+    /**
+     * Create the getter block used when reading the property including any defensive copying.
+     *
+     *  @param pNode the property node
+     */
+    public Statement createPropGetter(PropertyNode pNode) {
+        return pNode.getGetterBlock();
+    }
+
+    /**
+     * Create the setter block used when setting the property. Can be null for read-only properties.
+     *
+     *  @param pNode the property node
+     */
+    public Statement createPropSetter(PropertyNode pNode) {
+        return pNode.getSetterBlock();
+    }
+
+    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, GroovyClassLoader loader, ClassNode cNode) {
+        List<AnnotationNode> annotations = cNode.getAnnotations(PROPERTY_OPTIONS_TYPE);
+        AnnotationNode anno = annotations.isEmpty() ? null : annotations.get(0);
+        if (anno == null) return new groovy.transform.options.DefaultPropertyHandler();
+
+        ClassNode handlerClass = xform.getMemberClassValue(anno, "propertyHandler", ClassHelper.make(groovy.transform.options.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/e2f4ceb5/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
index e263f33..c04c545 100644
--- a/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java
+++ b/src/main/java/org/apache/groovy/ast/tools/ImmutablePropertyUtils.java
@@ -18,13 +18,14 @@
  */
 package org.apache.groovy.ast.tools;
 
-import groovy.transform.ImmutableBase;
+import groovy.transform.ImmutableOptions;
 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.ClassExpression;
 import org.codehaus.groovy.ast.expr.ConstantExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.ListExpression;
@@ -54,8 +55,9 @@ public class ImmutablePropertyUtils {
     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 Class<? extends Annotation> IMMUTABLE_OPTIONS_CLASS = ImmutableOptions.class;
+    public static final ClassNode IMMUTABLE_OPTIONS_TYPE = makeWithoutCaching(IMMUTABLE_OPTIONS_CLASS, false);
+    private static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = "knownImmutableClasses";
     private static final String MEMBER_KNOWN_IMMUTABLES = "knownImmutables";
     /*
               Currently leaving BigInteger and BigDecimal in list but see:
@@ -150,7 +152,7 @@ public class ImmutablePropertyUtils {
                 "- 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";
+                "Other restrictions apply, please see the groovydoc for " + IMMUTABLE_OPTIONS_TYPE.getNameWithoutPackage() + " for further details";
     }
 
     private static String prettyTypeName(String name) {
@@ -209,15 +211,17 @@ public class ImmutablePropertyUtils {
         return isBuiltinImmutable(clazz.getName()) || hasImmutableAnnotation(clazz);
     }
 
-    public static List<String> getKnownImmutables(AbstractASTTransformation xform, AnnotationNode node) {
+    public static List<String> getKnownImmutables(AbstractASTTransformation xform, ClassNode cNode) {
+        List<AnnotationNode> annotations = cNode.getAnnotations(ImmutablePropertyUtils.IMMUTABLE_OPTIONS_TYPE);
+        AnnotationNode anno = annotations.isEmpty() ? null : annotations.get(0);
         final List<String> immutables = new ArrayList<String>();
+        if (anno == null) return immutables;
 
-        if (node == null) return immutables;
-        final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLES);
+        final Expression expression = anno.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);
+            xform.addError("Use the Groovy list notation [el1, el2] to specify known immutable property names via \"" + MEMBER_KNOWN_IMMUTABLES + "\"", anno);
             return immutables;
         }
 
@@ -227,7 +231,32 @@ public class ImmutablePropertyUtils {
                 immutables.add((String) ((ConstantExpression) listItemExpression).getValue());
             }
         }
+        if (!xform.checkPropertyList(cNode, immutables, "knownImmutables", anno, "immutable class", false)) return immutables;
 
         return immutables;
     }
+
+    public static List<String> getKnownImmutableClasses(AbstractASTTransformation xform, ClassNode cNode) {
+        List<AnnotationNode> annotations = cNode.getAnnotations(ImmutablePropertyUtils.IMMUTABLE_OPTIONS_TYPE);
+        AnnotationNode anno = annotations.isEmpty() ? null : annotations.get(0);
+        final List<String> immutableClasses = new ArrayList<String>();
+
+        if (anno == null) return immutableClasses;
+        final Expression expression = anno.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 + "\"", anno);
+            return immutableClasses;
+        }
+
+        final ListExpression listExpression = (ListExpression) expression;
+        for (Expression listItemExpression : listExpression.getExpressions()) {
+            if (listItemExpression instanceof ClassExpression) {
+                immutableClasses.add(listItemExpression.getType().getName());
+            }
+        }
+
+        return immutableClasses;
+    }
 }