| /* |
| * 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.test.GroovyShellTestCase |
| |
| class CanonicalTransformTest extends GroovyShellTestCase { |
| |
| void testCanonical() { |
| def objects = evaluate(""" |
| import groovy.transform.Canonical |
| @Canonical class Foo { |
| String x, y |
| } |
| [new Foo(x:'x', y:'y'), |
| new Foo('x', 'y')] |
| """) |
| |
| assertEquals objects[0].hashCode(), objects[1].hashCode() |
| assertEquals objects[0], objects[1] |
| } |
| |
| void testCanonicalWithDeclaredConstructor() { |
| def msg = shouldFail(GroovyRuntimeException) { |
| assertScript """ |
| @groovy.transform.Canonical class Foo { |
| def foo, bar, baz |
| |
| Foo() {} |
| |
| Foo(foo, bar) { |
| this.foo = foo |
| this.bar = bar |
| } |
| } |
| |
| def foo = new Foo('a', 'b') |
| def foo1 = new Foo() |
| foo1.foo = 'a' |
| foo1.bar = 'b' |
| assert foo == foo1 |
| |
| // Fail here |
| new Foo('a', 'b', 'c') |
| """ |
| } |
| assert msg.contains('Could not find matching constructor') |
| } |
| |
| void testCanonicalNotCopyOrCloneProperty() { |
| assertScript """ |
| def date = new Date() |
| def array = [1, 2, 3] as Integer[] |
| def map = [foo: 'bar'] |
| def collection = [4, 5, 6] |
| |
| @groovy.transform.Canonical class Foo { |
| Date date |
| Integer[] array |
| Map map |
| Collection collection |
| } |
| |
| def foo = new Foo(date, array, map, collection) |
| |
| assert date.is(foo.date) |
| assert array.is(foo.array) |
| assert map.is(foo.map) |
| assert collection.is(foo.collection) |
| """ |
| } |
| |
| void testCanonicalWithSuper() { |
| // related to Apache PR#9: "Add includeSuper property to @Canonical annotation" |
| assertScript """ |
| import groovy.transform.* |
| @Canonical |
| class Foo { |
| int a |
| } |
| @Canonical(callSuper=true, includeSuperProperties=true, includeNames=true) |
| class Bar extends Foo { |
| int b |
| } |
| @Canonical(callSuper=true, includeSuper=true) |
| @TupleConstructor(includeSuperProperties=true) |
| class Baz extends Foo { |
| int b |
| } |
| def (b1, b2, b3) = [new Bar(a:5, b:20), new Bar(10, 20), new Baz(15, 20)] |
| assert [b1, b2, b3].toString() == '[Bar(b:20, a:5), Bar(b:20, a:10), Baz(20, Foo(15))]' |
| assert b1 != b2 |
| assert b1.hashCode() != b2.hashCode() |
| """ |
| } |
| |
| void testCanonicalChange() { |
| def objects = evaluate(""" |
| @groovy.transform.Canonical class Foo { |
| String x, y |
| } |
| [new Foo(x:'x', y:'y'), |
| new Foo('x', 'y')] |
| """) |
| objects[0].x = 'z' |
| assertFalse objects[0] == objects[1] |
| assertFalse objects[0].hashCode() == objects[1].hashCode() |
| } |
| |
| void testUntyped() { |
| def object = evaluate(""" |
| @groovy.transform.Canonical class Foo { |
| def x |
| def y = 10 |
| } |
| new Foo() |
| """) |
| |
| assert null == object.x |
| assert 10 == object.y |
| |
| object.x = 20 |
| assert 20 == object.x |
| object.x = [1, 2, 3] |
| assert [1, 2, 3] == object.x |
| } |
| |
| void testCanonicalRectangleFieldsNamesTupleToString() { |
| assert 'Rectangle(w:10, h:20, x:5, y:7)' == evaluate(""" |
| import groovy.transform.* |
| @Canonical |
| @ToString(includeNames=true, includeFields=true) |
| @TupleConstructor(includeFields=true) |
| class Rectangle { |
| int w, h |
| private int x, y |
| } |
| new Rectangle(10, 20, 5, 7).toString() |
| """) |
| } |
| |
| void testCanonicalRectangleFieldsToString() { |
| assert 'Rectangle(10, 20, 0, 0)' == evaluate(""" |
| import groovy.transform.* |
| @Canonical |
| @ToString(includeFields=true) |
| class Rectangle { |
| int w, h |
| private int x, y |
| } |
| new Rectangle(10, 20).toString() |
| """) |
| } |
| |
| void testCanonicalDefaultToString() { |
| assert 'Rectangle(10, 20)' == evaluate(""" |
| import groovy.transform.* |
| @Canonical |
| class Rectangle { |
| int w, h |
| private int x, y |
| } |
| new Rectangle(10, 20).toString() |
| """) |
| } |
| |
| void testCanonicalRectangleTupleNamesToString() { |
| assert 'Rectangle(w:10, h:20)' == evaluate(""" |
| import groovy.transform.* |
| @Canonical |
| @ToString(includeNames=true) |
| @TupleConstructor(includeFields=true) |
| class Rectangle { |
| int w, h |
| private int x, y |
| } |
| new Rectangle(10, 20, 5, 7).toString() |
| """) |
| } |
| |
| void testCanonicalRectangleFieldsTupleToString() { |
| assert 'Rectangle(10, 20, 5, 7)' == evaluate(""" |
| import groovy.transform.* |
| @Canonical |
| @ToString(includeFields=true) |
| @TupleConstructor(includeFields=true) |
| class Rectangle { |
| int w, h |
| private int x, y |
| } |
| new Rectangle(10, 20, 5, 7).toString() |
| """) |
| } |
| |
| void testCanonicalRectangleFieldsNamesToString() { |
| assert 'Rectangle(w:10, h:20, x:0, y:0)' == evaluate(""" |
| import groovy.transform.* |
| @Canonical |
| @ToString(includeNames=true, includeFields=true) |
| class Rectangle { |
| int w, h |
| private int x, y |
| } |
| new Rectangle(10, 20).toString() |
| """) |
| } |
| |
| void testMapConstructorOptional() { |
| assertScript """ |
| @groovy.transform.Canonical class Foo { |
| String bar |
| String baz = 'a' |
| } |
| |
| def foo = new Foo(bar: 'c') |
| def foo1 = new Foo(baz: 'd') |
| assert 'a' == foo.baz |
| assert 'c' == foo.bar |
| assert 'd' == foo1.baz |
| assert null == foo1.bar |
| """ |
| } |
| |
| void testMapConstructorOptionalPrimitive() { |
| assertScript """ |
| @groovy.transform.Canonical class Foo { |
| String a |
| int b |
| char c |
| short d |
| long e |
| byte f |
| double h |
| float i |
| boolean j |
| } |
| |
| new Foo("foo") |
| new Foo("foo", 10) |
| new Foo("foo", 10, (char) 20) |
| new Foo("foo", 10, (char) 20, (short) 30) |
| new Foo("foo", 10, (char) 20, (short) 30, 40L) |
| new Foo("foo", 10, (char) 20, (short) 30, 40L, (byte) 50) |
| new Foo("foo", 10, (char) 20, (short) 30, 40L, (byte) 50, 0.0) |
| new Foo("foo", 10, (char) 20, (short) 30, 40L, (byte) 50, 0.0, 0.0F) |
| """ |
| } |
| |
| void testOrderedConstructorOptional() { |
| assertScript """ |
| @groovy.transform.Canonical class Foo { |
| String bar |
| String baz = 'a' |
| } |
| |
| def foo = new Foo() |
| def foo0 = new Foo('c') |
| def foo1 = new Foo('c', 'd') |
| assert null == foo.bar |
| assert 'a' == foo.baz |
| assert 'c' == foo0.bar |
| assert 'a' == foo0.baz |
| assert 'c' == foo1.bar |
| assert 'd' == foo1.baz |
| """ |
| } |
| |
| void testCanonicalListProp() { |
| def objects = evaluate(""" |
| @groovy.transform.Canonical class HasList { |
| String[] letters |
| List nums |
| } |
| def letters = 'A,B,C'.split(',') |
| def nums = [1, 2] |
| [new HasList(letters:letters, nums:nums), |
| new HasList(letters, nums)] |
| """) |
| |
| assertEquals objects[0].hashCode(), objects[1].hashCode() |
| assertEquals objects[0], objects[1] |
| assert objects[0].letters.size() == 3 |
| assert objects[0].nums.size() == 2 |
| |
| objects[0].nums = [1, 2, 3] |
| objects[1].letters = 'D,E'.split(',') |
| |
| assert objects[0].nums.size() == 3 |
| assert objects[1].letters.size() == 2 |
| assertFalse objects[0] == objects[1] |
| assert !(objects[0].hashCode() == objects[1].hashCode()) |
| } |
| |
| void testCanonicalChangeArray() { |
| assertScript """ |
| @groovy.transform.Canonical class HasListAndMap { |
| Object[] foo |
| } |
| def object = new HasListAndMap() |
| def object2 = new HasListAndMap(['bar'] as Object[]) |
| |
| assert object != object2 |
| |
| object.foo = new Object[1] |
| object.foo[0] = 'bar' |
| |
| assert object == object2 |
| """ |
| } |
| |
| void testCanonicalChangeCollection() { |
| def object = evaluate(""" |
| @groovy.transform.Canonical class HasListAndMap { |
| List nums |
| Map map |
| } |
| new HasListAndMap(nums:[], map:[:]) |
| """) |
| |
| object.nums << 1 |
| object.nums.add 2 |
| object.map.foo = "bat" |
| assertEquals 2, object.nums.size() |
| assertEquals 1, object.map.size() |
| object.nums.remove 0 |
| assertEquals 1, object.nums.size() |
| } |
| |
| void testCanonicalAsMapKey() { |
| assertScript """ |
| @groovy.transform.Canonical final class HasString { |
| String s |
| } |
| def k1 = new HasString('xyz') |
| def k2 = new HasString('xyz') |
| def map = [(k1):42] |
| assert map[k2] == 42 |
| """ |
| } |
| |
| void testCanonicalWithOnlyMap() { |
| assertScript """ |
| @groovy.transform.Canonical final class HasMap { |
| Map map |
| } |
| def m = new HasMap([:]) |
| new HashMap() |
| """ |
| } |
| |
| void testCanonicalWithInvalidPropertyName() { |
| def msg = shouldFail(MissingPropertyException) { |
| assertScript """ |
| @groovy.transform.Canonical class Simple { } |
| new Simple(missing:'Name') |
| """ |
| } |
| assert msg.contains('No such property: missing for class: Simple') |
| } |
| |
| void testCanonicalWithHashMap() { |
| assertScript """ |
| @groovy.transform.Canonical final class HasHashMap { |
| HashMap map = [d:4] |
| } |
| assert new HasHashMap([a:1]).map == [a:1] |
| assert new HasHashMap(c:3).map == [c:3] |
| assert new HasHashMap(null).map == null |
| assert new HasHashMap().map == [d:4] |
| assert new HasHashMap([:]).map == [:] |
| assert new HasHashMap(map:5, c:3).map == [map:5, c:3] |
| assert new HasHashMap(map:[:]).map == [map:[:]] |
| """ |
| } |
| |
| void testCanonicalEquals() { |
| assertScript """ |
| @groovy.transform.Canonical class This { String value } |
| @groovy.transform.Canonical class That { String value } |
| class Other { } |
| |
| assert new This('foo') == new This("foo") |
| assert new This('f${"o"}o') == new This("foo") |
| |
| assert new This('foo') != new This("bar") |
| assert new This('foo') != new That("foo") |
| assert new This('foo') != new Other() |
| assert new Other() != new This("foo") |
| """ |
| } |
| |
| void testExistingToString() { |
| assertScript """ |
| import groovy.transform.Canonical |
| @Canonical class Foo { |
| String value |
| } |
| @Canonical class Bar { |
| String value |
| String toString() { 'zzz' + _toString() } |
| } |
| @Canonical class Baz { |
| String value |
| String toString() { 'zzz' + _toString() } |
| def _toString() { 'xxx' } |
| } |
| def foo = new Foo('abc') |
| def foo0 = new Foo('abc') |
| def foo1 = new Foo(value:'abc') |
| def bar = new Bar('abc') |
| def baz = new Baz('abc') |
| assert bar.toString() == 'zzz' + foo.toString().replaceAll('Foo', 'Bar') |
| assert baz.toString() == 'zzzxxx' |
| |
| assert 'Foo(abc)' == foo0.toString() |
| foo0.value = 'cde' |
| assert 'Foo(cde)' == foo0.toString() |
| assert 'Foo(abc)' == foo1.toString() |
| foo1.value = 'cde' |
| assert 'Foo(cde)' == foo1.toString() |
| """ |
| } |
| |
| void testExistingEquals() { |
| assertScript """ |
| import groovy.transform.Canonical |
| @Canonical class Foo { |
| String value |
| } |
| @Canonical class Bar { |
| String value |
| // doesn't follow normal conventions - for testing only |
| boolean equals(other) { value == 'abc' || _equals(other) } |
| } |
| @Canonical class Baz { |
| String value |
| // doesn't follow normal conventions - for testing only |
| boolean equals(Baz other) { value == 'abc' || _equals(other) } |
| def _equals(other) { false } |
| } |
| def foo1 = new Foo('abc') |
| def foo2 = new Foo('abc') |
| def foo3 = new Foo('def') |
| assert foo1 == foo2 |
| assert foo1 != foo3 |
| |
| def bar1 = new Bar('abc') |
| def bar2 = new Bar('abc') |
| def bar3 = new Bar('def') |
| def bar4 = new Bar('def') |
| assert bar1 == bar2 |
| assert bar1 == bar3 |
| assert bar3 != bar1 |
| |
| def baz1 = new Baz('abc') |
| def baz2 = new Baz('abc') |
| def baz3 = new Baz('def') |
| def baz4 = new Baz('def') |
| assert baz1 == baz2 |
| assert baz1 == baz3 |
| assert baz3 != baz1 |
| assert baz3 != baz4 |
| """ |
| } |
| |
| void testExistingHashCode() { |
| assertScript """ |
| import groovy.transform.Canonical |
| @Canonical class Foo { |
| String value |
| } |
| @Canonical class Bar { |
| String value |
| // doesn't follow normal conventions - for testing only |
| int hashCode() { value == 'abc' ? -1 : _hashCode() } |
| } |
| @Canonical class Baz { |
| String value |
| // doesn't follow normal conventions - for testing only |
| int hashCode() { value == 'abc' ? -1 : _hashCode() } |
| def _hashCode() { -100 } |
| } |
| def foo1 = new Foo('abc') |
| def foo2 = new Foo('abc') |
| assert foo1.hashCode() == foo2.hashCode() |
| |
| def bar1 = new Bar('abc') |
| def bar2 = new Bar('def') |
| def bar3 = new Bar('def') |
| assert bar1.hashCode() == -1 |
| assert bar2.hashCode() == bar3.hashCode() |
| |
| def baz1 = new Baz('abc') |
| def baz2 = new Baz('def') |
| assert baz1.hashCode() == -1 |
| assert baz2.hashCode() == -100 |
| """ |
| } |
| |
| void testEqualsCopesWithSelfReference() { |
| assertScript """ |
| @groovy.transform.Canonical class Tree { |
| Tree left, right |
| Object item |
| } |
| |
| def t = new Tree() |
| t.left = t |
| t.item = 4 |
| def s = new Tree() |
| s.left = s |
| s.item = 4 |
| assert s.equals(t) |
| // not smart enough to handle mutual-recursion yet |
| // don't use this annotation in such a scenario |
| // |
| // t.right = s |
| // s.right = t |
| // assert s.equals(t) // <= StackOverflowError |
| """ |
| } |
| |
| void testHashCodeCopesWithSelfReference() { |
| assertScript """ |
| @groovy.transform.Canonical class Tree { |
| Object item |
| Tree left, right |
| } |
| |
| def t = new Tree(4) |
| t.left = t |
| t.right = t |
| assert t.hashCode() == 7497 |
| // not smart enough to handle mutual-recursion yet |
| // don't use this annotation in such a scenario |
| // |
| // def s = new Tree(5, t) |
| // t.left = s |
| // println t.hashCode() // <= StackOverflowError |
| """ |
| } |
| |
| void testMapStyleConstructorSupportWithObjectOrMapFirstProperty() { |
| // GROOVY-5243: special support for Map, Object, AbstractMap, HashMap but currently not LinkedHashMap |
| assertScript """ |
| import groovy.transform.* |
| |
| def obj1 = new Canonical1(foo: [:], bar: 'BAR') |
| def obj2 = new Canonical2(foo: [:], bar: 'BAR') |
| def obj3 = new Canonical3(foo: [:], bar: 'BAR') |
| |
| check(obj1) |
| check(obj2) |
| check(obj3) |
| |
| def check(obj) { |
| assert obj.foo == [:] |
| assert obj.bar == 'BAR' |
| } |
| |
| @Canonical |
| class Canonical1 { |
| def foo |
| String bar |
| } |
| @Canonical |
| class Canonical2 { |
| Map foo |
| String bar |
| } |
| @Canonical |
| class Canonical3 { |
| HashMap foo |
| String bar |
| } |
| """ |
| } |
| } |