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:01 UTC

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

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;
+    }
 }