blob: bfcca36cf3d125a3d65000547c7dea14bda043a7 [file] [log] [blame]
/*
* 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
}
"""
}
}