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() + } +}