blob: 71a7dc5bf690ef20cc66069c01bc44d8cbbafb25 [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 gdk
import org.junit.jupiter.api.Test
import static groovy.test.GroovyAssert.assertScript
class WorkingWithCollectionsTest {
@Test
void testListLiterals() {
// tag::list_literals[]
def list = [5, 6, 7, 8]
assert list.get(2) == 7
assert list[2] == 7
assert list instanceof java.util.List
def emptyList = []
assert emptyList.size() == 0
emptyList.add(5)
assert emptyList.size() == 1
// end::list_literals[]
}
@Test
void testList() {
// tag::list_usecases[]
def list = [5, 6, 7, 8]
assert list.size() == 4
assert list.getClass() == ArrayList // the specific kind of list being used
assert list[2] == 7 // indexing starts at 0
assert list.getAt(2) == 7 // equivalent method to subscript operator []
assert list.get(2) == 7 // alternative method
list[2] = 9
assert list == [5, 6, 9, 8,] // trailing comma OK
list.putAt(2, 10) // equivalent method to [] when value being changed
assert list == [5, 6, 10, 8]
assert list.set(2, 11) == 10 // alternative method that returns old value
assert list == [5, 6, 11, 8]
assert ['a', 1, 'a', 'a', 2.5, 2.5f, 2.5d, 'hello', 7g, null, 9 as byte]
//objects can be of different types; duplicates allowed
assert [1, 2, 3, 4, 5][-1] == 5 // use negative indices to count from the end
assert [1, 2, 3, 4, 5][-2] == 4
assert [1, 2, 3, 4, 5].getAt(-2) == 4 // getAt() available with negative index...
try {
[1, 2, 3, 4, 5].get(-2) // but negative index not allowed with get()
assert false
} catch (e) {
assert e instanceof IndexOutOfBoundsException
}
// end::list_usecases[]
}
@Test
void testListsAsBoolean() {
// tag::list_to_bool[]
assert ![] // an empty list evaluates as false
//all other lists, irrespective of contents, evaluate as true
assert [1] && ['a'] && [0] && [0.0] && [false] && [null]
// end::list_to_bool[]
}
@Test
void testLeftShiftOnList() {
// tag::list_leftshift[]
def list = []
assert list.empty
list << 5
assert list.size() == 1
list << 7 << 'i' << 11
assert list == [5, 7, 'i', 11]
list << ['m', 'o']
assert list == [5, 7, 'i', 11, ['m', 'o']]
//first item in chain of << is target list
assert ([1, 2] << 3 << [4, 5] << 6) == [1, 2, 3, [4, 5], 6]
//using leftShift is equivalent to using <<
assert ([1, 2, 3] << 4) == ([1, 2, 3].leftShift(4))
// end::list_leftshift[]
}
@Test
void testListAdd() {
// tag::list_add[]
assert [1, 2] + 3 + [4, 5] + 6 == [1, 2, 3, 4, 5, 6]
// equivalent to calling the `plus` method
assert [1, 2].plus(3).plus([4, 5]).plus(6) == [1, 2, 3, 4, 5, 6]
def a = [1, 2, 3]
a += 4 // creates a new list and assigns it to `a`
a += [5, 6]
assert a == [1, 2, 3, 4, 5, 6]
assert [1, *[222, 333], 456] == [1, 222, 333, 456]
assert [*[1, 2, 3]] == [1, 2, 3]
assert [1, [2, 3, [4, 5], 6], 7, [8, 9]].flatten() == [1, 2, 3, 4, 5, 6, 7, 8, 9]
def list = [1, 2]
list.add(3)
list.addAll([5, 4])
assert list == [1, 2, 3, 5, 4]
list = [1, 2]
list.add(1, 3) // add 3 just before index 1
assert list == [1, 3, 2]
list.addAll(2, [5, 4]) //add [5,4] just before index 2
assert list == [1, 3, 5, 4, 2]
list = ['a', 'b', 'z', 'e', 'u', 'v', 'g']
list[8] = 'x' // the [] operator is growing the list as needed
// nulls inserted if required
assert list == ['a', 'b', 'z', 'e', 'u', 'v', 'g', null, 'x']
// end::list_add[]
}
@Test
void testListIteration() {
// tag::list_each[]
[1, 2, 3].each {
println "Item: $it" // `it` is an implicit parameter corresponding to the current element
}
['a', 'b', 'c'].eachWithIndex { it, i -> // `it` is the current element, while `i` is the index
println "$i: $it"
}
// end::list_each[]
}
@Test
void testListConstruct() {
// tag::list_construct[]
def list1 = ['a', 'b', 'c']
//construct a new list, seeded with the same items as in list1
def list2 = new ArrayList<String>(list1)
assert list2 == list1 // == checks that each corresponding element is the same
// clone() can also be called
def list3 = list1.clone()
assert list3 == list1
// end::list_construct[]
}
@Test
void testListCollect() {
// tag::list_collect[]
assert [1, 2, 3].collect { it * 2 } == [2, 4, 6]
// shortcut syntax instead of collect
assert [1, 2, 3]*.multiply(2) == [1, 2, 3].collect { it.multiply(2) }
def list = [0]
// it is possible to give `collect` the list which collects the elements
assert [1, 2, 3].collect(list) { it * 2 } == [0, 2, 4, 6]
assert list == [0, 2, 4, 6]
// end::list_collect[]
}
@Test
void testListGDK() {
// tag::list_gdk1[]
assert [1, 2, 3].find { it > 1 } == 2 // find 1st element matching criteria
assert [1, 2, 3].findAll { it > 1 } == [2, 3] // find all elements matching critieria
assert ['a', 'b', 'c', 'd', 'e'].findIndexOf { // find index of 1st element matching criteria
it in ['c', 'e', 'g']
} == 2
assert ['a', 'b', 'c', 'd', 'c'].indexOf('c') == 2 // index returned
assert ['a', 'b', 'c', 'd', 'c'].indexOf('z') == -1 // index -1 means value not in list
assert ['a', 'b', 'c', 'd', 'c'].lastIndexOf('c') == 4
assert [1, 2, 3].every { it < 5 } // returns true if all elements match the predicate
assert ![1, 2, 3].every { it < 3 }
assert [1, 2, 3].any { it > 2 } // returns true if any element matches the predicate
assert ![1, 2, 3].any { it > 3 }
assert [1, 2, 3, 4, 5, 6].sum() == 21 // sum anything with a plus() method
assert ['a', 'b', 'c', 'd', 'e'].sum {
it == 'a' ? 1 : it == 'b' ? 2 : it == 'c' ? 3 : it == 'd' ? 4 : it == 'e' ? 5 : 0
// custom value to use in sum
} == 15
assert ['a', 'b', 'c', 'd', 'e'].sum { ((char) it) - ((char) 'a') } == 10
assert ['a', 'b', 'c', 'd', 'e'].sum() == 'abcde'
assert [['a', 'b'], ['c', 'd']].sum() == ['a', 'b', 'c', 'd']
// an initial value can be provided
assert [].sum(1000) == 1000
assert [1, 2, 3].sum(1000) == 1006
assert [1, 2, 3].join('-') == '1-2-3' // String joining
assert [1, 2, 3].inject('counting: ') {
str, item -> str + item // reduce operation
} == 'counting: 123'
assert [1, 2, 3].inject(0) { count, item ->
count + item
} == 6
// end::list_gdk1[]
// tag::list_gdk2[]
def list = [9, 4, 2, 10, 5]
assert list.max() == 10
assert list.min() == 2
// we can also compare single characters, as anything comparable
assert ['x', 'y', 'a', 'z'].min() == 'a'
// we can use a closure to specify the sorting behaviour
def list2 = ['abc', 'z', 'xyzuvw', 'Hello', '321']
assert list2.max { it.size() } == 'xyzuvw'
assert list2.min { it.size() } == 'z'
// end::list_gdk2[]
assertScript '''
// tag::list_gdk3[]
assert ['a','b','c','b','b'] - 'c' == ['a','b','b','b']
assert ['a','b','c','b','b'] - 'b' == ['a','c']
assert ['a','b','c','b','b'] - ['b','c'] == ['a']
def list = [1,2,3,4,3,2,1]
list -= 3 // creates a new list by removing `3` from the original one
assert list == [1,2,4,2,1]
assert ( list -= [2,4] ) == [1,1]
// end::list_gdk3[]
'''
assertScript '''
// tag::list_gdk_remove_index[]
def list = ['a','b','c','d','e','f','b','b','a']
assert list.remove(2) == 'c' // remove the third element, and return it
assert list == ['a','b','d','e','f','b','b','a']
// end::list_gdk_remove_index[]
'''
assertScript '''
// tag::list_gdk4[]
def list = [1,2,3,4,5,6,2,2,1]
assert list.remove(2) == 3 // this removes the element at index 2, and returns it
assert list == [1,2,4,5,6,2,2,1]
assert list.removeElement(2) // remove first 2 and return true
assert list == [1,4,5,6,2,2,1]
assert ! list.removeElement(8) // return false because 8 is not in the list
assert list == [1,4,5,6,2,2,1]
assert list.removeAt(1) == 4 // remove element at index 1, and return it
assert list == [1,5,6,2,2,1]
// end::list_gdk4[]
'''
assertScript '''
// tag::list_gdk5[]
def list= ['a','b','c','b','b']
assert list.remove('c') // remove 'c', and return true because element removed
assert list.remove('b') // remove first 'b', and return true because element removed
assert ! list.remove('z') // return false because no elements removed
assert list == ['a','b','b']
// end::list_gdk5[]
'''
assertScript '''
// tag::list_gdk6[]
def list= ['a',2,'c',4]
list.clear()
assert list == []
// end::list_gdk6[]
'''
assertScript '''
// tag::list_gdk7[]
assert 'a' in ['a','b','c'] // returns true if an element belongs to the list
assert ['a','b','c'].contains('a') // equivalent to the `contains` method in Java
assert [1,3,4].containsAll([1,4]) // `containsAll` will check that all elements are found
assert [1,2,3,3,3,3,4,5].count(3) == 4 // count the number of elements which have some value
assert [1,2,3,3,3,3,4,5].count {
it%2==0 // count the number of elements which match the predicate
} == 2
assert [1,2,4,6,8,10,12].intersect([1,3,6,9,12]) == [1,6,12]
assert [1,2,3].disjoint( [4,6,9] )
assert ![1,2,3].disjoint( [2,4,6] )
// end::list_gdk7[]
'''
}
@Test
void testListComparator() {
// tag::list_comparator[]
Comparator mc = { a, b -> a == b ? 0 : (a < b ? -1 : 1) }
def list = [7, 4, 9, -6, -1, 11, 2, 3, -9, 5, -13]
assert list.max(mc) == 11
assert list.min(mc) == -13
Comparator mc2 = { a, b -> a == b ? 0 : (Math.abs(a) < Math.abs(b)) ? -1 : 1 }
assert list.max(mc2) == -13
assert list.min(mc2) == -1
assert list.max { a, b -> a.equals(b) ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == -13
assert list.min { a, b -> a.equals(b) ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == -1
// end::list_comparator[]
}
@Test
void testListSort() {
// tag::list_sort[]
assert [6, 3, 9, 2, 7, 1, 5].sort() == [1, 2, 3, 5, 6, 7, 9]
def list = ['abc', 'z', 'xyzuvw', 'Hello', '321']
assert list.sort {
it.size()
} == ['z', 'abc', '321', 'Hello', 'xyzuvw']
def list2 = [7, 4, -6, -1, 11, 2, 3, -9, 5, -13]
assert list2.sort { a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } ==
[-1, 2, 3, 4, 5, -6, 7, -9, 11, -13]
Comparator mc = { a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 }
// JDK 8+ only
// list2.sort(mc)
// assert list2 == [-1, 2, 3, 4, 5, -6, 7, -9, 11, -13]
def list3 = [6, -3, 9, 2, -7, 1, 5]
Collections.sort(list3)
assert list3 == [-7, -3, 1, 2, 5, 6, 9]
Collections.sort(list3, mc)
assert list3 == [1, 2, -3, 5, 6, -7, 9]
// end::list_sort[]
}
@Test
void testListMultiply() {
// tag::list_multiply[]
assert [1, 2, 3] * 3 == [1, 2, 3, 1, 2, 3, 1, 2, 3]
assert [1, 2, 3].multiply(2) == [1, 2, 3, 1, 2, 3]
assert Collections.nCopies(3, 'b') == ['b', 'b', 'b']
// nCopies from the JDK has different semantics than multiply for lists
assert Collections.nCopies(2, [1, 2]) == [[1, 2], [1, 2]] //not [1,2,1,2]
// end::list_multiply[]
}
@Test
void testIntRange() {
// tag::intrange[]
// an inclusive range
def range = 5..8
assert range.size() == 4
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert range.contains(8)
// lets use a half-open range
range = 5..<8
assert range.size() == 3
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert !range.contains(8)
//get the end points of the range without using indexes
range = 1..10
assert range.from == 1
assert range.to == 10
// end::intrange[]
}
@Test
void testStringRange() {
// tag::stringrange[]
// an inclusive range
def range = 'a'..'d'
assert range.size() == 4
assert range.get(2) == 'c'
assert range[2] == 'c'
assert range instanceof java.util.List
assert range.contains('a')
assert range.contains('d')
assert !range.contains('e')
// end::stringrange[]
}
@Test
void testRangeIteration() {
// tag::range_for[]
for (i in 1..10) {
println "Hello ${i}"
}
// end::range_for[]
// tag::range_each[]
(1..10).each { i ->
println "Hello ${i}"
}
// end::range_each[]
}
@Test
void testRangeInSwitch() {
int years = 1
double interestRate
// tag::range_switch[]
switch (years) {
case 1..10: interestRate = 0.076; break;
case 11..25: interestRate = 0.052; break;
default: interestRate = 0.037;
}
// end::range_switch[]
assert interestRate == 0.076d
}
@Test
void testMapLiteral() {
// tag::map_literal[]
def map = [name: 'Gromit', likes: 'cheese', id: 1234]
assert map.get('name') == 'Gromit'
assert map.get('id') == 1234
assert map['name'] == 'Gromit'
assert map['id'] == 1234
assert map instanceof java.util.Map
def emptyMap = [:]
assert emptyMap.size() == 0
emptyMap.put("foo", 5)
assert emptyMap.size() == 1
assert emptyMap.get("foo") == 5
// end::map_literal[]
}
@Test
void testMapLiteralUsingEscape() {
// tag::map_literal_gotcha[]
def a = 'Bob'
def ages = [a: 43]
assert ages['Bob'] == null // `Bob` is not found
assert ages['a'] == 43 // because `a` is a literal!
ages = [(a): 43] // now we escape `a` by using parenthesis
assert ages['Bob'] == 43 // and the value is found!
// end::map_literal_gotcha[]
}
@Test
void testMapPropertyNotation() {
// tag::map_property[]
def map = [name: 'Gromit', likes: 'cheese', id: 1234]
assert map.name == 'Gromit' // can be used instead of map.get('name')
assert map.id == 1234
def emptyMap = [:]
assert emptyMap.size() == 0
emptyMap.foo = 5
assert emptyMap.size() == 1
assert emptyMap.foo == 5
// end::map_property[]
}
@Test
void testMapPropertyGotcha() {
// tag::map_property_gotcha[]
def map = [name: 'Gromit', likes: 'cheese', id: 1234]
assert map.class == null
assert map.get('class') == null
assert map.getClass() == LinkedHashMap // this is probably what you want
map = [1 : 'a',
(true) : 'p',
(false): 'q',
(null) : 'x',
'null' : 'z']
assert map.containsKey(1) // 1 is not an identifier so used as is
assert map.true == null
assert map.false == null
assert map.get(true) == 'p'
assert map.get(false) == 'q'
assert map.null == 'z'
assert map.get(null) == 'x'
// end::map_property_gotcha[]
}
@Test
void testMapIteration() {
// tag::map_iteration[]
def map = [
Bob : 42,
Alice: 54,
Max : 33
]
// `entry` is a map entry
map.each { entry ->
println "Name: $entry.key Age: $entry.value"
}
// `entry` is a map entry, `i` the index in the map
map.eachWithIndex { entry, i ->
println "$i - Name: $entry.key Age: $entry.value"
}
// Alternatively you can use key and value directly
map.each { key, value ->
println "Name: $key Age: $value"
}
// Key, value and i as the index in the map
map.eachWithIndex { key, value, i ->
println "$i - Name: $key Age: $value"
}
// end::map_iteration[]
}
@Test
void testAddElementsToMap() {
// tag::map_add[]
def defaults = [1: 'a', 2: 'b', 3: 'c', 4: 'd']
def overrides = [2: 'z', 5: 'x', 13: 'x']
def result = new LinkedHashMap(defaults)
result.put(15, 't')
result[17] = 'u'
result.putAll(overrides)
assert result == [1: 'a', 2: 'z', 3: 'c', 4: 'd', 5: 'x', 13: 'x', 15: 't', 17: 'u']
// end::map_add[]
}
@Test
void testMapGDK() {
assertScript '''
// tag::map_gdk1[]
def m = [1:'a', 2:'b']
assert m.get(1) == 'a'
m.clear()
assert m == [:]
// end::map_gdk1[]
'''
assertScript '''
// tag::map_views[]
def map = [1:'a', 2:'b', 3:'c']
def entries = map.entrySet()
entries.each { entry ->
assert entry.key in [1,2,3]
assert entry.value in ['a','b','c']
}
def keys = map.keySet()
assert keys == [1,2,3] as Set
// end::map_views[]
'''
assertScript '''
// tag::map_gdk2[]
def people = [
1: [name:'Bob', age: 32, gender: 'M'],
2: [name:'Johnny', age: 36, gender: 'M'],
3: [name:'Claire', age: 21, gender: 'F'],
4: [name:'Amy', age: 54, gender:'F']
]
def bob = people.find { it.value.name == 'Bob' } // find a single entry
def females = people.findAll { it.value.gender == 'F' }
// both return entries, but you can use collect to retrieve the ages for example
def ageOfBob = bob.value.age
def agesOfFemales = females.collect {
it.value.age
}
assert ageOfBob == 32
assert agesOfFemales == [21,54]
// but you could also use a key/pair value as the parameters of the closures
def agesOfMales = people.findAll { id, person ->
person.gender == 'M'
}.collect { id, person ->
person.age
}
assert agesOfMales == [32, 36]
// `every` returns true if all entries match the predicate
assert people.every { id, person ->
person.age > 18
}
// `any` returns true if any entry matches the predicate
assert people.any { id, person ->
person.age == 54
}
// end::map_gdk2[]
'''
assertScript '''
// tag::map_gdk3[]
assert ['a', 7, 'b', [2, 3]].groupBy {
it.class
} == [(String) : ['a', 'b'],
(Integer) : [7],
(ArrayList): [[2, 3]]
]
assert [
[name: 'Clark', city: 'London'], [name: 'Sharma', city: 'London'],
[name: 'Maradona', city: 'LA'], [name: 'Zhang', city: 'HK'],
[name: 'Ali', city: 'HK'], [name: 'Liu', city: 'HK'],
].groupBy { it.city } == [
London: [[name: 'Clark', city: 'London'],
[name: 'Sharma', city: 'London']],
LA : [[name: 'Maradona', city: 'LA']],
HK : [[name: 'Zhang', city: 'HK'],
[name: 'Ali', city: 'HK'],
[name: 'Liu', city: 'HK']],
]
// end::map_gdk3[]
'''
}
@Test
void testShouldNotUseGStringAsKey() {
assertScript '''
// tag::gstring_gotcha[]
def key = 'some key'
def map = [:]
def gstringKey = "${key.toUpperCase()}"
map.put(gstringKey,'value')
assert map.get('SOME KEY') == null
// end::gstring_gotcha[]
'''
}
@Test
void testMapConstruct() {
// tag::map_construct[]
def map = [
simple : 123,
complex: [a: 1, b: 2]
]
def map2 = map.clone()
assert map2.get('simple') == map.get('simple')
assert map2.get('complex') == map.get('complex')
map2.get('complex').put('c', 3)
assert map.get('complex').get('c') == 3
// end::map_construct[]
}
@Test
void testGPathSupport() {
// tag::gpath_support_1[]
def listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22]]
assert listOfMaps.a == [11, 21] //GPath notation
assert listOfMaps*.a == [11, 21] //spread dot notation
listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22], null]
assert listOfMaps*.a == [11, 21, null] // caters for null values
assert listOfMaps*.a == listOfMaps.collect { it?.a } //equivalent notation
// end::gpath_support_1[]
// tag::gpath_support_2[]
// But this will only collect non-null values
assert listOfMaps.a == [11,21]
// end::gpath_support_2[]
// tag::gpath_support_3[]
assert [ 'z': 900,
*: ['a': 100, 'b': 200], 'a': 300] == ['a': 300, 'b': 200, 'z': 900]
//spread map notation in map definition
assert [*: [3: 3, *: [5: 5]], 7: 7] == [3: 3, 5: 5, 7: 7]
def f = { [1: 'u', 2: 'v', 3: 'w'] }
assert [*: f(), 10: 'zz'] == [1: 'u', 10: 'zz', 2: 'v', 3: 'w']
//spread map notation in function arguments
f = { map -> map.c }
assert f(*: ['a': 10, 'b': 20, 'c': 30], 'e': 50) == 30
f = { m, i, j, k -> [m, i, j, k] }
//using spread map notation with mixed unnamed and named arguments
assert f('e': 100, *[4, 5], *: ['a': 10, 'b': 20, 'c': 30], 6) ==
[["e": 100, "b": 20, "c": 30, "a": 10], 4, 5, 6]
// end::gpath_support_3[]
}
@Test
void testStarDot() {
// tag::stardot_1[]
assert [1, 3, 5] == ['a', 'few', 'words']*.size()
// end::stardot_1[]
assertScript '''
// tag::stardot_2[]
class Person {
String name
int age
}
def persons = [new Person(name:'Hugo', age:17), new Person(name:'Sandra',age:19)]
assert [17, 19] == persons*.age
// end::stardot_2[]
'''
}
@Test
void testSubscriptOperator() {
// tag::subscript[]
def text = 'nice cheese gromit!'
def x = text[2]
assert x == 'c'
assert x.class == String
def sub = text[5..10]
assert sub == 'cheese'
def list = [10, 11, 12, 13]
def answer = list[2,3]
assert answer == [12,13]
// end::subscript[]
// tag::subscript_2[]
list = 100..200
sub = list[1, 3, 20..25, 33]
assert sub == [101, 103, 120, 121, 122, 123, 124, 125, 133]
// end::subscript_2[]
// tag::subscript_3[]
list = ['a','x','x','d']
list[1..2] = ['b','c']
assert list == ['a','b','c','d']
// end::subscript_3[]
// tag::subscript_4[]
text = "nice cheese gromit!"
x = text[-1]
assert x == "!"
// end::subscript_4[]
// tag::subscript_4a[]
def name = text[-7..-2]
assert name == "gromit"
// end::subscript_4a[]
// tag::subscript_5[]
text = "nice cheese gromit!"
name = text[3..1]
assert name == "eci"
// end::subscript_5[]
}
}