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 2019/02/24 22:14:56 UTC
[groovy] 04/04: GROOVY-9009: AutoClone throws ClassCastException
when used with @ToString or @EqualsAndHashCode (closes #883)
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch GROOVY_2_5_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 66dec5d619c15b9c148335dfa5e3b09174166cb1
Author: Paul King <pa...@asert.com.au>
AuthorDate: Sun Feb 24 21:25:49 2019 +1000
GROOVY-9009: AutoClone throws ClassCastException when used with @ToString or @EqualsAndHashCode (closes #883)
---
.../transform/AutoCloneASTTransformation.java | 15 +-
.../CanonicalComponentsTransformTest.groovy | 2006 ++++++++++----------
2 files changed, 1022 insertions(+), 999 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/transform/AutoCloneASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/AutoCloneASTTransformation.java
index 953b32c..fd967dc 100644
--- a/src/main/java/org/codehaus/groovy/transform/AutoCloneASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/AutoCloneASTTransformation.java
@@ -72,6 +72,7 @@ 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.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
@@ -134,7 +135,7 @@ public class AutoCloneASTTransformation extends AbstractASTTransformation {
private void createCloneSerialization(ClassNode cNode) {
final BlockStatement body = new BlockStatement();
// def baos = new ByteArrayOutputStream()
- final Expression baos = varX("baos");
+ final Expression baos = localVarX("baos");
body.addStatement(declS(baos, ctorX(BAOS_TYPE)));
// baos.withObjectOutputStream{ it.writeObject(this) }
@@ -145,7 +146,7 @@ public class AutoCloneASTTransformation extends AbstractASTTransformation {
body.addStatement(stmt(callX(baos, "withObjectOutputStream", args(writeClos))));
// def bais = new ByteArrayInputStream(baos.toByteArray())
- final Expression bais = varX("bais");
+ final Expression bais = localVarX("bais");
body.addStatement(declS(bais, ctorX(BAIS_TYPE, args(callX(baos, "toByteArray")))));
// return bais.withObjectInputStream(getClass().classLoader){ (<type>) it.readObject() }
@@ -228,7 +229,7 @@ public class AutoCloneASTTransformation extends AbstractASTTransformation {
addGeneratedConstructor(cNode, ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, block(EmptyStatement.INSTANCE));
}
addSimpleCloneHelperMethod(cNode, fieldNodes, excludes);
- final Expression result = varX("_result", cNode);
+ final Expression result = localVarX("_result");
ClassNode[] exceptions = {make(CloneNotSupportedException.class)};
addGeneratedMethod(cNode, "clone", ACC_PUBLIC, GenericsUtils.nonGeneric(cNode), Parameter.EMPTY_ARRAY, exceptions, block(
declS(result, ctorX(cNode)),
@@ -267,8 +268,11 @@ public class AutoCloneASTTransformation extends AbstractASTTransformation {
private static void createClone(ClassNode cNode, List<FieldNode> fieldNodes, List<String> excludes) {
final BlockStatement body = new BlockStatement();
- final Expression result = varX("_result", cNode);
+
+ // def _result = super.clone() as cNode
+ final Expression result = localVarX("_result");
body.addStatement(declS(result, castX(cNode, callSuperX("clone"))));
+
for (FieldNode fieldNode : fieldNodes) {
if (excludes != null && excludes.contains(fieldNode.getName())) continue;
ClassNode fieldType = fieldNode.getType();
@@ -282,7 +286,10 @@ public class AutoCloneASTTransformation extends AbstractASTTransformation {
body.addStatement(ifS(isInstanceOfX(fieldExpr, CLONEABLE_TYPE), doCloneDynamic));
}
}
+
+ // return _result
body.addStatement(returnS(result));
+
ClassNode[] exceptions = {make(CloneNotSupportedException.class)};
addGeneratedMethod(cNode, "clone", ACC_PUBLIC, GenericsUtils.nonGeneric(cNode), Parameter.EMPTY_ARRAY, exceptions, body);
}
diff --git a/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy b/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
index c951435..2b839c9 100644
--- a/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/CanonicalComponentsTransformTest.groovy
@@ -1,995 +1,1011 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.codehaus.groovy.transform
-
-import groovy.transform.AutoClone
-import groovy.transform.AutoExternalize
-import groovy.transform.CompileStatic
-import groovy.transform.EqualsAndHashCode
-import groovy.transform.TupleConstructor
-import org.codehaus.groovy.control.MultipleCompilationErrorsException
-
-import static groovy.transform.AutoCloneStyle.*
-import groovy.transform.ToString
-import groovy.transform.Canonical
-
-class CanonicalComponentsTransformTest extends GroovyShellTestCase {
-
- void testTupleConstructorWithEnum() {
- assertScript """
- @groovy.transform.TupleConstructor
- enum Operator {
- PLUS('+'), MINUS('-')
- String symbol
- }
- assert Operator.PLUS.next() == Operator.MINUS
- """
- }
-
- void testHashCodeNullWrapperTypeCompileStatic_GROOVY7518() {
- assertScript """
- import groovy.transform.*
-
- @EqualsAndHashCode
- @CompileStatic
- class Person {
- Character someCharacter
- Integer someInteger
- Long someLong
- Float someFloat
- Double someDouble
- }
-
- assert new Person().hashCode()
- """
- }
-
- void testBooleanPropertyGROOVY6407() {
- assertScript """
- @groovy.transform.EqualsAndHashCode
- @groovy.transform.ToString
- class Demo {
- boolean myBooleanProperty
-
- boolean isMyBooleanProperty() {
- false
- }
-
- static main(args) {
- assert new Demo().hashCode() == 7590
- assert new Demo(myBooleanProperty: true).toString() == 'Demo(false)'
- }
- }
- """
- }
-
- void testCloningWithFinalFields() {
- def p1 = new Person1('John', 'Smith')
- def p2 = p1.clone()
- def c1 = new Customer1('John', 'Smith', ['ipod', 'shiraz'], new Date())
- def c2 = c1.clone()
-
- assert [p1, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(p1.first instanceof Cloneable)
- assert p1 == p2
- assert !p1.is(p2)
- assert !c1.is(c2)
- assert c1 == c2
- assert !c1.favItems.is(c2.favItems)
- assert !c1.since.is(c2.since)
- assert p1.first.is(p2.first)
- assert c1.first.is(c2.first)
- }
-
- void testCloningWithFinalFieldsCompileStatic() {
- def p1 = new Person1CS('John', 'Smith')
- def p2 = p1.clone()
- def c1 = new Customer1CS('John', 'Smith', ['ipod', 'shiraz'], new Date())
- def c2 = c1.clone()
-
- assert [p1, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(p1.first instanceof Cloneable)
- assert p1 == p2
- assert !p1.is(p2)
- assert !c1.is(c2)
- assert c1 == c2
- assert !c1.favItems.is(c2.favItems)
- assert !c1.since.is(c2.since)
- assert p1.first.is(p2.first)
- assert c1.first.is(c2.first)
- }
-
- void testCloningWithNonFinalFields() {
- def p1 = new Person2(first:'John', last:'Smith')
- def p2 = p1.clone()
- def c1 = new Customer2(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
- def c2 = c1.clone()
-
- assert [p1, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(p1.first instanceof Cloneable)
- assert !p1.is(p2)
- assert !c1.is(c2)
- assert !c1.favItems.is(c2.favItems)
- assert !c1.since.is(c2.since)
- assert p1.first.is(p2.first)
- assert c1.first.is(c2.first)
- }
-
- void testCloningWithNonFinalFieldsCompileStatic() {
- def p1 = new Person2CS(first:'John', last:'Smith')
- def p2 = p1.clone()
- def c1 = new Customer2CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
- def c2 = c1.clone()
-
- assert [p1, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(p1.first instanceof Cloneable)
- assert !p1.is(p2)
- assert !c1.is(c2)
- assert !c1.favItems.is(c2.favItems)
- assert !c1.since.is(c2.since)
- assert p1.first.is(p2.first)
- assert c1.first.is(c2.first)
- }
-
- void testCloningWithSerializable() {
- def c1 = new Customer3(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date(), bonus:10)
- def c2 = c1.clone()
-
- assert [c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(c1.first instanceof Cloneable)
- assert c1 == c2
- assert !c1.is(c2)
- assert !c1.favItems.is(c2.favItems)
- assert !c1.since.is(c2.since)
- // serialization gives us a new string even here
- assert !c1.first.is(c2.first)
- assert c1.bonus == c2.bonus
- }
-
- void testCloningWithSerializableCompileStatic() {
- def c1 = new Customer3CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date(), bonus:10)
- def c2 = c1.clone()
-
- assert [c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(c1.first instanceof Cloneable)
- assert c1 == c2
- assert !c1.is(c2)
- assert !c1.favItems.is(c2.favItems)
- assert !c1.since.is(c2.since)
- // serialization gives us a new string even here
- assert !c1.first.is(c2.first)
- assert c1.bonus == c2.bonus
- }
-
- void testCloningWithExternalizable() {
- def c1 = new Customer4(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date(), bonus:10)
- def c2 = c1.clone()
-
- assert c1 instanceof Externalizable
- assert [c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(c1.first instanceof Cloneable)
- assert c1 == c2
- assert !c1.is(c2)
- assert !c1.favItems.is(c2.favItems)
- assert !c1.since.is(c2.since)
- // serialization gives us a new string even here
- assert !c1.first.is(c2.first)
- assert c1.bonus == c2.bonus
- }
-
- void testCloningWithExternalizableCompileStatic() {
- def c1 = new Customer4CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date(), bonus:10)
- def c2 = c1.clone()
-
- assert c1 instanceof Externalizable
- assert [c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(c1.first instanceof Cloneable)
- assert c1 == c2
- assert !c1.is(c2)
- assert !c1.favItems.is(c2.favItems)
- assert !c1.since.is(c2.since)
- // serialization gives us a new string even here
- assert !c1.first.is(c2.first)
- assert c1.bonus == c2.bonus
- }
-
- void testCloningWithDefaultObjectClone() {
- def c1 = new Customer5(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], age:25)
- def c2 = c1.clone()
- assert c1.first == c2.first
- assert c1.last == c2.last
- assert c1.favItems == c2.favItems
- // no need to use includeFields=true here because of Object clone!
- assert c1.agePeek() == c2.agePeek()
- }
-
- void testCloningWithDefaultObjectCloneCompileStatic() {
- def c1 = new Customer5CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], age:25)
- def c2 = c1.clone()
- assert c1.first == c2.first
- assert c1.last == c2.last
- assert c1.favItems == c2.favItems
- // no need to use includeFields=true here because of Object clone!
- assert c1.agePeek() == c2.agePeek()
- }
-
- // GROOVY-4786
- void testExcludesWithEqualsAndHashCode() {
- def p1 = new PointIgnoreY(x:1, y:10)
- def p2 = new PointIgnoreY(x:1, y:100)
- assert p1 == p2
- assert p1.hashCode() == p2.hashCode()
- }
-
- // GROOVY-4894
- void testIncludesWithToString() {
- def p1 = new PointIgnoreY(x:1, y:5)
- def p2 = new PointIgnoreY(x:10, y:50)
- assert p1.toString() == "org.codehaus.groovy.transform.PointIgnoreY(1)"
- assert p2.toString() == "org.codehaus.groovy.transform.PointIgnoreY(10)"
- }
-
- // GROOVY-4894
- void testNestedExcludes() {
- def (s1, s2) = new GroovyShell().evaluate("""
- import groovy.transform.*
- @Canonical(excludes='foo, baz')
- @ToString(includeNames=true)
- class Hello {
- String foo = 'A', bar = 'B', baz = 'C'
- }
- @Canonical(excludes='foo, baz')
- @ToString(excludes='foo', includeNames=true)
- class Goodbye {
- String foo = 'A', bar = 'B', baz = 'C'
- }
- [new Hello().toString(), new Goodbye().toString()]
- """)
- assert s1 == 'Hello(bar:B)'
- assert s2 == 'Goodbye(bar:B, baz:C)'
- }
-
- // GROOVY-4844
- void testToStringCustomGetter() {
- def p1 = new Point(1, 2)
- def p2 = new Point(1, 1) { int getY() { 2 } }
- assert p1.toString() == 'org.codehaus.groovy.transform.Point(1, 2)'
- assert p2.toString() == 'org.codehaus.groovy.transform.Point(1, 2)'
- }
-
- // GROOVY-4849
- void testEqualsOnEquivalentClasses() {
- def (p1, p2, p3) = new GroovyShell().evaluate("""
- import groovy.transform.*
- @Canonical class IntPair {
- int x, y
- }
-
- @InheritConstructors
- class IntPairWithSum extends IntPair {
- def sum() { x + y }
- }
-
- [new IntPair(1, 2), new IntPair(1, 1) { int getY() { 2 } }, new IntPairWithSum(x:1, y:2)]
- """)
-
- assert p1 == p2 && p2 == p1
- assert p1 == p3 && p3 == p1
- assert p3 == p2 && p2 == p3
- }
-
- // GROOVY-4849
- void testEqualsOnDifferentClasses() {
- def (p1, p2, p3, t1) = new GroovyShell().evaluate("""
- import groovy.transform.*
- @Canonical class IntPair {
- int x, y
- boolean hasEqualXY(other) { other.x == getX() && other.y == getY() }
- }
-
- @InheritConstructors
- class IntPairWithSum extends IntPair {
- def sum() { x + y }
- }
-
- @EqualsAndHashCode
- @TupleConstructor(includeSuperProperties=true)
- class IntTriple extends IntPair { int z }
-
- [new IntPair(1, 2), new IntPair(1, 1) { int getY() { 2 } }, new IntPairWithSum(x:1, y:2), new IntTriple(1, 2, 3)]
- """)
-
- assert p1 != t1 && p2 != t1 && t1 != p3
- assert p1.hasEqualXY(t1) && t1.hasEqualXY(p1)
- assert p2.hasEqualXY(t1) && t1.hasEqualXY(p2)
- assert p3.hasEqualXY(t1) && t1.hasEqualXY(p3)
- }
-
- // GROOVY-4714
- void testCachingOfHashCode() {
- def (h1, h2, h3, h4) = new GroovyShell().evaluate("""
- import groovy.transform.*
- // DO NOT DO THIS AT HOME - cache should only be true for Immutable
- // objects but we cheat here for testing purposes
- @EqualsAndHashCode(cache = true) class ShouldBeImmutableIntPair {
- /* final */ int x, y
- }
- // the control (hashCode should change when x or y changes)
- @EqualsAndHashCode class MutableIntPair {
- int x, y
- }
- def sbmip = new ShouldBeImmutableIntPair(x: 3, y: 4)
- def h1 = sbmip.hashCode()
- sbmip.x = 5
- def h2 = sbmip.hashCode()
- def mip = new MutableIntPair(x: 3, y: 4)
- def h3 = mip.hashCode()
- mip.x = 5
- def h4 = mip.hashCode()
- [h1, h2, h3, h4]
- """)
-
- assert h1 == h2 // since it is cached
- assert h3 != h4 // no caching
- }
-
- // GROOVY-5928
- void testCachingOfToString() {
- def (ts1, ts2, ts3, ts4) = new GroovyShell().evaluate("""
- import groovy.transform.*
- // DO NOT DO THIS AT HOME - cache should only be true for Immutable
- // objects but we cheat here for testing purposes
- @ToString(cache = true) class ShouldBeImmutableIntPair {
- /* final */ int x, y
- }
- // the control (toString should change when x or y changes)
- @ToString class MutableIntPair {
- int x, y
- }
- def sbmip = new ShouldBeImmutableIntPair(x: 3, y: 4)
- def ts1 = sbmip.toString()
- sbmip.x = 5
- def ts2 = sbmip.toString()
- def mip = new MutableIntPair(x: 3, y: 4)
- def ts3 = mip.toString()
- mip.x = 5
- def ts4 = mip.toString()
- [ts1, ts2, ts3, ts4]
- """)
-
- assert ts1 == ts2 // since it is cached
- assert ts3 != ts4 // no caching
- }
-
- // GROOVY-6337
- void testCanonicalWithLazy() {
- def result = new GroovyShell().evaluate('''
- @groovy.transform.Canonical
- class Person {
- @Lazy first = missing()
- @Lazy last = 'Smith'
- int age
- }
- def p = new Person(21)
- // $first setter is an implementation detail
- p.$first = 'Mary'
- p.toString()
- ''')
- assert result == 'Person(21, Mary, Smith)'
- }
-
- // GROOVY-5901
- void testSimpleCloning() {
- def p1 = new Person6(first:'John', last:'Smith', since:new Date())
- def p2 = p1.clone()
- def c1 = new Customer6(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
- def c2 = c1.clone()
-
- assert [p1, p1.since, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(p1.first instanceof Cloneable)
- assert !p1.is(p2)
- assert !c1.is(c2)
- assert !c1.favItems.is(c2.favItems)
- assert !p1.since.is(p2.since)
- assert !c1.since.is(c2.since)
- assert p1.first.is(p2.first)
- assert c1.first.is(c2.first)
- }
-
- void testSimpleCloningCompileStatic() {
- def p1 = new Person6CS(first:'John', last:'Smith', since:new Date())
- def p2 = p1.clone()
- def c1 = new Customer6CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
- def c2 = c1.clone()
-
- assert [p1, p1.since, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
- assert !(p1.first instanceof Cloneable)
- assert !p1.is(p2)
- assert !c1.is(c2)
- assert !c1.favItems.is(c2.favItems)
- assert !p1.since.is(p2.since)
- assert !c1.since.is(c2.since)
- assert p1.first.is(p2.first)
- assert c1.first.is(c2.first)
- }
-
- // GROOVY-4849
- void testCanEqualDefined() {
- def p1 = new IntPair(1, 2)
- def p2 = new IntPairNoCanEqual(x:1, y:2)
- assert p1 != p2
- assert p1.hashCode() == p2.hashCode()
- assert 'canEqual' in p1.class.methods*.name
- assert !('canEqual' in p2.class.methods*.name)
- }
-
- // GROOVY-5864
- void testExternalizeMethodsWithImmutable() {
- try {
- 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)
- assert syntaxError.message.contains("cannot modify final field 'first' outside of constructor")
- }
- }
-
- // GROOVY-5864
- void testExternalizeVerifierWithNonExternalizableClass() {
- try {
- 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)
- assert syntaxError.message.contains("An Externalizable class must implement the Externalizable interface")
- }
- }
-
- // GROOVY-5864
- void testExternalizeVerifierWithFinalField() {
- try {
- 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)
- assert syntaxError.message.contains("The Externalizable property (or field) 'first' cannot be final")
- }
- }
-
- // GROOVY-5864
- void testAutoExternalizeWithoutNoArg() {
- try {
- 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)
- assert syntaxError.message.contains("An Externalizable class requires a no-arg constructor but none found")
- }
- }
-
- // GROOVY-5864
- void testExternalizeVerifierWithNonExternalizableField() {
- try {
- new GroovyShell().parse '''
- class Name {}
-
- @groovy.transform.ExternalizeVerifier(checkPropertyTypes=true)
- class Person implements Externalizable {
- Name name
- int age
- 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)
- assert syntaxError.message.contains("strict type checking is enabled and the non-primitive property (or field) 'name' in an Externalizable class has the type 'Name' which isn't Externalizable or Serializable")
- }
- }
-
- // GROOVY-5864
- void testAutoExternalizeHappyPath() {
- new GroovyShell().evaluate """
- import org.codehaus.groovy.transform.*
- def orig = new Person7(name: new Name7('John', 'Smith'), address: new Address7(street: 'somewhere lane', town: 'my town'), age: 21, verified: true)
- def baos = new ByteArrayOutputStream()
- baos.withObjectOutputStream{ os -> os.writeObject(orig) }
- def bais = new ByteArrayInputStream(baos.toByteArray())
- bais.withObjectInputStream { is -> assert is.readObject().toString() == 'Person7(Name7(John, Smith), Address7(somewhere lane, my town), 21, true)' }
- """
- }
-
- void testAutoExternalizeHappyPathCompileStatic() {
- new GroovyShell().evaluate """
- import org.codehaus.groovy.transform.*
- def orig = new Person7CS(name: new Name7CS('John', 'Smith'), address: new Address7CS(street: 'somewhere lane', town: 'my town'), age: 21, verified: true)
- def baos = new ByteArrayOutputStream()
- baos.withObjectOutputStream{ os -> os.writeObject(orig) }
- def bais = new ByteArrayInputStream(baos.toByteArray())
- bais.withObjectInputStream { is -> assert is.readObject().toString() == 'Person7CS(Name7CS(John, Smith), Address7CS(somewhere lane, my town), 21, true)' }
- """
- }
-
- void testAutoExternalizeNestedClassCompileStatic_GROOVY7644() {
- new GroovyShell().evaluate '''
- import org.codehaus.groovy.transform.*
-
- def orig = new Person7NestedAddressCS.Address7CS(street: 'somewhere lane', town: 'my town')
- def baos = new ByteArrayOutputStream()
- baos.withObjectOutputStream{ os -> os.writeObject(orig) }
- def bais = new ByteArrayInputStream(baos.toByteArray())
- bais.withObjectInputStream { is -> assert is.readObject().toString() == 'Person7NestedAddressCS$Address7CS(somewhere lane, my town)' }
- '''
- }
-
- // GROOVY-4570
- void testToStringForEnums() {
- assert Color.PURPLE.toString() == 'org.codehaus.groovy.transform.Color(r:255, g:0, b:255)'
- }
-
- void testCustomCopyConstructor_GROOVY7016() {
- new GroovyShell().evaluate """
- import org.codehaus.groovy.transform.Shopper
- def p1 = new Shopper('John', [['bread', 'milk'], ['bacon', 'eggs']])
- def p2 = p1.clone()
- p2.shoppingHistory[0][1] = 'jam'
- assert p1.shoppingHistory[0] == ['bread', 'milk']
- assert p2.shoppingHistory[0] == ['bread', 'jam']
- """
- }
-
- void testTupleConstructorNoDefaultParameterValues_GROOVY7427() {
- new GroovyShell().evaluate """
- // checks special Map behavior isn't added if defaults=false
- import groovy.transform.*
-
- @ToString
- @TupleConstructor(defaults=false)
- class Person {
- def name
- }
-
- assert new Person('John Smith').toString() == 'Person(John Smith)'
- assert Person.constructors.size() == 1
- """
- }
-
- void testNullCloneableField_GROOVY7091() {
- new GroovyShell().evaluate """
- import groovy.transform.AutoClone
- @AutoClone
- class B {
- String name='B'
- }
-
- @AutoClone
- class A {
- B b
- C c
- ArrayList x
- List y
- String name='A'
- }
-
- @AutoClone
- class C {
- String name='C'
- }
-
- def b = new B().clone()
- assert b
- assert new A(b:b).clone()
- assert new A().clone()
- """
- }
-
- void testTupleConstructorUsesSetters_GROOVY7087() {
- new GroovyShell().evaluate """
- import groovy.transform.*
-
- @ToString @TupleConstructor(useSetters=true)
- class Foo1 {
- String bar, baz
- void setBar(String bar) {
- this.bar = bar?.toUpperCase()
- }
- }
-
- assert new Foo1('cat', 'dog').toString() == 'Foo1(CAT, dog)'
- // check the default map-style constructor too
- assert new Foo1(bar: 'cat', baz: 'dog').toString() == 'Foo1(CAT, dog)'
- """
- }
-
- void testincludeSuperFieldsAndroperties_GROOVY8013() {
- new GroovyShell().evaluate """
- import groovy.transform.*
-
- @ToString
- class Foo {
- String baz = 'baz'
- protected String baz2
- }
-
- @TupleConstructor(includes='a,b,baz2', includeSuperFields=true)
- @ToString(includes='a,c,super,baz,d', includeFields=true, includeSuperProperties=true, includeSuper=true)
- class Bar extends Foo {
- int a = 1
- int b = 2
- private int c = 3
- public int d = 4
- }
-
- assert new Bar().toString() == 'Bar(1, 3, Foo(baz), baz, 4)'
- """
- }
-
- void testOrderTupleParamsUsingIncludes_GROOVY8016() {
- new GroovyShell().evaluate """
- import groovy.transform.*
-
- @ToString
- class Foo {
- String a
- String c
- }
- @TupleConstructor(includes='d,c,b,a', includeSuperProperties=true, includeFields=true)
- @ToString(includes='super,b,d', includeFields=true, includeSuperProperties=true, includeSuper=true)
- class Bar extends Foo {
- String d
- private String b
- }
-
- assert new Bar('1', '2', '3', '4').toString() == 'Bar(Foo(4, 2), 3, 1)'
- """
- }
-
- void testTupleConstructorWithForceDirectBypassesSetters_GROOVY7087() {
- new GroovyShell().evaluate """
- import groovy.transform.*
-
- @ToString @TupleConstructor
- class Foo2 {
- String bar, baz
- void setBar(String bar) {
- this.bar = bar.toUpperCase()
- }
- }
-
- assert new Foo2(bar: 'cat', baz: 'dog').toString() == 'Foo2(CAT, dog)'
- assert new Foo2('cat', 'dog').toString() == 'Foo2(cat, dog)'
- """
- }
-
- void testEqualsHashCodeToStringConsistencyWithExplicitBooleanGetters_GROOVY7417() {
- new GroovyShell().evaluate """
- import groovy.transform.*
-
- @ToString
- @EqualsAndHashCode
- class A {
- boolean x
- }
-
- def a1 = new A(x: true)
- def a2 = new A(x: true)
- def a3 = new A(x: false)
- assert a1.toString() == a2.toString()
- assert a1.hashCode() == a2.hashCode()
- assert a1 == a2
- assert a1.toString() != a3.toString()
- assert a1.hashCode() != a3.hashCode()
- assert a1 != a3
-
- @ToString
- @EqualsAndHashCode
- class B {
- boolean x
- boolean isX() { false }
- }
-
- def b1 = new B(x: true)
- def b2 = new B(x: false)
- assert b1.toString() == b2.toString()
- assert b1.hashCode() == b2.hashCode()
- assert b1 == b2
-
- @ToString
- @EqualsAndHashCode
- class C {
- boolean x
- boolean getX() { false }
- }
-
- def c1 = new C(x: true)
- def c2 = new C(x: false)
- assert c1.toString() == c2.toString()
- assert c1.hashCode() == c2.hashCode()
- assert c1 == c2
-
- @ToString
- @EqualsAndHashCode
- class D {
- boolean x
- boolean isX() { false }
- boolean getX() { false }
- }
-
- def d1 = new D(x: true)
- def d2 = new D(x: false)
- assert d1.toString() == d2.toString()
- assert d1.hashCode() == d2.hashCode()
- assert d1 == d2
- """
- }
-
- void testHashCodeForInstanceWithNullPropertyAndField() {
- new GroovyShell().evaluate """
- import groovy.transform.*
- @EqualsAndHashCode(includeFields = true)
- class FieldAndPropertyIncludedInHashCode {
- private String field
- String property
- }
- assert new FieldAndPropertyIncludedInHashCode().hashCode() == 442087
- """
- }
-
- void testHashCodeForInstanceWithNullPropertyAndJavaBeanProperty() {
- new GroovyShell().evaluate '''
- import groovy.transform.*
- @EqualsAndHashCode(allProperties = true)
- class FieldAndPropertyIncludedInHashCode {
- String property
- String getField() { null }
- }
- assert new FieldAndPropertyIncludedInHashCode().hashCode() == 442087
- '''
- }
-}
-
-@TupleConstructor
-@AutoClone(style=COPY_CONSTRUCTOR)
-@EqualsAndHashCode
-class Person1 { final String first, last }
-
-@TupleConstructor(includeSuperProperties=true, callSuper=true)
-@AutoClone(style=COPY_CONSTRUCTOR)
-@EqualsAndHashCode
-class Customer1 extends Person1 { final List favItems; final Date since }
-
-@TupleConstructor
-@AutoClone(style=COPY_CONSTRUCTOR)
-@EqualsAndHashCode
-@CompileStatic
-class Person1CS { final String first, last }
-
-@CompileStatic
-@TupleConstructor(includeSuperProperties=true, callSuper=true)
-@AutoClone(style=COPY_CONSTRUCTOR)
-@EqualsAndHashCode
-class Customer1CS extends Person1CS { final List favItems; final Date since }
-
-@AutoClone(style=COPY_CONSTRUCTOR)
-class Person2 { String first, last }
-
-@AutoClone(style=COPY_CONSTRUCTOR)
-class Customer2 extends Person2 { List favItems = []; Date since }
-
-@AutoClone(style=COPY_CONSTRUCTOR)
-@CompileStatic
-class Person2CS { String first, last }
-
-@CompileStatic
-@AutoClone(style=COPY_CONSTRUCTOR)
-class Customer2CS extends Person2CS { List favItems = []; Date since }
-
-@AutoClone(style=SERIALIZATION)
-@EqualsAndHashCode
-class Customer3 implements Serializable {
- String first, last
- List favItems
- Date since
- int bonus
-}
-
-@AutoClone(style=SERIALIZATION)
-@CompileStatic
-@EqualsAndHashCode
-class Customer3CS implements Serializable {
- String first, last
- List favItems
- Date since
- int bonus
-}
-
-@AutoExternalize
-@AutoClone(style=SERIALIZATION)
-@EqualsAndHashCode
-class Customer4 {
- String first, last
- List favItems
- Date since
- int bonus
-}
-
-@CompileStatic
-@AutoExternalize
-@AutoClone(style=SERIALIZATION)
-@EqualsAndHashCode
-class Customer4CS {
- String first, last
- List favItems
- Date since
- int bonus
-}
-
-@AutoClone
-class Customer5 {
- String first, last
- List favItems
- private int age
- int agePeek() { age }
-}
-
-@CompileStatic
-@AutoClone
-class Customer5CS {
- String first, last
- List favItems
- private int age
- int agePeek() { age }
-}
-
-@TupleConstructor
-@AutoClone(style=SIMPLE)
-@EqualsAndHashCode
-class Person6 { String first, last; Date since }
-
-@TupleConstructor(includeSuperProperties=true, callSuper=true)
-@AutoClone(style=SIMPLE)
-@EqualsAndHashCode
-class Customer6 extends Person6 { List<String> favItems }
-
-@TupleConstructor
-@AutoClone(style=SIMPLE)
-@EqualsAndHashCode
-@CompileStatic
-class Person6CS { String first, last; Date since }
-
-@CompileStatic
-@TupleConstructor(includeSuperProperties=true, callSuper=true)
-@AutoClone(style=SIMPLE)
-@EqualsAndHashCode
-class Customer6CS extends Person6CS { List<String> favItems }
-
-// GROOVY-5864
-@Canonical
-@ToString(includePackage=false)
-class Name7 implements Serializable { String first, last }
-
-// GROOVY-5864
-@AutoExternalize
-@ToString(includePackage=false)
-class Address7 { String street, town }
-
-// GROOVY-5864
-@ToString(includePackage=false)
-@AutoExternalize(checkPropertyTypes=true)
-class Person7 {
- Name7 name
- Address7 address
- int age
- Boolean verified
-}
-
-@ToString(includePackage=false)
-@AutoExternalize(checkPropertyTypes=true)
-@CompileStatic
-class Person7CS {
- Name7CS name
- Address7CS address
- int age
- Boolean verified
-}
-
-@Canonical
-@CompileStatic
-@ToString(includePackage=false)
-class Name7CS implements Serializable { String first, last }
-
-@AutoExternalize
-@ToString(includePackage=false)
-@CompileStatic
-class Address7CS { String street, town }
-
-// GROOVY-7644
-class Person7NestedAddressCS {
- @ToString(includePackage=false)
- @AutoExternalize(checkPropertyTypes=true)
- @CompileStatic
- static class Address7CS {
- String street, town
- }
-}
-
-// GROOVY-4786
-@EqualsAndHashCode(excludes="y")
-@ToString(includes="x")
-class PointIgnoreY {
- int x
- int y // y coordinate excluded from Equals and hashCode
-}
-
-// GROOVY-4844
-@TupleConstructor @ToString
-class Point { int x, y }
-
-// GROOVY-4849
-@Canonical class IntPair {
- int x, y
-}
-
-// GROOVY-4849
-@EqualsAndHashCode(useCanEqual=false)
-class IntPairNoCanEqual {
- int x, y
-}
-
-// GROOVY-4570
-@ToString(includeNames=true)
-enum Color {
- BLACK(0,0,0), WHITE(255,255,255), PURPLE(255,0,255)
- int r, g, b
- Color(int r, g, b) { this.r = r; this.g = g; this.b = b }
-}
-
-@TupleConstructor(force=true) @AutoClone(style=COPY_CONSTRUCTOR)
-class Shopper {
- final String name
- final List<List<String>> shoppingHistory
- Shopper(Shopper other) {
- name = other.name
- // requires deep clone
- shoppingHistory = other.shoppingHistory*.clone()
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.transform
+
+import groovy.transform.AutoClone
+import groovy.transform.AutoExternalize
+import groovy.transform.CompileStatic
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.TupleConstructor
+import org.codehaus.groovy.control.MultipleCompilationErrorsException
+
+import static groovy.transform.AutoCloneStyle.*
+import groovy.transform.ToString
+import groovy.transform.Canonical
+
+class CanonicalComponentsTransformTest extends GroovyShellTestCase {
+
+ void testTupleConstructorWithEnum() {
+ assertScript """
+ @groovy.transform.TupleConstructor
+ enum Operator {
+ PLUS('+'), MINUS('-')
+ String symbol
+ }
+ assert Operator.PLUS.next() == Operator.MINUS
+ """
+ }
+
+ void testHashCodeNullWrapperTypeCompileStatic_GROOVY7518() {
+ assertScript """
+ import groovy.transform.*
+
+ @EqualsAndHashCode
+ @CompileStatic
+ class Person {
+ Character someCharacter
+ Integer someInteger
+ Long someLong
+ Float someFloat
+ Double someDouble
+ }
+
+ assert new Person().hashCode()
+ """
+ }
+
+ void testBooleanPropertyGROOVY6407() {
+ assertScript """
+ @groovy.transform.EqualsAndHashCode
+ @groovy.transform.ToString
+ class Demo {
+ boolean myBooleanProperty
+
+ boolean isMyBooleanProperty() {
+ false
+ }
+
+ static main(args) {
+ assert new Demo().hashCode() == 7590
+ assert new Demo(myBooleanProperty: true).toString() == 'Demo(false)'
+ }
+ }
+ """
+ }
+
+ void testCloningWithFinalFields() {
+ def p1 = new Person1('John', 'Smith')
+ def p2 = p1.clone()
+ def c1 = new Customer1('John', 'Smith', ['ipod', 'shiraz'], new Date())
+ def c2 = c1.clone()
+
+ assert [p1, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(p1.first instanceof Cloneable)
+ assert p1 == p2
+ assert !p1.is(p2)
+ assert !c1.is(c2)
+ assert c1 == c2
+ assert !c1.favItems.is(c2.favItems)
+ assert !c1.since.is(c2.since)
+ assert p1.first.is(p2.first)
+ assert c1.first.is(c2.first)
+ }
+
+ void testCloningWithFinalFieldsCompileStatic() {
+ def p1 = new Person1CS('John', 'Smith')
+ def p2 = p1.clone()
+ def c1 = new Customer1CS('John', 'Smith', ['ipod', 'shiraz'], new Date())
+ def c2 = c1.clone()
+
+ assert [p1, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(p1.first instanceof Cloneable)
+ assert p1 == p2
+ assert !p1.is(p2)
+ assert !c1.is(c2)
+ assert c1 == c2
+ assert !c1.favItems.is(c2.favItems)
+ assert !c1.since.is(c2.since)
+ assert p1.first.is(p2.first)
+ assert c1.first.is(c2.first)
+ }
+
+ void testCloningWithNonFinalFields() {
+ def p1 = new Person2(first:'John', last:'Smith')
+ def p2 = p1.clone()
+ def c1 = new Customer2(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
+ def c2 = c1.clone()
+
+ assert [p1, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(p1.first instanceof Cloneable)
+ assert !p1.is(p2)
+ assert !c1.is(c2)
+ assert !c1.favItems.is(c2.favItems)
+ assert !c1.since.is(c2.since)
+ assert p1.first.is(p2.first)
+ assert c1.first.is(c2.first)
+ }
+
+ void testCloningWithNonFinalFieldsCompileStatic() {
+ def p1 = new Person2CS(first:'John', last:'Smith')
+ def p2 = p1.clone()
+ def c1 = new Customer2CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
+ def c2 = c1.clone()
+
+ assert [p1, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(p1.first instanceof Cloneable)
+ assert !p1.is(p2)
+ assert !c1.is(c2)
+ assert !c1.favItems.is(c2.favItems)
+ assert !c1.since.is(c2.since)
+ assert p1.first.is(p2.first)
+ assert c1.first.is(c2.first)
+ }
+
+ void testCloningWithSerializable() {
+ def c1 = new Customer3(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date(), bonus:10)
+ def c2 = c1.clone()
+
+ assert [c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(c1.first instanceof Cloneable)
+ assert c1 == c2
+ assert !c1.is(c2)
+ assert !c1.favItems.is(c2.favItems)
+ assert !c1.since.is(c2.since)
+ // serialization gives us a new string even here
+ assert !c1.first.is(c2.first)
+ assert c1.bonus == c2.bonus
+ }
+
+ void testCloningWithSerializableCompileStatic() {
+ def c1 = new Customer3CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date(), bonus:10)
+ def c2 = c1.clone()
+
+ assert [c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(c1.first instanceof Cloneable)
+ assert c1 == c2
+ assert !c1.is(c2)
+ assert !c1.favItems.is(c2.favItems)
+ assert !c1.since.is(c2.since)
+ // serialization gives us a new string even here
+ assert !c1.first.is(c2.first)
+ assert c1.bonus == c2.bonus
+ }
+
+ void testCloningWithExternalizable() {
+ def c1 = new Customer4(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date(), bonus:10)
+ def c2 = c1.clone()
+
+ assert c1 instanceof Externalizable
+ assert [c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(c1.first instanceof Cloneable)
+ assert c1 == c2
+ assert !c1.is(c2)
+ assert !c1.favItems.is(c2.favItems)
+ assert !c1.since.is(c2.since)
+ // serialization gives us a new string even here
+ assert !c1.first.is(c2.first)
+ assert c1.bonus == c2.bonus
+ }
+
+ void testCloningWithExternalizableCompileStatic() {
+ def c1 = new Customer4CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date(), bonus:10)
+ def c2 = c1.clone()
+
+ assert c1 instanceof Externalizable
+ assert [c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(c1.first instanceof Cloneable)
+ assert c1 == c2
+ assert !c1.is(c2)
+ assert !c1.favItems.is(c2.favItems)
+ assert !c1.since.is(c2.since)
+ // serialization gives us a new string even here
+ assert !c1.first.is(c2.first)
+ assert c1.bonus == c2.bonus
+ }
+
+ void testCloningWithDefaultObjectClone() {
+ def c1 = new Customer5(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], age:25)
+ def c2 = c1.clone()
+ assert c1.first == c2.first
+ assert c1.last == c2.last
+ assert c1.favItems == c2.favItems
+ // no need to use includeFields=true here because of Object clone!
+ assert c1.agePeek() == c2.agePeek()
+ }
+
+ void testCloningWithDefaultObjectCloneCompileStatic() {
+ def c1 = new Customer5CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], age:25)
+ def c2 = c1.clone()
+ assert c1.first == c2.first
+ assert c1.last == c2.last
+ assert c1.favItems == c2.favItems
+ // no need to use includeFields=true here because of Object clone!
+ assert c1.agePeek() == c2.agePeek()
+ }
+
+ // GROOVY-4786
+ void testExcludesWithEqualsAndHashCode() {
+ def p1 = new PointIgnoreY(x:1, y:10)
+ def p2 = new PointIgnoreY(x:1, y:100)
+ assert p1 == p2
+ assert p1.hashCode() == p2.hashCode()
+ }
+
+ // GROOVY-4894
+ void testIncludesWithToString() {
+ def p1 = new PointIgnoreY(x:1, y:5)
+ def p2 = new PointIgnoreY(x:10, y:50)
+ assert p1.toString() == "org.codehaus.groovy.transform.PointIgnoreY(1)"
+ assert p2.toString() == "org.codehaus.groovy.transform.PointIgnoreY(10)"
+ }
+
+ // GROOVY-4894
+ void testNestedExcludes() {
+ def (s1, s2) = new GroovyShell().evaluate("""
+ import groovy.transform.*
+ @Canonical(excludes='foo, baz')
+ @ToString(includeNames=true)
+ class Hello {
+ String foo = 'A', bar = 'B', baz = 'C'
+ }
+ @Canonical(excludes='foo, baz')
+ @ToString(excludes='foo', includeNames=true)
+ class Goodbye {
+ String foo = 'A', bar = 'B', baz = 'C'
+ }
+ [new Hello().toString(), new Goodbye().toString()]
+ """)
+ assert s1 == 'Hello(bar:B)'
+ assert s2 == 'Goodbye(bar:B, baz:C)'
+ }
+
+ // GROOVY-4844
+ void testToStringCustomGetter() {
+ def p1 = new Point(1, 2)
+ def p2 = new Point(1, 1) { int getY() { 2 } }
+ assert p1.toString() == 'org.codehaus.groovy.transform.Point(1, 2)'
+ assert p2.toString() == 'org.codehaus.groovy.transform.Point(1, 2)'
+ }
+
+ // GROOVY-4849
+ void testEqualsOnEquivalentClasses() {
+ def (p1, p2, p3) = new GroovyShell().evaluate("""
+ import groovy.transform.*
+ @Canonical class IntPair {
+ int x, y
+ }
+
+ @InheritConstructors
+ class IntPairWithSum extends IntPair {
+ def sum() { x + y }
+ }
+
+ [new IntPair(1, 2), new IntPair(1, 1) { int getY() { 2 } }, new IntPairWithSum(x:1, y:2)]
+ """)
+
+ assert p1 == p2 && p2 == p1
+ assert p1 == p3 && p3 == p1
+ assert p3 == p2 && p2 == p3
+ }
+
+ // GROOVY-4849
+ void testEqualsOnDifferentClasses() {
+ def (p1, p2, p3, t1) = new GroovyShell().evaluate("""
+ import groovy.transform.*
+ @Canonical class IntPair {
+ int x, y
+ boolean hasEqualXY(other) { other.x == getX() && other.y == getY() }
+ }
+
+ @InheritConstructors
+ class IntPairWithSum extends IntPair {
+ def sum() { x + y }
+ }
+
+ @EqualsAndHashCode
+ @TupleConstructor(includeSuperProperties=true)
+ class IntTriple extends IntPair { int z }
+
+ [new IntPair(1, 2), new IntPair(1, 1) { int getY() { 2 } }, new IntPairWithSum(x:1, y:2), new IntTriple(1, 2, 3)]
+ """)
+
+ assert p1 != t1 && p2 != t1 && t1 != p3
+ assert p1.hasEqualXY(t1) && t1.hasEqualXY(p1)
+ assert p2.hasEqualXY(t1) && t1.hasEqualXY(p2)
+ assert p3.hasEqualXY(t1) && t1.hasEqualXY(p3)
+ }
+
+ // GROOVY-4714
+ void testCachingOfHashCode() {
+ def (h1, h2, h3, h4) = new GroovyShell().evaluate("""
+ import groovy.transform.*
+ // DO NOT DO THIS AT HOME - cache should only be true for Immutable
+ // objects but we cheat here for testing purposes
+ @EqualsAndHashCode(cache = true) class ShouldBeImmutableIntPair {
+ /* final */ int x, y
+ }
+ // the control (hashCode should change when x or y changes)
+ @EqualsAndHashCode class MutableIntPair {
+ int x, y
+ }
+ def sbmip = new ShouldBeImmutableIntPair(x: 3, y: 4)
+ def h1 = sbmip.hashCode()
+ sbmip.x = 5
+ def h2 = sbmip.hashCode()
+ def mip = new MutableIntPair(x: 3, y: 4)
+ def h3 = mip.hashCode()
+ mip.x = 5
+ def h4 = mip.hashCode()
+ [h1, h2, h3, h4]
+ """)
+
+ assert h1 == h2 // since it is cached
+ assert h3 != h4 // no caching
+ }
+
+ // GROOVY-5928
+ void testCachingOfToString() {
+ def (ts1, ts2, ts3, ts4) = new GroovyShell().evaluate("""
+ import groovy.transform.*
+ // DO NOT DO THIS AT HOME - cache should only be true for Immutable
+ // objects but we cheat here for testing purposes
+ @ToString(cache = true) class ShouldBeImmutableIntPair {
+ /* final */ int x, y
+ }
+ // the control (toString should change when x or y changes)
+ @ToString class MutableIntPair {
+ int x, y
+ }
+ def sbmip = new ShouldBeImmutableIntPair(x: 3, y: 4)
+ def ts1 = sbmip.toString()
+ sbmip.x = 5
+ def ts2 = sbmip.toString()
+ def mip = new MutableIntPair(x: 3, y: 4)
+ def ts3 = mip.toString()
+ mip.x = 5
+ def ts4 = mip.toString()
+ [ts1, ts2, ts3, ts4]
+ """)
+
+ assert ts1 == ts2 // since it is cached
+ assert ts3 != ts4 // no caching
+ }
+
+ // GROOVY-6337
+ void testCanonicalWithLazy() {
+ def result = new GroovyShell().evaluate('''
+ @groovy.transform.Canonical
+ class Person {
+ @Lazy first = missing()
+ @Lazy last = 'Smith'
+ int age
+ }
+ def p = new Person(21)
+ // $first setter is an implementation detail
+ p.$first = 'Mary'
+ p.toString()
+ ''')
+ assert result == 'Person(21, Mary, Smith)'
+ }
+
+ // GROOVY-5901
+ void testSimpleCloning() {
+ def p1 = new Person6(first:'John', last:'Smith', since:new Date())
+ def p2 = p1.clone()
+ def c1 = new Customer6(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
+ def c2 = c1.clone()
+
+ assert [p1, p1.since, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(p1.first instanceof Cloneable)
+ assert !p1.is(p2)
+ assert !c1.is(c2)
+ assert !c1.favItems.is(c2.favItems)
+ assert !p1.since.is(p2.since)
+ assert !c1.since.is(c2.since)
+ assert p1.first.is(p2.first)
+ assert c1.first.is(c2.first)
+ }
+
+ void testSimpleCloningCompileStatic() {
+ def p1 = new Person6CS(first:'John', last:'Smith', since:new Date())
+ def p2 = p1.clone()
+ def c1 = new Customer6CS(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
+ def c2 = c1.clone()
+
+ assert [p1, p1.since, c1, c1.favItems, c1.since].every{ it instanceof Cloneable }
+ assert !(p1.first instanceof Cloneable)
+ assert !p1.is(p2)
+ assert !c1.is(c2)
+ assert !c1.favItems.is(c2.favItems)
+ assert !p1.since.is(p2.since)
+ assert !c1.since.is(c2.since)
+ assert p1.first.is(p2.first)
+ assert c1.first.is(c2.first)
+ }
+
+ // GROOVY-4849
+ void testCanEqualDefined() {
+ def p1 = new IntPair(1, 2)
+ def p2 = new IntPairNoCanEqual(x:1, y:2)
+ assert p1 != p2
+ assert p1.hashCode() == p2.hashCode()
+ assert 'canEqual' in p1.class.methods*.name
+ assert !('canEqual' in p2.class.methods*.name)
+ }
+
+ // GROOVY-5864
+ void testExternalizeMethodsWithImmutable() {
+ try {
+ 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)
+ assert syntaxError.message.contains("cannot modify final field 'first' outside of constructor")
+ }
+ }
+
+ // GROOVY-5864
+ void testExternalizeVerifierWithNonExternalizableClass() {
+ try {
+ 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)
+ assert syntaxError.message.contains("An Externalizable class must implement the Externalizable interface")
+ }
+ }
+
+ // GROOVY-5864
+ void testExternalizeVerifierWithFinalField() {
+ try {
+ 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)
+ assert syntaxError.message.contains("The Externalizable property (or field) 'first' cannot be final")
+ }
+ }
+
+ // GROOVY-5864
+ void testAutoExternalizeWithoutNoArg() {
+ try {
+ 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)
+ assert syntaxError.message.contains("An Externalizable class requires a no-arg constructor but none found")
+ }
+ }
+
+ // GROOVY-5864
+ void testExternalizeVerifierWithNonExternalizableField() {
+ try {
+ new GroovyShell().parse '''
+ class Name {}
+
+ @groovy.transform.ExternalizeVerifier(checkPropertyTypes=true)
+ class Person implements Externalizable {
+ Name name
+ int age
+ 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)
+ assert syntaxError.message.contains("strict type checking is enabled and the non-primitive property (or field) 'name' in an Externalizable class has the type 'Name' which isn't Externalizable or Serializable")
+ }
+ }
+
+ // GROOVY-5864
+ void testAutoExternalizeHappyPath() {
+ new GroovyShell().evaluate """
+ import org.codehaus.groovy.transform.*
+ def orig = new Person7(name: new Name7('John', 'Smith'), address: new Address7(street: 'somewhere lane', town: 'my town'), age: 21, verified: true)
+ def baos = new ByteArrayOutputStream()
+ baos.withObjectOutputStream{ os -> os.writeObject(orig) }
+ def bais = new ByteArrayInputStream(baos.toByteArray())
+ bais.withObjectInputStream { is -> assert is.readObject().toString() == 'Person7(Name7(John, Smith), Address7(somewhere lane, my town), 21, true)' }
+ """
+ }
+
+ void testAutoExternalizeHappyPathCompileStatic() {
+ new GroovyShell().evaluate """
+ import org.codehaus.groovy.transform.*
+ def orig = new Person7CS(name: new Name7CS('John', 'Smith'), address: new Address7CS(street: 'somewhere lane', town: 'my town'), age: 21, verified: true)
+ def baos = new ByteArrayOutputStream()
+ baos.withObjectOutputStream{ os -> os.writeObject(orig) }
+ def bais = new ByteArrayInputStream(baos.toByteArray())
+ bais.withObjectInputStream { is -> assert is.readObject().toString() == 'Person7CS(Name7CS(John, Smith), Address7CS(somewhere lane, my town), 21, true)' }
+ """
+ }
+
+ void testAutoExternalizeNestedClassCompileStatic_GROOVY7644() {
+ new GroovyShell().evaluate '''
+ import org.codehaus.groovy.transform.*
+
+ def orig = new Person7NestedAddressCS.Address7CS(street: 'somewhere lane', town: 'my town')
+ def baos = new ByteArrayOutputStream()
+ baos.withObjectOutputStream{ os -> os.writeObject(orig) }
+ def bais = new ByteArrayInputStream(baos.toByteArray())
+ bais.withObjectInputStream { is -> assert is.readObject().toString() == 'Person7NestedAddressCS$Address7CS(somewhere lane, my town)' }
+ '''
+ }
+
+ // GROOVY-4570
+ void testToStringForEnums() {
+ assert Color.PURPLE.toString() == 'org.codehaus.groovy.transform.Color(r:255, g:0, b:255)'
+ }
+
+ void testCustomCopyConstructor_GROOVY7016() {
+ new GroovyShell().evaluate """
+ import org.codehaus.groovy.transform.Shopper
+ def p1 = new Shopper('John', [['bread', 'milk'], ['bacon', 'eggs']])
+ def p2 = p1.clone()
+ p2.shoppingHistory[0][1] = 'jam'
+ assert p1.shoppingHistory[0] == ['bread', 'milk']
+ assert p2.shoppingHistory[0] == ['bread', 'jam']
+ """
+ }
+
+ void testTupleConstructorNoDefaultParameterValues_GROOVY7427() {
+ new GroovyShell().evaluate """
+ // checks special Map behavior isn't added if defaults=false
+ import groovy.transform.*
+
+ @ToString
+ @TupleConstructor(defaults=false)
+ class Person {
+ def name
+ }
+
+ assert new Person('John Smith').toString() == 'Person(John Smith)'
+ assert Person.constructors.size() == 1
+ """
+ }
+
+ void testNullCloneableField_GROOVY7091() {
+ new GroovyShell().evaluate """
+ import groovy.transform.AutoClone
+ @AutoClone
+ class B {
+ String name='B'
+ }
+
+ @AutoClone
+ class A {
+ B b
+ C c
+ ArrayList x
+ List y
+ String name='A'
+ }
+
+ @AutoClone
+ class C {
+ String name='C'
+ }
+
+ def b = new B().clone()
+ assert b
+ assert new A(b:b).clone()
+ assert new A().clone()
+ """
+ }
+
+ void testTupleConstructorUsesSetters_GROOVY7087() {
+ new GroovyShell().evaluate """
+ import groovy.transform.*
+
+ @ToString @TupleConstructor(useSetters=true)
+ class Foo1 {
+ String bar, baz
+ void setBar(String bar) {
+ this.bar = bar?.toUpperCase()
+ }
+ }
+
+ assert new Foo1('cat', 'dog').toString() == 'Foo1(CAT, dog)'
+ // check the default map-style constructor too
+ assert new Foo1(bar: 'cat', baz: 'dog').toString() == 'Foo1(CAT, dog)'
+ """
+ }
+
+ void testincludeSuperFieldsAndroperties_GROOVY8013() {
+ new GroovyShell().evaluate """
+ import groovy.transform.*
+
+ @ToString
+ class Foo {
+ String baz = 'baz'
+ protected String baz2
+ }
+
+ @TupleConstructor(includes='a,b,baz2', includeSuperFields=true)
+ @ToString(includes='a,c,super,baz,d', includeFields=true, includeSuperProperties=true, includeSuper=true)
+ class Bar extends Foo {
+ int a = 1
+ int b = 2
+ private int c = 3
+ public int d = 4
+ }
+
+ assert new Bar().toString() == 'Bar(1, 3, Foo(baz), baz, 4)'
+ """
+ }
+
+ void testOrderTupleParamsUsingIncludes_GROOVY8016() {
+ new GroovyShell().evaluate """
+ import groovy.transform.*
+
+ @ToString
+ class Foo {
+ String a
+ String c
+ }
+ @TupleConstructor(includes='d,c,b,a', includeSuperProperties=true, includeFields=true)
+ @ToString(includes='super,b,d', includeFields=true, includeSuperProperties=true, includeSuper=true)
+ class Bar extends Foo {
+ String d
+ private String b
+ }
+
+ assert new Bar('1', '2', '3', '4').toString() == 'Bar(Foo(4, 2), 3, 1)'
+ """
+ }
+
+ void testTupleConstructorWithForceDirectBypassesSetters_GROOVY7087() {
+ new GroovyShell().evaluate """
+ import groovy.transform.*
+
+ @ToString @TupleConstructor
+ class Foo2 {
+ String bar, baz
+ void setBar(String bar) {
+ this.bar = bar.toUpperCase()
+ }
+ }
+
+ assert new Foo2(bar: 'cat', baz: 'dog').toString() == 'Foo2(CAT, dog)'
+ assert new Foo2('cat', 'dog').toString() == 'Foo2(cat, dog)'
+ """
+ }
+
+ void testEqualsHashCodeToStringConsistencyWithExplicitBooleanGetters_GROOVY7417() {
+ new GroovyShell().evaluate """
+ import groovy.transform.*
+
+ @ToString
+ @EqualsAndHashCode
+ class A {
+ boolean x
+ }
+
+ def a1 = new A(x: true)
+ def a2 = new A(x: true)
+ def a3 = new A(x: false)
+ assert a1.toString() == a2.toString()
+ assert a1.hashCode() == a2.hashCode()
+ assert a1 == a2
+ assert a1.toString() != a3.toString()
+ assert a1.hashCode() != a3.hashCode()
+ assert a1 != a3
+
+ @ToString
+ @EqualsAndHashCode
+ class B {
+ boolean x
+ boolean isX() { false }
+ }
+
+ def b1 = new B(x: true)
+ def b2 = new B(x: false)
+ assert b1.toString() == b2.toString()
+ assert b1.hashCode() == b2.hashCode()
+ assert b1 == b2
+
+ @ToString
+ @EqualsAndHashCode
+ class C {
+ boolean x
+ boolean getX() { false }
+ }
+
+ def c1 = new C(x: true)
+ def c2 = new C(x: false)
+ assert c1.toString() == c2.toString()
+ assert c1.hashCode() == c2.hashCode()
+ assert c1 == c2
+
+ @ToString
+ @EqualsAndHashCode
+ class D {
+ boolean x
+ boolean isX() { false }
+ boolean getX() { false }
+ }
+
+ def d1 = new D(x: true)
+ def d2 = new D(x: false)
+ assert d1.toString() == d2.toString()
+ assert d1.hashCode() == d2.hashCode()
+ assert d1 == d2
+ """
+ }
+
+ void testHashCodeForInstanceWithNullPropertyAndField() {
+ new GroovyShell().evaluate """
+ import groovy.transform.*
+ @EqualsAndHashCode(includeFields = true)
+ class FieldAndPropertyIncludedInHashCode {
+ private String field
+ String property
+ }
+ assert new FieldAndPropertyIncludedInHashCode().hashCode() == 442087
+ """
+ }
+
+ void testHashCodeForInstanceWithNullPropertyAndJavaBeanProperty() {
+ new GroovyShell().evaluate '''
+ import groovy.transform.*
+ @EqualsAndHashCode(allProperties = true)
+ class FieldAndPropertyIncludedInHashCode {
+ String property
+ String getField() { null }
+ }
+ assert new FieldAndPropertyIncludedInHashCode().hashCode() == 442087
+ '''
+ }
+
+ // GROOVY-9009
+ void testAutoCloneToStringCompileStatic() {
+ new GroovyShell().evaluate '''
+ import groovy.transform.*
+
+ @ToString
+ @CompileStatic
+ @AutoClone
+ class SomeClass {
+ String someId
+ }
+
+ assert new SomeClass(someId: 'myid').clone().toString() == 'SomeClass(myid)'
+ '''
+ }
+}
+
+@TupleConstructor
+@AutoClone(style=COPY_CONSTRUCTOR)
+@EqualsAndHashCode
+class Person1 { final String first, last }
+
+@TupleConstructor(includeSuperProperties=true, callSuper=true)
+@AutoClone(style=COPY_CONSTRUCTOR)
+@EqualsAndHashCode
+class Customer1 extends Person1 { final List favItems; final Date since }
+
+@TupleConstructor
+@AutoClone(style=COPY_CONSTRUCTOR)
+@EqualsAndHashCode
+@CompileStatic
+class Person1CS { final String first, last }
+
+@CompileStatic
+@TupleConstructor(includeSuperProperties=true, callSuper=true)
+@AutoClone(style=COPY_CONSTRUCTOR)
+@EqualsAndHashCode
+class Customer1CS extends Person1CS { final List favItems; final Date since }
+
+@AutoClone(style=COPY_CONSTRUCTOR)
+class Person2 { String first, last }
+
+@AutoClone(style=COPY_CONSTRUCTOR)
+class Customer2 extends Person2 { List favItems = []; Date since }
+
+@AutoClone(style=COPY_CONSTRUCTOR)
+@CompileStatic
+class Person2CS { String first, last }
+
+@CompileStatic
+@AutoClone(style=COPY_CONSTRUCTOR)
+class Customer2CS extends Person2CS { List favItems = []; Date since }
+
+@AutoClone(style=SERIALIZATION)
+@EqualsAndHashCode
+class Customer3 implements Serializable {
+ String first, last
+ List favItems
+ Date since
+ int bonus
+}
+
+@AutoClone(style=SERIALIZATION)
+@CompileStatic
+@EqualsAndHashCode
+class Customer3CS implements Serializable {
+ String first, last
+ List favItems
+ Date since
+ int bonus
+}
+
+@AutoExternalize
+@AutoClone(style=SERIALIZATION)
+@EqualsAndHashCode
+class Customer4 {
+ String first, last
+ List favItems
+ Date since
+ int bonus
+}
+
+@CompileStatic
+@AutoExternalize
+@AutoClone(style=SERIALIZATION)
+@EqualsAndHashCode
+class Customer4CS {
+ String first, last
+ List favItems
+ Date since
+ int bonus
+}
+
+@AutoClone
+class Customer5 {
+ String first, last
+ List favItems
+ private int age
+ int agePeek() { age }
+}
+
+@CompileStatic
+@AutoClone
+class Customer5CS {
+ String first, last
+ List favItems
+ private int age
+ int agePeek() { age }
+}
+
+@TupleConstructor
+@AutoClone(style=SIMPLE)
+@EqualsAndHashCode
+class Person6 { String first, last; Date since }
+
+@TupleConstructor(includeSuperProperties=true, callSuper=true)
+@AutoClone(style=SIMPLE)
+@EqualsAndHashCode
+class Customer6 extends Person6 { List<String> favItems }
+
+@TupleConstructor
+@AutoClone(style=SIMPLE)
+@EqualsAndHashCode
+@CompileStatic
+class Person6CS { String first, last; Date since }
+
+@CompileStatic
+@TupleConstructor(includeSuperProperties=true, callSuper=true)
+@AutoClone(style=SIMPLE)
+@EqualsAndHashCode
+class Customer6CS extends Person6CS { List<String> favItems }
+
+// GROOVY-5864
+@Canonical
+@ToString(includePackage=false)
+class Name7 implements Serializable { String first, last }
+
+// GROOVY-5864
+@AutoExternalize
+@ToString(includePackage=false)
+class Address7 { String street, town }
+
+// GROOVY-5864
+@ToString(includePackage=false)
+@AutoExternalize(checkPropertyTypes=true)
+class Person7 {
+ Name7 name
+ Address7 address
+ int age
+ Boolean verified
+}
+
+@ToString(includePackage=false)
+@AutoExternalize(checkPropertyTypes=true)
+@CompileStatic
+class Person7CS {
+ Name7CS name
+ Address7CS address
+ int age
+ Boolean verified
+}
+
+@Canonical
+@CompileStatic
+@ToString(includePackage=false)
+class Name7CS implements Serializable { String first, last }
+
+@AutoExternalize
+@ToString(includePackage=false)
+@CompileStatic
+class Address7CS { String street, town }
+
+// GROOVY-7644
+class Person7NestedAddressCS {
+ @ToString(includePackage=false)
+ @AutoExternalize(checkPropertyTypes=true)
+ @CompileStatic
+ static class Address7CS {
+ String street, town
+ }
+}
+
+// GROOVY-4786
+@EqualsAndHashCode(excludes="y")
+@ToString(includes="x")
+class PointIgnoreY {
+ int x
+ int y // y coordinate excluded from Equals and hashCode
+}
+
+// GROOVY-4844
+@TupleConstructor @ToString
+class Point { int x, y }
+
+// GROOVY-4849
+@Canonical class IntPair {
+ int x, y
+}
+
+// GROOVY-4849
+@EqualsAndHashCode(useCanEqual=false)
+class IntPairNoCanEqual {
+ int x, y
+}
+
+// GROOVY-4570
+@ToString(includeNames=true)
+enum Color {
+ BLACK(0,0,0), WHITE(255,255,255), PURPLE(255,0,255)
+ int r, g, b
+ Color(int r, g, b) { this.r = r; this.g = g; this.b = b }
+}
+
+@TupleConstructor(force=true) @AutoClone(style=COPY_CONSTRUCTOR)
+class Shopper {
+ final String name
+ final List<List<String>> shoppingHistory
+ Shopper(Shopper other) {
+ name = other.name
+ // requires deep clone
+ shoppingHistory = other.shoppingHistory*.clone()
+ }
+}