Repository: groovy
Updated Branches:
  refs/heads/master 889fb49c9 -> ae51fc62b


GROOVY-8440: Groovy's @Immutable annotation should be re-vamped to be a 
meta-annotation (closes #653)


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

Branch: refs/heads/master
Commit: ae51fc62b050d7408508cf2f919b97ad1d06fba3
Parents: 889fb49
Author: paulk <[email protected]>
Authored: Wed Jan 10 23:24:25 2018 +1000
Committer: paulk <[email protected]>
Committed: Wed Jan 24 12:57:28 2018 +1000

----------------------------------------------------------------------
 .../groovy/groovy/transform/Immutable.groovy    | 181 ++++++++++++++
 src/main/groovy/groovy/transform/Immutable.java | 249 -------------------
 .../groovy/groovy/transform/ImmutableBase.java  | 110 ++++++++
 .../groovy/groovy/transform/KnownImmutable.java |  39 +++
 .../groovy/groovy/transform/MapConstructor.java |  14 ++
 .../groovy/transform/TupleConstructor.java      |  10 +
 .../transform/AbstractASTTransformation.java    |   2 +-
 .../transform/ImmutableASTTransformation.java   | 248 +++++++++---------
 .../MapConstructorASTTransformation.java        |  30 ++-
 .../TupleConstructorASTTransformation.java      |  24 +-
 .../test/builder/ObjectGraphBuilderTest.groovy  |   2 +-
 ...ithJointCompilationGroovy6836StubTest.groovy |   2 -
 .../CanonicalComponentsTransformTest.groovy     |  20 +-
 .../transform/ImmutableTransformTest.groovy     |   8 +-
 .../TransformsAndCustomClassLoadersTest.groovy  |  15 +-
 15 files changed, 552 insertions(+), 402 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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
new file mode 100644
index 0000000..e59342b
--- /dev/null
+++ b/src/main/groovy/groovy/transform/Immutable.groovy
@@ -0,0 +1,181 @@
+/*
+ *  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
+
+/**
+ * Meta annotation used when defining immutable classes.
+ * <p>
+ * It allows you to write classes in this shortened form:
+ * <pre class="groovyTestCase">
+ * {@code @groovy.transform.Immutable} class Customer {
+ *     String first, last
+ *     int age
+ *     Date since
+ *     Collection favItems
+ * }
+ * def d = new Date()
+ * def c1 = new Customer(first:'Tom', last:'Jones', age:21, since:d, 
favItems:['Books', 'Games'])
+ * def c2 = new Customer('Tom', 'Jones', 21, d, ['Books', 'Games'])
+ * assert c1 == c2
+ * </pre>
+ * The {@code @Immutable} meta-annotation corresponds to adding the following 
annotations:
+ * {@code @ToString}, {@code @EqualsAndHashCode}, {@code @ImmutableBase}, 
{@code @KnownImmutable}, {@code @MapConstructor} and {@code @TupleConstructor}.
+ * Together these annotations instruct the compiler to execute the necessary 
transformations to add
+ * the necessary getters, constructors, equals, hashCode and other helper 
methods that are typically
+ * written when creating immutable classes with the defined properties.
+ * <p>
+ * A class created in this way has the following characteristics:
+ * <ul>
+ * <li>The class is automatically made final.
+ * <li>Properties must be of an immutable type or a type with a strategy for 
handling non-immutable
+ * characteristics. Specifically, the type must be one of the primitive or 
wrapper types, Strings, enums,
+ * other {@code @Immutable} classes or known immutables (e.g. java.awt.Color, 
java.net.URI, java.util.UUID).
+ * Also handled are Cloneable classes, collections, maps and arrays, other 
"effectively immutable"
+ * classes with special handling (e.g. java.util.Date), and usages of 
java.util.Optional where the
+ * contained type is immutable (e.g. Optional&lt;String&gt;).
+ * <li>Properties automatically have private, final backing fields with 
getters.
+ * Attempts to update the property will result in a {@code 
ReadOnlyPropertyException}.
+ * <li>A map-based constructor is provided which allows you to set properties 
by name.
+ * <li>A tuple-style constructor is provided which allows you to set 
properties in the same order as they are defined.
+ * <li>Default {@code equals}, {@code hashCode} and {@code toString} methods 
are provided based on the property values.
+ * Though not normally required, you may write your own implementations of 
these methods. For {@code equals} and {@code hashCode},
+ * if you do write your own method, it is up to you to obey the general 
contract for {@code equals} methods and supply
+ * a corresponding matching {@code hashCode} method.
+ * If you do provide one of these methods explicitly, the default 
implementation will be made available in a private
+ * "underscore" variant which you can call. E.g., you could provide a (not 
very elegant) multi-line formatted
+ * {@code toString} method for {@code Customer} above as follows:
+ * <pre>
+ *     String toString() {
+ *        _toString().replaceAll(/\(/, '(\n\t').replaceAll(/\)/, 
'\n)').replaceAll(/, /, '\n\t')
+ *    }
+ * </pre>
+ * If an "underscore" version of the respective method already exists, then no 
default implementation is provided.
+ * <li>{@code Date}s, {@code Cloneable}s and arrays are defensively copied on 
the way in (constructor) and out (getters).
+ * Arrays and {@code Cloneable} objects use the {@code clone} method. For your 
own classes,
+ * it is up to you to define this method and use deep cloning if appropriate.
+ * <li>{@code Collection}s and {@code Map}s are wrapped by immutable wrapper 
classes (but not deeply cloned!).
+ * Attempts to update them will result in an {@code 
UnsupportedOperationException}.
+ * <li>Fields that are enums or other {@code @Immutable} classes are allowed 
but for an
+ * otherwise possible mutable property type, an error is thrown.
+ * <li>You don't have to follow Groovy's normal property conventions, e.g. you 
can create an explicit private field and
+ * then you can write explicit get and set methods. Such an approach, isn't 
currently prohibited (to give you some
+ * wiggle room to get around these conventions) but any fields created in this 
way are deemed not to be part of the
+ * significant state of the object and aren't factored into the {@code equals} 
or {@code hashCode} methods.
+ * Similarly, you may use static properties (though usually this is 
discouraged) and these too will be ignored
+ * as far as significant state is concerned. If you do break standard 
conventions, you do so at your own risk and
+ * your objects may no longer be immutable. It is up to you to ensure that 
your objects remain immutable at least
+ * to the extent expected in other parts of your program!
+ * </ul>
+ * Immutable classes are particularly useful for functional and concurrent 
styles of programming
+ * and for use as key values within maps. If you want similar functionality to 
what this annotation
+ * provides but don't need immutability then consider using {@code @Canonical}.
+ * <p>
+ * Customising behaviour:
+ * <p>
+ * You can customise the toString() method provided for you by {@code 
@Immutable}
+ * by also adding the {@code @ToString} annotation to your class definition.
+ * <p>
+ * Limitations:
+ * <ul>
+ * <li>
+ * As outlined above, Arrays and {@code Cloneable} objects use the {@code 
clone} method. For your own classes,
+ * it is up to you to define this method and use deep cloning if appropriate.
+ * </li>
+ * <li>
+ * As outlined above, {@code Collection}s and {@code Map}s are wrapped by 
immutable wrapper classes (but not deeply cloned!).
+ * </li>
+ * <li>
+ * Currently {@code BigInteger} and {@code BigDecimal} are deemed immutable 
but see:
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6348370
+ * </li>
+ * <li>
+ * {@code java.awt.Color} is treated as "effectively immutable" but is not 
final so while not normally used with child
+ * classes, it isn't strictly immutable. Use at your own risk.
+ * </li>
+ * <li>
+ * {@code java.util.Date} is treated as "effectively immutable" but is not 
final so it isn't strictly immutable.
+ * Use at your own risk.
+ * </li>
+ * <li>
+ * Groovy's normal map-style naming conventions will not be available if the 
first property
+ * has type {@code LinkedHashMap} or if there is a single Map, AbstractMap or 
HashMap property.
+ * </li>
+ * </ul>
+ * <p>More examples:</p>
+ 
--------------------------------------------------------------------------------
+ * <pre class="groovyTestCase">
+ * import groovy.transform.*
+ *
+ * &#64;Canonical
+ * class Building {
+ *     String name
+ *     int floors
+ *     boolean officeSpace
+ * }
+ *
+ * // Constructors are added.
+ * def officeSpace = new Building('Initech office', 1, true)
+ *
+ * // toString() added.
+ * assert officeSpace.toString() == 'Building(Initech office, 1, true)'
+ *
+ * // Default values are used if constructor
+ * // arguments are not assigned.
+ * def theOffice = new Building('Wernham Hogg Paper Company')
+ * assert theOffice.floors == 0
+ * theOffice.officeSpace = true
+ *
+ * def anotherOfficeSpace = new Building(name: 'Initech office', floors: 1, 
officeSpace: true)
+ *
+ * // equals() method is added.
+ * assert anotherOfficeSpace == officeSpace
+ *
+ * // equals() and hashCode() are added, so duplicate is not in Set.
+ * def offices = [officeSpace, anotherOfficeSpace, theOffice] as Set
+ * assert offices.size() == 2
+ * assert offices.name.join(',') == 'Initech office,Wernham Hogg Paper Company'
+ *
+ * &#64;Canonical
+ * &#64;ToString(excludes='age')  // Customize one of the transformations.
+ * class Person {
+ *     String name
+ *     int age
+ * }
+ *
+ * def mrhaki = new Person('mrhaki', 37)
+ * assert mrhaki.toString() == 'Person(mrhaki)'
+ * </pre>
+ *
+ * @see ToString
+ * @see EqualsAndHashCode
+ * @see ImmutableBase
+ * @see KnownImmutable
+ * @see MapConstructor
+ * @see TupleConstructor
+ * @see Canonical
+ * @since 1.7
+ */
+@ToString(cache = true, includeSuperProperties = true)
+@EqualsAndHashCode(cache = true)
+@ImmutableBase
+@TupleConstructor(makeImmutable = true, defaults = false)
+@MapConstructor(makeImmutable = true, noArg = true, includeSuperProperties = 
true)
+@KnownImmutable
+@AnnotationCollector(mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED)
+@interface Immutable { }

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/src/main/groovy/groovy/transform/Immutable.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/Immutable.java 
b/src/main/groovy/groovy/transform/Immutable.java
deleted file mode 100644
index 5f6bf90..0000000
--- a/src/main/groovy/groovy/transform/Immutable.java
+++ /dev/null
@@ -1,249 +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;
-
-import org.codehaus.groovy.transform.GroovyASTTransformationClass;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Class annotation used to assist in the creation of immutable classes.
- * <p>
- * It allows you to write classes in this shortened form:
- * <pre class="groovyTestCase">
- * {@code @groovy.transform.Immutable} class Customer {
- *     String first, last
- *     int age
- *     Date since
- *     Collection favItems
- * }
- * def d = new Date()
- * def c1 = new Customer(first:'Tom', last:'Jones', age:21, since:d, 
favItems:['Books', 'Games'])
- * def c2 = new Customer('Tom', 'Jones', 21, d, ['Books', 'Games'])
- * assert c1 == c2
- * </pre>
- * The {@code @Immutable} annotation instructs the compiler to execute an
- * AST transformation which adds the necessary getters, constructors,
- * equals, hashCode and other helper methods that are typically written
- * when creating immutable classes with the defined properties.
- * <p>
- * A class created in this way has the following characteristics:
- * <ul>
- * <li>The class is automatically made final.
- * <li>Properties must be of an immutable type or a type with a strategy for 
handling non-immutable
- * characteristics. Specifically, the type must be one of the primitive or 
wrapper types, Strings, enums,
- * other {@code @Immutable} classes or known immutables (e.g. java.awt.Color, 
java.net.URI, java.util.UUID).
- * Also handled are Cloneable classes, collections, maps and arrays, other 
"effectively immutable"
- * classes with special handling (e.g. java.util.Date), and usages of 
java.util.Optional where the
- * contained type is immutable (e.g. Optional&lt;String&gt;).
- * <li>Properties automatically have private, final backing fields with 
getters.
- * Attempts to update the property will result in a {@code 
ReadOnlyPropertyException}.
- * <li>A map-based constructor is provided which allows you to set properties 
by name.
- * <li>A tuple-style constructor is provided which allows you to set 
properties in the same order as they are defined.
- * <li>Default {@code equals}, {@code hashCode} and {@code toString} methods 
are provided based on the property values.
- * Though not normally required, you may write your own implementations of 
these methods. For {@code equals} and {@code hashCode},
- * if you do write your own method, it is up to you to obey the general 
contract for {@code equals} methods and supply
- * a corresponding matching {@code hashCode} method.
- * If you do provide one of these methods explicitly, the default 
implementation will be made available in a private
- * "underscore" variant which you can call. E.g., you could provide a (not 
very elegant) multi-line formatted
- * {@code toString} method for {@code Customer} above as follows:
- * <pre>
- *     String toString() {
- *        _toString().replaceAll(/\(/, '(\n\t').replaceAll(/\)/, 
'\n)').replaceAll(/, /, '\n\t')
- *    }
- * </pre>
- * If an "underscore" version of the respective method already exists, then no 
default implementation is provided.
- * <li>{@code Date}s, {@code Cloneable}s and arrays are defensively copied on 
the way in (constructor) and out (getters).
- * Arrays and {@code Cloneable} objects use the {@code clone} method. For your 
own classes,
- * it is up to you to define this method and use deep cloning if appropriate.
- * <li>{@code Collection}s and {@code Map}s are wrapped by immutable wrapper 
classes (but not deeply cloned!).
- * Attempts to update them will result in an {@code 
UnsupportedOperationException}.
- * <li>Fields that are enums or other {@code @Immutable} classes are allowed 
but for an
- * otherwise possible mutable property type, an error is thrown.
- * <li>You don't have to follow Groovy's normal property conventions, e.g. you 
can create an explicit private field and
- * then you can write explicit get and set methods. Such an approach, isn't 
currently prohibited (to give you some
- * wiggle room to get around these conventions) but any fields created in this 
way are deemed not to be part of the
- * significant state of the object and aren't factored into the {@code equals} 
or {@code hashCode} methods.
- * Similarly, you may use static properties (though usually this is 
discouraged) and these too will be ignored
- * as far as significant state is concerned. If you do break standard 
conventions, you do so at your own risk and
- * your objects may no longer be immutable. It is up to you to ensure that 
your objects remain immutable at least
- * to the extent expected in other parts of your program!
- * </ul>
- * Immutable classes are particularly useful for functional and concurrent 
styles of programming
- * and for use as key values within maps. If you want similar functionality to 
what this annotation
- * provides but don't need immutability then consider using {@code @Canonical}.
- * <p>
- * Customising behaviour:
- * <p>
- * You can customise the toString() method provided for you by {@code 
@Immutable}
- * by also adding the {@code @ToString} annotation to your class definition.
- * <p>
- * Limitations:
- * <ul>
- * <li>
- * As outlined above, Arrays and {@code Cloneable} objects use the {@code 
clone} method. For your own classes,
- * it is up to you to define this method and use deep cloning if appropriate.
- * </li>
- * <li>
- * As outlined above, {@code Collection}s and {@code Map}s are wrapped by 
immutable wrapper classes (but not deeply cloned!).
- * </li>
- * <li>
- * Currently {@code BigInteger} and {@code BigDecimal} are deemed immutable 
but see:
- * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6348370
- * </li>
- * <li>
- * {@code java.awt.Color} is treated as "effectively immutable" but is not 
final so while not normally used with child
- * classes, it isn't strictly immutable. Use at your own risk.
- * </li>
- * <li>
- * {@code java.util.Date} is treated as "effectively immutable" but is not 
final so it isn't strictly immutable.
- * Use at your own risk.
- * </li>
- * <li>
- * Groovy's normal map-style naming conventions will not be available if the 
first property
- * has type {@code LinkedHashMap} or if there is a single Map, AbstractMap or 
HashMap property.
- * </li>
- * </ul>
- * <p>More examples:</p>
- 
--------------------------------------------------------------------------------
- * <pre class="groovyTestCase">
- * import groovy.transform.*
- *
- * &#64;Canonical
- * class Building {
- *     String name
- *     int floors
- *     boolean officeSpace
- * }
- *
- * // Constructors are added.
- * def officeSpace = new Building('Initech office', 1, true)
- *
- * // toString() added.
- * assert officeSpace.toString() == 'Building(Initech office, 1, true)'
- *
- * // Default values are used if constructor
- * // arguments are not assigned.
- * def theOffice = new Building('Wernham Hogg Paper Company')
- * assert theOffice.floors == 0
- * theOffice.officeSpace = true
- *
- * def anotherOfficeSpace = new Building(name: 'Initech office', floors: 1, 
officeSpace: true)
- *
- * // equals() method is added.
- * assert anotherOfficeSpace == officeSpace
- *
- * // equals() and hashCode() are added, so duplicate is not in Set.
- * def offices = [officeSpace, anotherOfficeSpace, theOffice] as Set  
- * assert offices.size() == 2 
- * assert offices.name.join(',') == 'Initech office,Wernham Hogg Paper Company'
- *
- * &#64;Canonical
- * &#64;ToString(excludes='age')  // Customize one of the transformations.
- * class Person {
- *     String name
- *     int age
- * }
- *
- * def mrhaki = new Person('mrhaki', 37)
- * assert mrhaki.toString() == 'Person(mrhaki)'
- * </pre>
- *
- * @author Paul King
- * @author Andre Steingress
- * @see groovy.transform.ToString
- * @see groovy.transform.Canonical
- * @since 1.7
- */
[email protected]
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE})
-@GroovyASTTransformationClass("org.codehaus.groovy.transform.ImmutableASTTransformation")
-public @interface Immutable {
-    /**
-     * 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.
-     * Example:
-     * <pre class="groovyTestCase">
-     * {@code @groovy.transform.Immutable}(copyWith = true)
-     * class Person {
-     *     String first, last
-     * }
-     *
-     * def tim   = new Person( 'tim', 'yates' )
-     * def alice = tim.copyWith( first:'alice' )
-     *
-     * assert tim.first   == 'tim'
-     * assert alice.first == 'alice'
-     * </pre>
-     * Unknown keys in the map are ignored, and if the values would not change
-     * the object, then the original object is returned.
-     *
-     * If a method called {@code copyWith} that takes a single parameter 
already
-     * exists in the class, then this setting is ignored, and no method is 
generated.
-     *
-     * @since 2.2.0
-     */
-    boolean copyWith() default false;
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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
new file mode 100644
index 0000000..e6689da
--- /dev/null
+++ b/src/main/groovy/groovy/transform/ImmutableBase.java
@@ -0,0 +1,110 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.transform;
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Class annotation used to assist in the creation of 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.
+ *
+ * @see Immutable
+ * @see MapConstructor
+ * @see TupleConstructor
+ * @since 2.5
+ */
[email protected]
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE})
+@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.
+     * Example:
+     * <pre class="groovyTestCase">
+     * {@code @groovy.transform.Immutable}(copyWith = true)
+     * class Person {
+     *     String first, last
+     * }
+     *
+     * def tim   = new Person( 'tim', 'yates' )
+     * def alice = tim.copyWith( first:'alice' )
+     *
+     * assert tim.first   == 'tim'
+     * assert alice.first == 'alice'
+     * </pre>
+     * Unknown keys in the map are ignored, and if the values would not change
+     * the object, then the original object is returned.
+     *
+     * If a method called {@code copyWith} that takes a single parameter 
already
+     * exists in the class, then this setting is ignored, and no method is 
generated.
+     *
+     * @since 2.2.0
+     */
+    boolean copyWith() default false;
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/src/main/groovy/groovy/transform/KnownImmutable.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/KnownImmutable.java 
b/src/main/groovy/groovy/transform/KnownImmutable.java
new file mode 100644
index 0000000..c7a349d
--- /dev/null
+++ b/src/main/groovy/groovy/transform/KnownImmutable.java
@@ -0,0 +1,39 @@
+/*
+ *  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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Class annotation used as a marker for immutable classes.
+ * Not usually used explicitly but rather implicitly via the {@code 
@Immutable} meta annotation which adds it automatically.
+ * If you create your own Java or Groovy immutable class manually, you can add 
this annotation if you don't want to have
+ * to list your class as one of the known immutable classes.
+ *
+ * @see Immutable
+ * @since 2.5
+ */
[email protected]
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface KnownImmutable {
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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 64cd1da..ccd127a 100644
--- a/src/main/groovy/groovy/transform/MapConstructor.java
+++ b/src/main/groovy/groovy/transform/MapConstructor.java
@@ -27,6 +27,8 @@ import java.lang.annotation.Target;
 
 /**
  * Class annotation used to assist in the creation of map constructors in 
classes.
+ * If the class is also annotated with {@code @KnownImmutable}, then the 
generated
+ * constructor will contain additional code needed for immutable classes.
  * <p>
  * It allows you to write classes in this shortened form:
  * <pre class="groovyTestCase">
@@ -94,6 +96,11 @@ public @interface MapConstructor {
     boolean includeFields() default false;
 
     /**
+     * Whether immutable pre-cautions (defensive copying, cloning, etc.) 
should be applied to incoming/outgoing properties.
+     */
+    boolean makeImmutable() default false;
+
+    /**
      * Include properties in the constructor.
      */
     boolean includeProperties() default true;
@@ -104,6 +111,13 @@ public @interface MapConstructor {
     boolean includeSuperProperties() default false;
 
     /**
+     * Whether to include all properties (as per the JavaBean spec) in the 
generated constructor.
+     * When true, Groovy treats any explicitly created setXxx() methods as 
property setters as per the JavaBean
+     * specification.
+     */
+    boolean allProperties() default false;
+
+    /**
      * By default, properties are set directly using their respective field.
      * By setting {@code useSetters=true} then a writable property will be set 
using its setter.
      * If turning on this flag we recommend that setters that might be called 
are

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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 12e0cb6..bad5518 100644
--- a/src/main/groovy/groovy/transform/TupleConstructor.java
+++ b/src/main/groovy/groovy/transform/TupleConstructor.java
@@ -27,6 +27,9 @@ import java.lang.annotation.Target;
 
 /**
  * Class annotation used to assist in the creation of tuple constructors in 
classes.
+ * If the class is also annotated with {@code @KnownImmutable}, then the 
generated
+ * constructor will contain additional code needed for immutable classes.
+ *
  * Should be used with care with other annotations which create constructors - 
see "Known
  * Limitations" for more details.
  * <p>
@@ -229,6 +232,13 @@ public @interface TupleConstructor {
     boolean force() default false;
 
     /**
+     * Whether immutable pre-cautions (defensive copying, cloning, etc.) 
should be applied to incoming/outgoing properties.
+     *
+     * @since 2.5.0
+     */
+    boolean makeImmutable() default false;
+
+    /**
      * Used to set whether default value processing is enabled (the default) 
or disabled.
      *
      * By default, every constructor parameter is given a default value. This 
value will

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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 24313f6..e8ca861 100644
--- a/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/AbstractASTTransformation.java
@@ -250,7 +250,7 @@ public abstract class AbstractASTTransformation implements 
Opcodes, ASTTransform
         return true;
     }
 
-    public boolean hasAnnotation(ClassNode cNode, ClassNode annotation) {
+    public static boolean hasAnnotation(ClassNode cNode, ClassNode annotation) 
{
         List annots = cNode.getAnnotations(annotation);
         return (annots != null && !annots.isEmpty());
     }

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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 97d5033..dcbe6d0 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/ImmutableASTTransformation.java
@@ -21,7 +21,8 @@ package org.codehaus.groovy.transform;
 import groovy.lang.MetaClass;
 import groovy.lang.MissingPropertyException;
 import groovy.lang.ReadOnlyPropertyException;
-import groovy.transform.Immutable;
+import groovy.transform.ImmutableBase;
+import groovy.transform.KnownImmutable;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
@@ -50,6 +51,7 @@ import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 import org.codehaus.groovy.runtime.InvokerHelper;
 import org.codehaus.groovy.runtime.ReflectionMethodInvoker;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -97,16 +99,9 @@ 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;
-import static 
org.codehaus.groovy.transform.EqualsAndHashCodeASTTransformation.createEquals;
-import static 
org.codehaus.groovy.transform.EqualsAndHashCodeASTTransformation.createHashCode;
-import static 
org.codehaus.groovy.transform.ToStringASTTransformation.createToString;
 
 /**
  * Handles generation of code for the @Immutable annotation.
- *
- * @author Paul King
- * @author Andre Steingress
- * @author Tim Yates
  */
 @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
 public class ImmutableASTTransformation extends AbstractASTTransformation {
@@ -138,13 +133,16 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
             "java.net.URI",
             "java.util.UUID"
     );
-    private static final Class MY_CLASS = groovy.transform.Immutable.class;
+    private static final Class MY_CLASS = ImmutableBase.class;
+    private static final Class<? extends Annotation> KNOWN_IMMUTABLE_CLASS = 
KnownImmutable.class;
+    private static final Class<? extends Annotation> IMMUTABLE_BASE_CLASS = 
ImmutableBase.class;
+    private static final ClassNode IMMUTABLE_BASE_TYPE = 
makeWithoutCaching(IMMUTABLE_BASE_CLASS, false);
     public static final ClassNode MY_TYPE = make(MY_CLASS);
     static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
-    static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = 
"knownImmutableClasses";
-    static final String MEMBER_KNOWN_IMMUTABLES = "knownImmutables";
-    static final String MEMBER_ADD_COPY_WITH = "copyWith";
-    static final String COPY_WITH_METHOD = "copyWith";
+    private static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = 
"knownImmutableClasses";
+    private static final String MEMBER_KNOWN_IMMUTABLES = "knownImmutables";
+    private static final String MEMBER_ADD_COPY_WITH = "copyWith";
+    private static final String COPY_WITH_METHOD = "copyWith";
 
     private static final ClassNode DATE_TYPE = make(Date.class);
     private static final ClassNode CLONEABLE_TYPE = make(Cloneable.class);
@@ -165,62 +163,56 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         init(nodes, source);
         AnnotatedNode parent = (AnnotatedNode) nodes[1];
         AnnotationNode node = (AnnotationNode) nodes[0];
-        // temporarily have weaker check which allows for old Deprecated 
Annotation
-//        if (!MY_TYPE.equals(node.getClassNode())) return;
-        if (!node.getClassNode().getName().endsWith(".Immutable")) return;
-        List<PropertyNode> newProperties = new ArrayList<PropertyNode>();
+        if (!MY_TYPE.equals(node.getClassNode())) return;
 
         if (parent instanceof ClassNode) {
-            final List<String> knownImmutableClasses = 
getKnownImmutableClasses(node);
-            final List<String> knownImmutables = getKnownImmutables(node);
-
-            ClassNode cNode = (ClassNode) parent;
-            String cName = cNode.getName();
-            if (!checkNotInterface(cNode, MY_TYPE_NAME)) return;
-            if (!checkPropertyList(cNode, knownImmutables, "knownImmutables", 
node, MY_TYPE_NAME, false)) return;
-            makeClassFinal(cNode);
-
-            final List<PropertyNode> pList = getInstanceProperties(cNode);
-            for (PropertyNode pNode : pList) {
-                adjustPropertyForImmutability(pNode, newProperties);
-            }
-            for (PropertyNode pNode : newProperties) {
-                cNode.getProperties().remove(pNode);
-                addProperty(cNode, pNode);
-            }
-            final List<FieldNode> fList = cNode.getFields();
-            for (FieldNode fNode : fList) {
-                ensureNotPublic(cName, fNode);
-            }
-            boolean includeSuperProperties = false;
-            if (hasAnnotation(cNode, 
TupleConstructorASTTransformation.MY_TYPE)) {
-                AnnotationNode tupleCons = 
cNode.getAnnotations(TupleConstructorASTTransformation.MY_TYPE).get(0);
-                includeSuperProperties = memberHasValue(tupleCons, 
"includeSuperProperties", true);
-                if (unsupportedTupleAttribute(tupleCons, "excludes")) return;
-                if (unsupportedTupleAttribute(tupleCons, "includes")) return;
-                if (unsupportedTupleAttribute(tupleCons, "includeFields")) 
return;
-                if (unsupportedTupleAttribute(tupleCons, "includeProperties")) 
return;
-                if (unsupportedTupleAttribute(tupleCons, 
"includeSuperFields")) return;
-                if (unsupportedTupleAttribute(tupleCons, "callSuper")) return;
-                if (unsupportedTupleAttribute(tupleCons, "force")) return;
-            }
-            createConstructors(cNode, knownImmutableClasses, knownImmutables, 
includeSuperProperties);
-            if (!hasAnnotation(cNode, 
EqualsAndHashCodeASTTransformation.MY_TYPE)) {
-                createHashCode(cNode, true, false, false, null, null);
-                createEquals(cNode, false, false, false, null, null);
-            }
-            if (!hasAnnotation(cNode, ToStringASTTransformation.MY_TYPE)) {
-                createToString(cNode, false, false, null, null, false, true, 
true, true);
-            }
-            if( memberHasValue(node, MEMBER_ADD_COPY_WITH, true) &&
-                    !pList.isEmpty() &&
-                !hasDeclaredMethod(cNode, COPY_WITH_METHOD, 1) ) {
-                createCopyWith( cNode, pList ) ;
-            }
+            doMakeImmutable((ClassNode) parent, node);
+        }
+    }
+
+    private void doMakeImmutable(ClassNode cNode, AnnotationNode node) {
+        List<PropertyNode> newProperties = new ArrayList<PropertyNode>();
+//        final List<String> knownImmutableClasses = 
getKnownImmutableClasses(this, node);
+        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);
+        }
+        for (PropertyNode pNode : newProperties) {
+            cNode.getProperties().remove(pNode);
+            addProperty(cNode, pNode);
+        }
+        final List<FieldNode> fList = cNode.getFields();
+        for (FieldNode fNode : fList) {
+            ensureNotPublic(this, cName, fNode);
+        }
+        boolean includeSuperProperties = false;
+        if (hasAnnotation(cNode, TupleConstructorASTTransformation.MY_TYPE)) {
+            AnnotationNode tupleCons = 
cNode.getAnnotations(TupleConstructorASTTransformation.MY_TYPE).get(0);
+//            includeSuperProperties = memberHasValue(tupleCons, 
"includeSuperProperties", true);
+            if (unsupportedTupleAttribute(tupleCons, "excludes")) return;
+            if (unsupportedTupleAttribute(tupleCons, "includes")) return;
+            if (unsupportedTupleAttribute(tupleCons, "includeFields")) return;
+            if (unsupportedTupleAttribute(tupleCons, "includeProperties")) 
return;
+            if (unsupportedTupleAttribute(tupleCons, "includeSuperFields")) 
return;
+            if (unsupportedTupleAttribute(tupleCons, "callSuper")) return;
+            if (unsupportedTupleAttribute(tupleCons, "force")) return;
+        }
+        if (!validateConstructors(cNode)) return;
+//        createConstructors(this, cNode, knownImmutableClasses, 
knownImmutables, includeSuperProperties);
+        if (memberHasValue(node, MEMBER_ADD_COPY_WITH, true) && 
!pList.isEmpty() &&
+                !hasDeclaredMethod(cNode, COPY_WITH_METHOD, 1)) {
+            createCopyWith(cNode, pList);
         }
     }
 
-    protected boolean unsupportedTupleAttribute(AnnotationNode anno, String 
memberName) {
+    private boolean unsupportedTupleAttribute(AnnotationNode anno, String 
memberName) {
         if (getMemberValue(anno, memberName) != null) {
             String tname = TupleConstructorASTTransformation.MY_TYPE_NAME;
             addError("Error during " + MY_TYPE_NAME + " processing: Annotation 
attribute '" + memberName +
@@ -240,7 +232,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
                 break;
             }
         }
-        if (argsParam!=null) {
+        if (argsParam != null) {
             final Parameter arg = argsParam;
             ClassCodeVisitorSupport variableExpressionFix = new 
ClassCodeVisitorSupport() {
                 @Override
@@ -260,14 +252,14 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         }
     }
 
-    private List<String> getKnownImmutableClasses(AnnotationNode node) {
+    static List<String> getKnownImmutableClasses(AbstractASTTransformation 
xform, AnnotationNode node) {
         final List<String> immutableClasses = new ArrayList<String>();
 
         final Expression expression = 
node.getMember(MEMBER_KNOWN_IMMUTABLE_CLASSES);
         if (expression == null) return immutableClasses;
 
         if (!(expression instanceof ListExpression)) {
-            addError("Use the Groovy list notation [el1, el2] to specify known 
immutable classes via \"" + MEMBER_KNOWN_IMMUTABLE_CLASSES + "\"", node);
+            xform.addError("Use the Groovy list notation [el1, el2] to specify 
known immutable classes via \"" + MEMBER_KNOWN_IMMUTABLE_CLASSES + "\"", node);
             return immutableClasses;
         }
 
@@ -281,14 +273,14 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         return immutableClasses;
     }
 
-    private List<String> getKnownImmutables(AnnotationNode node) {
+    static List<String> getKnownImmutables(AbstractASTTransformation xform, 
AnnotationNode node) {
         final List<String> immutables = new ArrayList<String>();
 
         final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLES);
         if (expression == null) return immutables;
 
         if (!(expression instanceof ListExpression)) {
-            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 + "\"", node);
             return immutables;
         }
 
@@ -302,21 +294,35 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         return immutables;
     }
 
-    private void makeClassFinal(ClassNode cNode) {
+    private static void makeClassFinal(AbstractASTTransformation xform, 
ClassNode cNode) {
         int modifiers = cNode.getModifiers();
         if ((modifiers & ACC_FINAL) == 0) {
             if ((modifiers & (ACC_ABSTRACT | ACC_SYNTHETIC)) == (ACC_ABSTRACT 
| ACC_SYNTHETIC)) {
-                addError("Error during " + MY_TYPE_NAME + " processing: 
annotation found on inappropriate class " + cNode.getName(), cNode);
+                xform.addError("Error during " + MY_TYPE_NAME + " processing: 
annotation found on inappropriate class " + cNode.getName(), cNode);
                 return;
             }
             cNode.setModifiers(modifiers | ACC_FINAL);
         }
     }
 
-    private void createConstructors(ClassNode cNode, List<String> 
knownImmutableClasses, List<String> knownImmutables, boolean 
includeSuperProperties) {
-        if (!validateConstructors(cNode)) return;
+    private static void createConstructors(AbstractASTTransformation xform, 
ClassNode cNode, List<String> knownImmutableClasses, List<String> 
knownImmutables, boolean includeSuperProperties, boolean allProperties) {
+        List<PropertyNode> list = getProperties(cNode, includeSuperProperties, 
allProperties);
+        boolean specialHashMapCase = isSpecialHashMapCase(list);
+        if (specialHashMapCase) {
+            createConstructorMapSpecial(cNode, list);
+        } else {
+            createConstructorMap(xform, cNode, list, knownImmutableClasses, 
knownImmutables);
+            createConstructorOrdered(cNode, list);
+        }
+    }
 
+    static boolean isSpecialHashMapCase(List<PropertyNode> list) {
+        return list.size() == 1 && 
list.get(0).getField().getType().equals(HASHMAP_TYPE);
+    }
+
+    static List<PropertyNode> getProperties(ClassNode cNode, boolean 
includeSuperProperties, boolean allProperties) {
         List<PropertyNode> list = getInstanceProperties(cNode);
+        //addPseudoProperties
         if (includeSuperProperties) {
             ClassNode next = cNode.getSuperClass();
             while (next != null) {
@@ -326,16 +332,10 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
                 next = next.getSuperClass();
             }
         }
-        boolean specialHashMapCase = list.size() == 1 && 
list.get(0).getField().getType().equals(HASHMAP_TYPE);
-        if (specialHashMapCase) {
-            createConstructorMapSpecial(cNode, list);
-        } else {
-            createConstructorMap(cNode, list, knownImmutableClasses, 
knownImmutables);
-            createConstructorOrdered(cNode, list);
-        }
+        return list;
     }
 
-    private static void createConstructorOrdered(ClassNode cNode, 
List<PropertyNode> list) {
+    static void createConstructorOrdered(ClassNode cNode, List<PropertyNode> 
list) {
         final MapExpression argMap = new MapExpression();
         final Parameter[] orderedParams = new Parameter[list.size()];
         int index = 0;
@@ -388,17 +388,24 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         return castX(type, smce);
     }
 
-    private static void createConstructorMapSpecial(ClassNode cNode, 
List<PropertyNode> list) {
+    static void createConstructorMapSpecial(ClassNode cNode, 
List<PropertyNode> list) {
         final BlockStatement body = new BlockStatement();
         
body.addStatement(createConstructorStatementMapSpecial(list.get(0).getField()));
         createConstructorMapCommon(cNode, body);
     }
 
-    private void createConstructorMap(ClassNode cNode, List<PropertyNode> 
list, List<String> knownImmutableClasses, List<String> knownImmutables) {
+    static void createConstructorMap(AbstractASTTransformation xform, 
ClassNode cNode, List<PropertyNode> list) {
+        AnnotationNode annoImmutable = 
cNode.getAnnotations(IMMUTABLE_BASE_TYPE).get(0);
+        final List<String> knownImmutableClasses = 
getKnownImmutableClasses(xform, annoImmutable);
+        final List<String> knownImmutables = getKnownImmutables(xform, 
annoImmutable);
+        createConstructorMap(xform, cNode, list, knownImmutableClasses, 
knownImmutables);
+    }
+
+    private static void createConstructorMap(AbstractASTTransformation xform, 
ClassNode cNode, List<PropertyNode> list, List<String> knownImmutableClasses, 
List<String> knownImmutables) {
         final BlockStatement body = new BlockStatement();
         body.addStatement(ifS(equalsNullX(varX("args")), assignS(varX("args"), 
new MapExpression())));
         for (PropertyNode pNode : list) {
-            body.addStatement(createConstructorStatement(cNode, pNode, 
knownImmutableClasses, knownImmutables));
+            body.addStatement(createConstructorStatement(xform, cNode, pNode, 
knownImmutableClasses, knownImmutables));
         }
         // check for missing properties
         body.addStatement(stmt(callX(SELF_TYPE, "checkPropNames", args("this", 
"args"))));
@@ -442,7 +449,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         final ClassNode fieldType = fieldExpr.getType();
         final Expression initExpr = fNode.getInitialValueExpression();
         final Statement assignInit;
-        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression)initExpr).isNullExpression())) {
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
             assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
         } else {
             assignInit = assignS(fieldExpr, cloneCollectionExpr(initExpr, 
fieldType));
@@ -466,11 +473,11 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         );
     }
 
-    private void ensureNotPublic(String cNode, FieldNode fNode) {
+    private static void ensureNotPublic(AbstractASTTransformation xform, 
String cNode, FieldNode fNode) {
         String fName = fNode.getName();
         // TODO: do we need to lock down things like: $ownClass
         if (fNode.isPublic() && !fName.contains("$") && !(fNode.isStatic() && 
fNode.isFinal())) {
-            addError("Public field '" + fName + "' not allowed for " + 
MY_TYPE_NAME + " class '" + cNode + "'.", fNode);
+            xform.addError("Public field '" + fName + "' not allowed for " + 
MY_TYPE_NAME + " class '" + cNode + "'.", fNode);
         }
     }
 
@@ -489,20 +496,19 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         for (ConstructorNode constructorNode : declaredConstructors) {
             // allow constructors added by other transforms if flagged as safe
             Object nodeMetaData = 
constructorNode.getNodeMetaData(IMMUTABLE_SAFE_FLAG);
-            if (nodeMetaData != null && ((Boolean)nodeMetaData)) {
+            if (nodeMetaData != null && ((Boolean) nodeMetaData)) {
                 continue;
             }
-            // TODO: allow constructors which only call provided constructor?
             addError("Explicit constructors not allowed for " + MY_TYPE_NAME + 
" class: " + cNode.getNameWithoutPackage(), constructorNode);
             return false;
         }
         return true;
     }
 
-    private Statement createConstructorStatement(ClassNode cNode, PropertyNode 
pNode, List<String> knownImmutableClasses, List<String> knownImmutables) {
+    private static Statement 
createConstructorStatement(AbstractASTTransformation xform, ClassNode cNode, 
PropertyNode pNode, List<String> knownImmutableClasses, List<String> 
knownImmutables) {
         FieldNode fNode = pNode.getField();
         final ClassNode fieldType = fNode.getType();
-        Statement statement = null;
+        Statement statement;
         if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) {
             statement = createConstructorStatementArrayOrCloneable(fNode);
         } else if (isKnownImmutableClass(fieldType, knownImmutableClasses) || 
isKnownImmutable(pNode.getName(), knownImmutables)) {
@@ -512,7 +518,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         } else if (isOrImplements(fieldType, COLLECTION_TYPE) || 
fieldType.isDerivedFrom(COLLECTION_TYPE) || isOrImplements(fieldType, MAP_TYPE) 
|| fieldType.isDerivedFrom(MAP_TYPE)) {
             statement = createConstructorStatementCollection(fNode);
         } else if (fieldType.isResolved()) {
-            addError(createErrorMessage(cNode.getName(), fNode.getName(), 
fieldType.getName(), "compiling"), fNode);
+            xform.addError(createErrorMessage(cNode.getName(), 
fNode.getName(), fieldType.getName(), "compiling"), fNode);
             statement = EmptyStatement.INSTANCE;
         } else {
             statement = createConstructorStatementGuarded(cNode, fNode);
@@ -524,7 +530,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         final Expression fieldExpr = varX(fNode);
         Expression initExpr = fNode.getInitialValueExpression();
         final Statement assignInit;
-        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression)initExpr).isNullExpression())) {
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
             assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
         } else {
             assignInit = assignS(fieldExpr, checkUnresolved(fNode, initExpr));
@@ -543,7 +549,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         ClassNode fieldType = fieldExpr.getType();
         Expression initExpr = fNode.getInitialValueExpression();
         final Statement assignInit;
-        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression)initExpr).isNullExpression())) {
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
             assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
         } else {
             assignInit = assignS(fieldExpr, cloneCollectionExpr(initExpr, 
fieldType));
@@ -569,7 +575,8 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
             if (optionalType.isResolved() && !optionalType.isPlaceholder() && 
!optionalType.isWildcard()) {
                 String name = optionalType.getType().getName();
                 if (inImmutableList(name) || 
knownImmutableClasses.contains(name)) return true;
-                if (optionalType.getType().isEnum() || 
!optionalType.getType().getAnnotations(MY_TYPE).isEmpty()) return true;
+                if (optionalType.getType().isEnum() || 
!optionalType.getType().getAnnotations(MY_TYPE).isEmpty())
+                    return true;
             }
         }
         return fieldType.isEnum() ||
@@ -591,7 +598,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         ClassNode fieldType = fNode.getType();
         final Expression array = findArg(fNode.getName());
         final Statement assignInit;
-        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression)initExpr).isNullExpression())) {
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
             assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
         } else {
             assignInit = assignS(fieldExpr, 
cloneArrayOrCloneableExpr(initExpr, fieldType));
@@ -603,7 +610,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         final Expression fieldExpr = varX(fNode);
         Expression initExpr = fNode.getInitialValueExpression();
         final Statement assignInit;
-        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression)initExpr).isNullExpression())) {
+        if (initExpr == null || (initExpr instanceof ConstantExpression && 
((ConstantExpression) initExpr).isNullExpression())) {
             assignInit = assignS(fieldExpr, 
ConstantExpression.EMPTY_EXPRESSION);
         } else {
             assignInit = assignS(fieldExpr, cloneDateExpr(initExpr));
@@ -644,11 +651,10 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
     }
 
     private static String createErrorMessage(String className, String 
fieldName, String typeName, String mode) {
-        return MY_TYPE_NAME + " processor doesn't know how to handle field '" 
+ fieldName + "' of type '" +
-                prettyTypeName(typeName) + "' while " + mode + " class " + 
className + ".\n" +
-                MY_TYPE_NAME + " classes only support properties with 
effectively immutable types including:\n" +
+        return "Unsupported type (" + prettyTypeName(typeName) + ") found for 
field '" + fieldName + "' while " + mode + " immutable class " + className + 
".\n" +
+                "Immutable classes only support properties with effectively 
immutable types including:\n" +
                 "- Strings, primitive types, wrapper types, Class, BigInteger 
and BigDecimal, enums\n" +
-                "- other " + MY_TYPE_NAME + " classes and known immutables 
(java.awt.Color, java.net.URI)\n" +
+                "- classes annotated with @KnownImmutable and known immutables 
(java.awt.Color, java.net.URI)\n" +
                 "- Cloneable classes, collections, maps and arrays, and other 
classes with special handling (java.util.Date)\n" +
                 "Other restrictions apply, please see the groovydoc for " + 
MY_TYPE_NAME + " for further details";
     }
@@ -669,7 +675,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         return safeExpression(fieldExpr, expression);
     }
 
-    private static Statement createCheckForProperty( final PropertyNode pNode 
) {
+    private static Statement createCheckForProperty(final PropertyNode pNode) {
         return block(
                 new VariableScope(),
                 ifElseS(
@@ -771,22 +777,37 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
     public static Object checkImmutable(String className, String fieldName, 
Object field) {
         if (field == null || field instanceof Enum || 
inImmutableList(field.getClass().getName())) return field;
         if (field instanceof Collection) return 
DefaultGroovyMethods.asImmutable((Collection) field);
-        if (field.getClass().getAnnotation(MY_CLASS) != null) return field;
+        if (getAnnotationByName(field, "groovy.transform.Immutable") != null) 
return field;
+
         final String typeName = field.getClass().getName();
         throw new RuntimeException(createErrorMessage(className, fieldName, 
typeName, "constructing"));
     }
 
+    private static Annotation getAnnotationByName(Object field, String name) {
+        // find longhand since the annotation from earlier versions is now a 
meta annotation
+        for (Annotation an : field.getClass().getAnnotations()) {
+            if (an.getClass().getName().equals(name)) {
+                return an;
+            }
+        }
+        return null;
+    }
+
     @SuppressWarnings("Unchecked")
     public static Object checkImmutable(Class<?> clazz, String fieldName, 
Object field) {
-        Immutable immutable = (Immutable) clazz.getAnnotation(MY_CLASS);
-        List<Class> knownImmutableClasses = new ArrayList<Class>();
-        if (immutable != null && immutable.knownImmutableClasses().length > 0) 
{
-            knownImmutableClasses = 
Arrays.asList(immutable.knownImmutableClasses());
+        if (field == null || field instanceof Enum || 
knownImmutable(field.getClass())) {
+            return field;
         }
 
-        if (field == null || field instanceof Enum || 
inImmutableList(field.getClass().getName()) || 
knownImmutableClasses.contains(field.getClass()))
-            return field;
-        if (field.getClass().getAnnotation(MY_CLASS) != null) return field;
+        boolean isImmutable = false;
+        for (Annotation an : field.getClass().getAnnotations()) {
+            if 
(an.getClass().getName().startsWith("groovy.transform.Immutable")) {
+                isImmutable = true;
+                break;
+            }
+        }
+        if (isImmutable) return field;
+
         if (field instanceof Collection) {
             Field declaredField;
             try {
@@ -796,8 +817,7 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
                     return DefaultGroovyMethods.asImmutable((Collection) 
field);
                 }
                 // potentially allow Collection coercion for a constructor
-                if (fieldType.getAnnotation(MY_CLASS) != null) return field;
-                if (inImmutableList(fieldType.getName()) || 
knownImmutableClasses.contains(fieldType)) {
+                if (knownImmutable(fieldType)) {
                     return field;
                 }
             } catch (NoSuchFieldException ignore) {
@@ -808,6 +828,10 @@ public class ImmutableASTTransformation extends 
AbstractASTTransformation {
         throw new RuntimeException(createErrorMessage(clazz.getName(), 
fieldName, typeName, "constructing"));
     }
 
+    private static boolean knownImmutable(Class<?> clazz) {
+        return inImmutableList(clazz.getName()) || 
clazz.getAnnotation(KNOWN_IMMUTABLE_CLASS) != null;
+    }
+
     public static void checkPropNames(Object instance, Map<String, Object> 
args) {
         final MetaClass metaClass = InvokerHelper.getMetaClass(instance);
         for (String k : args.keySet()) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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 77f31dc..b6407d3 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
@@ -28,6 +28,7 @@ import org.codehaus.groovy.ast.ConstructorNode;
 import org.codehaus.groovy.ast.DynamicVariable;
 import org.codehaus.groovy.ast.FieldNode;
 import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
 import org.codehaus.groovy.ast.expr.ArgumentListExpression;
 import org.codehaus.groovy.ast.expr.ClosureExpression;
 import org.codehaus.groovy.ast.expr.Expression;
@@ -75,6 +76,8 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
     static final ClassNode MY_TYPE = make(MY_CLASS);
     static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
     private static final ClassNode MAP_TYPE = makeWithoutCaching(Map.class, 
false);
+//    private static final ClassNode IMMUTABLE_CLASS_TYPE = 
makeWithoutCaching(KnownImmutable.class, false);
+//    private static final ClassNode IMMUTABLE_BASE_TYPE = 
makeWithoutCaching(ImmutableBase.class, false);
 //    private static final ClassNode CHECK_METHOD_TYPE = 
make(ImmutableASTTransformation.class);
 
     public void visit(ASTNode[] nodes, SourceUnit source) {
@@ -90,15 +93,17 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
             boolean includeProperties = !memberHasValue(anno, 
"includeProperties", false);
             boolean includeSuperProperties = memberHasValue(anno, 
"includeSuperProperties", true);
             boolean useSetters = memberHasValue(anno, "useSetters", true);
+            boolean allProperties = memberHasValue(anno, "allProperties", 
true);
             boolean noArg = memberHasValue(anno, "noArg", true);
             List<String> excludes = getMemberStringList(anno, "excludes");
             List<String> includes = getMemberStringList(anno, "includes");
             boolean allNames = memberHasValue(anno, "allNames", true);
+            boolean makeImmutable = memberHasValue(anno, "makeImmutable", 
true);
             if (!checkIncludeExcludeUndefinedAware(anno, excludes, includes, 
MY_TYPE_NAME)) return;
-            if (!checkPropertyList(cNode, includes, "includes", anno, 
MY_TYPE_NAME, includeFields, includeSuperProperties, false)) return;
-            if (!checkPropertyList(cNode, excludes, "excludes", anno, 
MY_TYPE_NAME, includeFields, includeSuperProperties, false)) return;
-            // if @Immutable is found, let it pick up options and do work so 
we'll skip
-            if (hasAnnotation(cNode, ImmutableASTTransformation.MY_TYPE)) 
return;
+            if (!checkPropertyList(cNode, includes, "includes", anno, 
MY_TYPE_NAME, includeFields, includeSuperProperties, false))
+                return;
+            if (!checkPropertyList(cNode, excludes, "excludes", anno, 
MY_TYPE_NAME, includeFields, includeSuperProperties, false))
+                return;
 
             Expression pre = anno.getMember("pre");
             if (pre != null && !(pre instanceof ClosureExpression)) {
@@ -111,7 +116,8 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
                 return;
             }
 
-            createConstructors(cNode, includeFields, includeProperties, 
includeSuperProperties, useSetters, noArg, allNames, excludes, includes, 
(ClosureExpression) pre, (ClosureExpression) post, source);
+            createConstructors(this, cNode, includeFields, includeProperties, 
includeSuperProperties, useSetters, noArg, allNames, allProperties, 
makeImmutable, excludes, includes, (ClosureExpression) pre, (ClosureExpression) 
post, source);
+
             if (pre != null) {
                 anno.setMember("pre", new ClosureExpression(new Parameter[0], 
EmptyStatement.INSTANCE));
             }
@@ -121,12 +127,24 @@ public class MapConstructorASTTransformation extends 
AbstractASTTransformation {
         }
     }
 
-    public static void createConstructors(ClassNode cNode, boolean 
includeFields, boolean includeProperties, boolean includeSuperProperties, 
boolean useSetters, boolean noArg, boolean allNames, List<String> excludes, 
List<String> includes, ClosureExpression pre, ClosureExpression post, 
SourceUnit source) {
+    private static void createConstructors(AbstractASTTransformation xform, 
ClassNode cNode, boolean includeFields, boolean includeProperties, boolean 
includeSuperProperties, boolean useSetters, boolean noArg, boolean allNames, 
boolean allProperties, boolean makeImmutable, List<String> excludes, 
List<String> includes, ClosureExpression pre, ClosureExpression post, 
SourceUnit source) {
         List<ConstructorNode> constructors = cNode.getDeclaredConstructors();
         boolean foundEmpty = constructors.size() == 1 && 
constructors.get(0).getFirstStatement() == null;
         // HACK: JavaStubGenerator could have snuck in a constructor we don't 
want
         if (foundEmpty) constructors.remove(0);
 
+        // TODO remove duplicated code from alternative paths below
+        if (makeImmutable) {
+            List<PropertyNode> list = 
ImmutableASTTransformation.getProperties(cNode, includeSuperProperties, 
allProperties);
+            boolean specialHashMapCase = 
ImmutableASTTransformation.isSpecialHashMapCase(list);
+            if (specialHashMapCase) {
+                ImmutableASTTransformation.createConstructorMapSpecial(cNode, 
list);
+            } else {
+                ImmutableASTTransformation.createConstructorMap(xform, cNode, 
list);
+            }
+            return;
+        }
+
         List<FieldNode> superList = new ArrayList<FieldNode>();
         if (includeSuperProperties) {
             superList.addAll(getSuperPropertyFields(cNode.getSuperClass()));

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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 ce2e25d..179ba1a 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
@@ -18,6 +18,7 @@
  */
 package org.codehaus.groovy.transform;
 
+import groovy.transform.KnownImmutable;
 import groovy.transform.TupleConstructor;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotatedNode;
@@ -61,8 +62,6 @@ import static 
org.codehaus.groovy.ast.tools.GeneralUtils.copyStatementsWithSuper
 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.getAllFields;
-import static 
org.codehaus.groovy.ast.tools.GeneralUtils.getInstanceNonPropertyFields;
-import static 
org.codehaus.groovy.ast.tools.GeneralUtils.getInstancePropertyFields;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
@@ -84,6 +83,7 @@ public class TupleConstructorASTTransformation extends 
AbstractASTTransformation
     private static final ClassNode LHMAP_TYPE = 
makeWithoutCaching(LinkedHashMap.class, false);
     private static final ClassNode HMAP_TYPE = 
makeWithoutCaching(HashMap.class, false);
     private static final ClassNode CHECK_METHOD_TYPE = 
make(ImmutableASTTransformation.class);
+    private static final ClassNode IMMUTABLE_CLASS_TYPE = 
makeWithoutCaching(KnownImmutable.class, false);
     private static final Map<Class<?>, Expression> primitivesInitialValues;
 
     static {
@@ -118,14 +118,13 @@ public class TupleConstructorASTTransformation extends 
AbstractASTTransformation
             boolean defaults = !memberHasValue(anno, "defaults", false);
             boolean useSetters = memberHasValue(anno, "useSetters", true);
             boolean allProperties = memberHasValue(anno, "allProperties", 
true);
+            boolean makeImmutable = memberHasValue(anno, "makeImmutable", 
true);
             List<String> excludes = getMemberStringList(anno, "excludes");
             List<String> includes = getMemberStringList(anno, "includes");
             boolean allNames = memberHasValue(anno, "allNames", true);
             if (!checkIncludeExcludeUndefinedAware(anno, excludes, includes, 
MY_TYPE_NAME)) return;
             if (!checkPropertyList(cNode, includes, "includes", anno, 
MY_TYPE_NAME, includeFields, includeSuperProperties, false, 
includeSuperFields)) return;
             if (!checkPropertyList(cNode, excludes, "excludes", anno, 
MY_TYPE_NAME, includeFields, includeSuperProperties, false, 
includeSuperFields)) return;
-            // if @Immutable is found, let it pick up options and do work so 
we'll skip
-            if (hasAnnotation(cNode, ImmutableASTTransformation.MY_TYPE)) 
return;
             Expression pre = anno.getMember("pre");
             if (pre != null && !(pre instanceof ClosureExpression)) {
                 addError("Expected closure value for annotation parameter 
'pre'. Found " + pre, cNode);
@@ -136,9 +135,20 @@ public class TupleConstructorASTTransformation extends 
AbstractASTTransformation
                 addError("Expected closure value for annotation parameter 
'post'. Found " + post, cNode);
                 return;
             }
-            createConstructor(this, cNode, includeFields, includeProperties, 
includeSuperFields, includeSuperProperties,
-                    callSuper, force, excludes, includes, useSetters, 
defaults, allNames, allProperties, sourceUnit,
-                    (ClosureExpression) pre, (ClosureExpression) post);
+
+            // TODO remove duplication between various paths below
+            List<PropertyNode> list = 
ImmutableASTTransformation.getProperties(cNode, includeSuperProperties, 
allProperties);
+            boolean specialHashMapCase = 
ImmutableASTTransformation.isSpecialHashMapCase(list);
+            if (makeImmutable) {
+                if (!specialHashMapCase) {
+                    ImmutableASTTransformation.createConstructorOrdered(cNode, 
list);
+                }
+            } else {
+                createConstructor(this, cNode, includeFields, 
includeProperties, includeSuperFields, includeSuperProperties,
+                        callSuper, force, excludes, includes, useSetters, 
defaults, allNames, allProperties, sourceUnit,
+                        (ClosureExpression) pre, (ClosureExpression) post);
+            }
+
             if (pre != null) {
                 anno.setMember("pre", new ClosureExpression(new Parameter[0], 
EmptyStatement.INSTANCE));
             }

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/src/spec/test/builder/ObjectGraphBuilderTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/builder/ObjectGraphBuilderTest.groovy 
b/src/spec/test/builder/ObjectGraphBuilderTest.groovy
index a32094f..4f3ccc5 100644
--- a/src/spec/test/builder/ObjectGraphBuilderTest.groovy
+++ b/src/spec/test/builder/ObjectGraphBuilderTest.groovy
@@ -125,7 +125,7 @@ builder.classNameResolver = "com.acme"
 
 // tag::newinstanceresolver[]
 builder.newInstanceResolver = { Class klazz, Map attributes ->
-    if (klazz.isAnnotationPresent(Immutable)) {
+    if (klazz.getConstructor(HashMap)) {
         def o = klazz.newInstance(attributes)
         attributes.clear()
         return o

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/src/test/org/codehaus/groovy/tools/stubgenerator/ImmutableWithJointCompilationGroovy6836StubTest.groovy
----------------------------------------------------------------------
diff --git 
a/src/test/org/codehaus/groovy/tools/stubgenerator/ImmutableWithJointCompilationGroovy6836StubTest.groovy
 
b/src/test/org/codehaus/groovy/tools/stubgenerator/ImmutableWithJointCompilationGroovy6836StubTest.groovy
index 82ed1d8..6a73e69 100644
--- 
a/src/test/org/codehaus/groovy/tools/stubgenerator/ImmutableWithJointCompilationGroovy6836StubTest.groovy
+++ 
b/src/test/org/codehaus/groovy/tools/stubgenerator/ImmutableWithJointCompilationGroovy6836StubTest.groovy
@@ -20,8 +20,6 @@ package org.codehaus.groovy.tools.stubgenerator
 
 /**
  * Checks that {@code @Immutable} classes work correctly with stubs.
- *
- * @author Paul King
  */
 class ImmutableWithJointCompilationGroovy6836StubTest extends 
StringSourcesStubTestCase {
 

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
----------------------------------------------------------------------
diff --git 
a/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
 
b/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
index 6457315..f2ffe43 100644
--- 
a/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
+++ 
b/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
@@ -442,13 +442,13 @@ class CanonicalComponentsTransformTest extends 
GroovyShellTestCase {
     // GROOVY-5864
     void testExternalizeMethodsWithImmutable() {
         try {
-            new GroovyShell().parse """
+            new GroovyShell().parse '''
                 @groovy.transform.ExternalizeMethods
                 @groovy.transform.Immutable
                 class Person {
                     String first
                 }
-            """
+            '''
             fail('The compilation should have failed as the final field first 
(created via @Immutable) is being assigned to (via @ExternalizeMethods).')
         } catch (MultipleCompilationErrorsException e) {
             def syntaxError = e.errorCollector.getSyntaxError(0)
@@ -459,10 +459,10 @@ class CanonicalComponentsTransformTest extends 
GroovyShellTestCase {
     // GROOVY-5864
     void testExternalizeVerifierWithNonExternalizableClass() {
         try {
-            new GroovyShell().parse """
+            new GroovyShell().parse '''
                 @groovy.transform.ExternalizeVerifier
                 class Person { }
-            """
+            '''
             fail("The compilation should have failed as the class doesn't 
implement Externalizable")
         } catch (MultipleCompilationErrorsException e) {
             def syntaxError = e.errorCollector.getSyntaxError(0)
@@ -473,14 +473,14 @@ class CanonicalComponentsTransformTest extends 
GroovyShellTestCase {
     // GROOVY-5864
     void testExternalizeVerifierWithFinalField() {
         try {
-            new GroovyShell().parse """
+            new GroovyShell().parse '''
                 @groovy.transform.ExternalizeVerifier
                 class Person implements Externalizable {
                     final String first
                     void writeExternal(ObjectOutput out)throws IOException{ }
                     void readExternal(ObjectInput objectInput)throws 
IOException,ClassNotFoundException{ }
                 }
-            """
+            '''
             fail("The compilation should have failed as the final field first 
(can't be set inside readExternal).")
         } catch (MultipleCompilationErrorsException e) {
             def syntaxError = e.errorCollector.getSyntaxError(0)
@@ -491,13 +491,13 @@ class CanonicalComponentsTransformTest extends 
GroovyShellTestCase {
     // GROOVY-5864
     void testAutoExternalizeWithoutNoArg() {
         try {
-            new GroovyShell().parse """
+            new GroovyShell().parse '''
                 @groovy.transform.AutoExternalize
                 class Person {
                     Person(String first) {}
                     String first
                 }
-            """
+            '''
             fail("The compilation should have failed as there is no no-arg 
constructor.")
         } catch (MultipleCompilationErrorsException e) {
             def syntaxError = e.errorCollector.getSyntaxError(0)
@@ -508,7 +508,7 @@ class CanonicalComponentsTransformTest extends 
GroovyShellTestCase {
     // GROOVY-5864
     void testExternalizeVerifierWithNonExternalizableField() {
         try {
-            new GroovyShell().parse """
+            new GroovyShell().parse '''
                 class Name {}
 
                 @groovy.transform.ExternalizeVerifier(checkPropertyTypes=true)
@@ -518,7 +518,7 @@ class CanonicalComponentsTransformTest extends 
GroovyShellTestCase {
                     void writeExternal(ObjectOutput out)throws IOException{ }
                     void readExternal(ObjectInput objectInput)throws 
IOException,ClassNotFoundException{ }
                 }
-            """
+            '''
             fail("The compilation should have failed as the type of Name isn't 
Externalizable or Serializable.")
         } catch (MultipleCompilationErrorsException e) {
             def syntaxError = e.errorCollector.getSyntaxError(0)

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/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 f867c5b..4d47351 100644
--- a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
@@ -607,8 +607,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
                 new Person(first: 'John', last: 'Doe', address: new 
Address(street: 'Street'))
             '''
         }
-        assert msg.contains("doesn't know how to handle field 'address' of 
type 'Address'")
-        assert msg.contains("@Immutable classes only support properties with 
effectively immutable types")
+        assert msg.contains("Unsupported type (Address) found for field 
'address' while constructing immutable class Person")
+        assert msg.contains("Immutable classes only support properties with 
effectively immutable types")
     }
 
     // GROOVY-5828
@@ -655,7 +655,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
             }
             '''
         }
-        assert msg.contains("@Immutable processor doesn't know how to handle 
field 'name' of type 'java.lang.Object or def'")
+        assert msg.contains("Unsupported type (java.lang.Object or def) found 
for field 'name' while ")
     }
 
     // GROOVY-6192
@@ -986,7 +986,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
                new Person(surName: "Doe")
            """
         }
-        assert message.contains("Error during @Immutable processing: 
'knownImmutables' property 'sirName' does not exist.")
+        assert message.contains("Error during immutable class processing: 
'knownImmutables' property 'sirName' does not exist.")
     }
 
     // GROOVY-7162

http://git-wip-us.apache.org/repos/asf/groovy/blob/ae51fc62/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy
----------------------------------------------------------------------
diff --git 
a/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy
 
b/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy
index 66bf434..456f8ab 100644
--- 
a/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy
+++ 
b/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy
@@ -40,8 +40,6 @@ import java.lang.annotation.ElementType
  * Tests whether local and global transforms are successfully detected, loaded,
  * and run if separate class loaders are used for loading compile dependencies
  * and AST transforms.
- *
- * @author Peter Niederwieser
  */
 class TransformsAndCustomClassLoadersTest extends GroovyTestCase {
     URL[] urls = collectUrls(getClass().classLoader) + addGroovyUrls()
@@ -68,8 +66,8 @@ class TransformsAndCustomClassLoadersTest extends 
GroovyTestCase {
     }
 
     void testBuiltInLocalTransform() {
-        def clazz = compileAndLoadClass("@groovy.transform.Immutable class Foo 
{ String bar }", dependencyLoader, transformLoader)
-        checkIsImmutable(clazz)
+        def clazz = compileAndLoadClass("@groovy.transform.TupleConstructor 
class Foo { String bar }", dependencyLoader, transformLoader)
+        checkHasTupleConstructor(clazz)
     }
 
     void testThirdPartyLocalTransform() {
@@ -102,12 +100,9 @@ class TransformsAndCustomClassLoadersTest extends 
GroovyTestCase {
         return loader.defineClass(classInfo.name, classInfo.bytes)
     }
 
-    private checkIsImmutable(Class clazz) {
-        try {
-            def foo = clazz.newInstance(["setting property"] as Object[])
-            foo.bar = "updating property"
-            fail()
-        } catch (ReadOnlyPropertyException expected) {}
+    private checkHasTupleConstructor(Class clazz) {
+        def foo = clazz.newInstance(["some property"] as Object[])
+        assert foo.bar == 'some property'
     }
 
     private Set<URL> collectUrls(ClassLoader classLoader) {

Reply via email to