/*
 * 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
 *
 *      https://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.apache.commons.collections4;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.functors.EqualPredicate;
import org.apache.commons.collections4.multiset.HashMultiSet;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * Tests for IterableUtils.
 */
class IterableUtilsTest {

    private static final Predicate<Number> EQUALS_TWO = input -> input.intValue() == 2;

    private static final Predicate<Number> EVEN = input -> input.intValue() % 2 == 0;

    /**
     * Iterable of {@link Integer}s
     */
    private Iterable<Integer> iterableA;

    /**
     * Iterable of {@link Long}s
     */
    private Iterable<Long> iterableB;

    /**
     * An empty Iterable.
     */
    private Iterable<Integer> emptyIterable;

    public void firstFromIterable() throws Exception {
        // Collection, entry exists
        final Bag<String> bag = new HashBag<>();
        bag.add("element", 1);
        assertEquals("element", IterableUtils.first(bag));
    }

    public void getFromIterable() throws Exception {
        // Collection, entry exists
        final Bag<String> bag = new HashBag<>();
        bag.add("element", 1);
        assertEquals("element", IterableUtils.get(bag, 0));
    }

    @BeforeEach
    public void setUp() {
        final Collection<Integer> collectionA = new ArrayList<>();
        collectionA.add(1);
        collectionA.add(2);
        collectionA.add(2);
        collectionA.add(3);
        collectionA.add(3);
        collectionA.add(3);
        collectionA.add(4);
        collectionA.add(4);
        collectionA.add(4);
        collectionA.add(4);
        iterableA = collectionA;

        final Collection<Long> collectionB = new LinkedList<>();
        collectionB.add(5L);
        collectionB.add(4L);
        collectionB.add(4L);
        collectionB.add(3L);
        collectionB.add(3L);
        collectionB.add(3L);
        collectionB.add(2L);
        collectionB.add(2L);
        collectionB.add(2L);
        collectionB.add(2L);
        iterableB = collectionB;

        emptyIterable = Collections.emptyList();
    }

    @Test
    void testContainsWithEquator() {
        final List<String> base = new ArrayList<>();
        base.add("AC");
        base.add("BB");
        base.add("CA");

        final Equator<String> secondLetterEquator = new Equator<String>() {

            @Override
            public boolean equate(final String o1, final String o2) {
                return o1.charAt(1) == o2.charAt(1);
            }

            @Override
            public int hash(final String o) {
                return o.charAt(1);
            }

        };

        assertFalse(base.contains("CC"));
        assertTrue(IterableUtils.contains(base, "AC", secondLetterEquator));
        assertTrue(IterableUtils.contains(base, "CC", secondLetterEquator));
        assertFalse(IterableUtils.contains(base, "CX", secondLetterEquator));
        assertFalse(IterableUtils.contains(null, null, secondLetterEquator));

        assertThrows(NullPointerException.class, () -> IterableUtils.contains(base, "AC", null), "expecting NullPointerException");
    }

    @Test
    void testCountMatches() {
        assertEquals(4, IterableUtils.countMatches(iterableB, EQUALS_TWO));
        assertEquals(0, IterableUtils.countMatches(null, EQUALS_TWO));
        assertThrows(NullPointerException.class, () -> assertEquals(0, IterableUtils.countMatches(iterableA, null)), "predicate must not be null");
        assertThrows(NullPointerException.class, () -> assertEquals(0, IterableUtils.countMatches(null, null)), "predicate must not be null");
    }

    @Test
    void testDuplicateListAllSameInList() {
        final List<Integer> input = Arrays.asList(5, 5, 5, 5);
        assertEquals(Arrays.asList(5), IterableUtils.duplicateList(input));
    }

    @Test
    void testDuplicateListEmptyDeque() {
        assertTrue(IterableUtils.duplicateList(new ArrayDeque<>()).isEmpty());
    }

    @Test
    void testDuplicateListEmptyList() {
        final List<Integer> input = Arrays.asList();
        assertTrue(IterableUtils.duplicateList(input).isEmpty());
    }

    @Test
    void testDuplicateListEmptySet() {
        assertTrue(IterableUtils.duplicateList(new HashSet<>()).isEmpty());
    }

    @Test
    void testDuplicateListMultipleDuplicatesInDeque() {
        final Deque<Integer> input = new ArrayDeque<>(Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4));
        final List<Integer> expected = Arrays.asList(1, 2, 3, 4);
        assertEquals(expected, IterableUtils.duplicateList(input));
    }

    @Test
    void testDuplicateListMultipleDuplicatesInDequeReverse() {
        // We want to make sure that the actual list is in the expected order
        final Deque<Integer> input = new ArrayDeque<>(Arrays.asList(4, 4, 3, 3, 2, 2, 1, 1));
        final List<Integer> expected = Arrays.asList(4, 3, 2, 1);
        assertEquals(expected, IterableUtils.duplicateList(input));
    }

    @Test
    void testDuplicateListMultipleDuplicatesInList() {
        final List<Integer> input = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4);
        final List<Integer> expected = Arrays.asList(1, 2, 3, 4);
        assertEquals(expected, IterableUtils.duplicateList(input));
    }

    @Test
    void testDuplicateListMultipleDuplicatesInListReverse() {
        // We want to make sure that the actual list is in the expected order
        final List<Integer> input = Arrays.asList(4, 4, 3, 3, 2, 2, 1, 1);
        final List<Integer> expected = Arrays.asList(4, 3, 2, 1);
        assertEquals(expected, IterableUtils.duplicateList(input));
    }

    @Test
    void testDuplicateListNoDuplicates() {
        final List<Integer> input = Arrays.asList(1, 2, 3, 4, 5);
        assertTrue(IterableUtils.duplicateList(input).isEmpty());
    }

    @Test
    void testDuplicateListSingleElement() {
        final List<Integer> input = Arrays.asList(1);
        assertTrue(IterableUtils.duplicateList(input).isEmpty());
    }

    @Test
    void testDuplicateListWithDuplicates() {
        final List<Integer> input = Arrays.asList(1, 2, 3, 2, 4, 5, 3);
        final List<Integer> expected = Arrays.asList(2, 3);
        assertEquals(expected, IterableUtils.duplicateList(input));
    }

    @Test
    void testDuplicateSequencedSetMultipleDuplicates() {
        final List<Integer> input = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4);
        final List<Integer> list = Arrays.asList(1, 2, 3, 4);
        assertEquals(list, new ArrayList<>(IterableUtils.duplicateSequencedSet(input)));
        assertEquals(new LinkedHashSet<>(list), IterableUtils.duplicateSequencedSet(input));
    }

    @Test
    void testDuplicateSetEmptyDeque() {
        assertTrue(IterableUtils.duplicateSet(new ArrayDeque<>()).isEmpty());
    }

    @Test
    void testDuplicateSetEmptyList() {
        final List<Integer> input = Arrays.asList();
        assertTrue(IterableUtils.duplicateSet(input).isEmpty());
    }

    @Test
    void testDuplicateSetEmptySet() {
        assertTrue(IterableUtils.duplicateSet(new HashSet<>()).isEmpty());
    }

    @Test
    void testDuplicateSetInSet() {
        // Sets don't have duplicates, so the result is always an empty set.
        final Set<Integer> input = new HashSet<>(Arrays.asList(5));
        assertTrue(IterableUtils.duplicateSet(input).isEmpty());
    }

    @Test
    void testDuplicateSetMultipleDuplicatesInDeque() {
        final Deque<Integer> input = new ArrayDeque<>(Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4));
        final Set<Integer> expected = new HashSet<>(Arrays.asList(1, 2, 3, 4));
        assertEquals(expected, IterableUtils.duplicateSet(input));
    }

    @Test
    void testDuplicateSetMultipleDuplicatesInList() {
        final List<Integer> input = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4);
        final Set<Integer> expected = new HashSet<>(Arrays.asList(1, 2, 3, 4));
        assertEquals(expected, IterableUtils.duplicateSet(input));
    }

    @Test
    void testDuplicateSetNoDuplicates() {
        final List<Integer> input = Arrays.asList(1, 2, 3, 4, 5);
        assertTrue(IterableUtils.duplicateSet(input).isEmpty());
    }

    @Test
    void testDuplicateSetSingleElement() {
        final List<Integer> input = Arrays.asList(1);
        assertTrue(IterableUtils.duplicateSet(input).isEmpty());
    }

    @Test
    void testDuplicateSetWithDuplicates() {
        final List<Integer> input = Arrays.asList(1, 2, 3, 2, 4, 5, 3);
        final Set<Integer> expected = new HashSet<>(Arrays.asList(2, 3));
        assertEquals(expected, IterableUtils.duplicateSet(input));
    }

    @Test
    void testDuplicatListAllSameInDeque() {
        final Deque<Integer> input = new ArrayDeque<>(Arrays.asList(5, 5, 5, 5));
        assertEquals(Arrays.asList(5), IterableUtils.duplicateList(input));
    }

    @Test
    void testDuplicatSetAllSameInDeque() {
        final Deque<Integer> input = new ArrayDeque<>(Arrays.asList(5, 5, 5, 5));
        assertEquals(new HashSet<>(Arrays.asList(5)), IterableUtils.duplicateSet(input));
    }

    @Test
    void testFind() {
        Predicate<Number> testPredicate = EqualPredicate.equalPredicate(4);
        Integer test = IterableUtils.find(iterableA, testPredicate);
        assertEquals(4, (int) test);
        testPredicate = EqualPredicate.equalPredicate(45);
        test = IterableUtils.find(iterableA, testPredicate);
        assertNull(test);
        assertNull(IterableUtils.find(null, testPredicate));

        assertThrows(NullPointerException.class, () -> IterableUtils.find(iterableA, null), "expecting NullPointerException");
    }

    @Test
    void testFirstFromIterableIndexOutOfBoundsException() throws Exception {
        // Collection, entry exists
        final Bag<String> bag = new HashBag<>();
        // Collection, non-existent entry
        assertThrows(IndexOutOfBoundsException.class, () -> IterableUtils.first(bag));
    }

    @Test
    void testForEach() {
        final List<Integer> listA = new ArrayList<>();
        listA.add(1);

        final List<Integer> listB = new ArrayList<>();
        listB.add(2);

        final Closure<List<Integer>> testClosure = ClosureUtils.invokerClosure("clear");
        final Collection<List<Integer>> col = new ArrayList<>();
        col.add(listA);
        col.add(listB);
        IterableUtils.forEach(col, testClosure);
        assertTrue(listA.isEmpty() && listB.isEmpty());

        assertThrows(NullPointerException.class, () -> IterableUtils.forEach(col, null), "expecting NullPointerException");

        IterableUtils.forEach(null, testClosure);

        // null should be OK
        col.add(null);
        IterableUtils.forEach(col, testClosure);
    }

    @Test
    void testForEachButLast() {
        final List<Integer> listA = new ArrayList<>();
        listA.add(1);

        final List<Integer> listB = new ArrayList<>();
        listB.add(2);

        final Closure<List<Integer>> testClosure = ClosureUtils.invokerClosure("clear");
        final Collection<List<Integer>> col = new ArrayList<>();
        col.add(listA);
        col.add(listB);
        List<Integer> last = IterableUtils.forEachButLast(col, testClosure);
        assertTrue(listA.isEmpty() && !listB.isEmpty());
        assertSame(listB, last);

        assertThrows(NullPointerException.class, () -> IterableUtils.forEachButLast(col, null), "expecting NullPointerException");

        IterableUtils.forEachButLast(null, testClosure);

        // null should be OK
        col.add(null);
        col.add(null);
        last = IterableUtils.forEachButLast(col, testClosure);
        assertNull(last);
    }

    @Test
    void testForEachFailure() {
        final Closure<String> testClosure = ClosureUtils.invokerClosure("clear");
        final Collection<String> col = new ArrayList<>();
        col.add("x");
        assertThrows(FunctorException.class, () -> IterableUtils.forEach(col, testClosure));
    }

    @Test
    void testFrequency() {
        // null iterable test
        assertEquals(0, IterableUtils.frequency(null, 1));

        assertEquals(1, IterableUtils.frequency(iterableA, 1));
        assertEquals(2, IterableUtils.frequency(iterableA, 2));
        assertEquals(3, IterableUtils.frequency(iterableA, 3));
        assertEquals(4, IterableUtils.frequency(iterableA, 4));
        assertEquals(0, IterableUtils.frequency(iterableA, 5));

        assertEquals(0, IterableUtils.frequency(iterableB, 1L));
        assertEquals(4, IterableUtils.frequency(iterableB, 2L));
        assertEquals(3, IterableUtils.frequency(iterableB, 3L));
        assertEquals(2, IterableUtils.frequency(iterableB, 4L));
        assertEquals(1, IterableUtils.frequency(iterableB, 5L));

        // Ensure that generic bounds accept valid parameters, but return
        // expected results
        // for example no longs in the "int" Iterable<Number>, and vice versa.
        final Iterable<Number> iterableIntAsNumber = Arrays.<Number>asList(1, 2, 3, 4, 5);
        final Iterable<Number> iterableLongAsNumber = Arrays.<Number>asList(1L, 2L, 3L, 4L, 5L);
        assertEquals(0, IterableUtils.frequency(iterableIntAsNumber, 2L));
        assertEquals(0, IterableUtils.frequency(iterableLongAsNumber, 2));

        final Set<String> set = new HashSet<>();
        set.add("A");
        set.add("C");
        set.add("E");
        set.add("E");
        assertEquals(1, IterableUtils.frequency(set, "A"));
        assertEquals(0, IterableUtils.frequency(set, "B"));
        assertEquals(1, IterableUtils.frequency(set, "C"));
        assertEquals(0, IterableUtils.frequency(set, "D"));
        assertEquals(1, IterableUtils.frequency(set, "E"));

        final Bag<String> bag = new HashBag<>();
        bag.add("A", 3);
        bag.add("C");
        bag.add("E");
        bag.add("E");
        assertEquals(3, IterableUtils.frequency(bag, "A"));
        assertEquals(0, IterableUtils.frequency(bag, "B"));
        assertEquals(1, IterableUtils.frequency(bag, "C"));
        assertEquals(0, IterableUtils.frequency(bag, "D"));
        assertEquals(2, IterableUtils.frequency(bag, "E"));

        final MultiSet<String> multiSet = new HashMultiSet<>();
        multiSet.add("A", 3);
        multiSet.add("C");
        multiSet.add("E");
        multiSet.add("E");
        assertEquals(3, IterableUtils.frequency(multiSet, "A"));
        assertEquals(0, IterableUtils.frequency(multiSet, "B"));
        assertEquals(1, IterableUtils.frequency(multiSet, "C"));
        assertEquals(0, IterableUtils.frequency(multiSet, "D"));
        assertEquals(2, IterableUtils.frequency(multiSet, "E"));
    }

    @Test
    void testFrequencyOfNull() {
        final List<String> list = new ArrayList<>();
        assertEquals(0, IterableUtils.frequency(list, null));
        list.add("A");
        assertEquals(0, IterableUtils.frequency(list, null));
        list.add(null);
        assertEquals(1, IterableUtils.frequency(list, null));
        list.add("B");
        assertEquals(1, IterableUtils.frequency(list, null));
        list.add(null);
        assertEquals(2, IterableUtils.frequency(list, null));
        list.add("B");
        assertEquals(2, IterableUtils.frequency(list, null));
        list.add(null);
        assertEquals(3, IterableUtils.frequency(list, null));
    }

    @Test
    void testGetFromIterableIndexOutOfBoundsException() throws Exception {
        // Collection, entry exists
        final Bag<String> bag = new HashBag<>();
        bag.add("element", 1);
        // Collection, non-existent entry
        assertThrows(IndexOutOfBoundsException.class, () -> IterableUtils.get(bag, 1));
    }

    @Test
    void testIndexOf() {
        Predicate<Number> testPredicate = EqualPredicate.equalPredicate((Number) 4);
        int index = IterableUtils.indexOf(iterableA, testPredicate);
        assertEquals(6, index);
        testPredicate = EqualPredicate.equalPredicate((Number) 45);
        index = IterableUtils.indexOf(iterableA, testPredicate);
        assertEquals(-1, index);
        assertEquals(-1, IterableUtils.indexOf(null, testPredicate));

        assertThrows(NullPointerException.class, () -> IterableUtils.indexOf(iterableA, null), "expecting NullPointerException");
    }

    @Test
    void testMatchesAll() {
        assertThrows(NullPointerException.class, () -> assertFalse(IterableUtils.matchesAll(null, null)), "predicate must not be null");

        assertThrows(NullPointerException.class, () -> assertFalse(IterableUtils.matchesAll(iterableA, null)), "predicate must not be null");

        final Predicate<Integer> lessThanFive = object -> object < 5;
        assertTrue(IterableUtils.matchesAll(iterableA, lessThanFive));

        final Predicate<Integer> lessThanFour = object -> object < 4;
        assertFalse(IterableUtils.matchesAll(iterableA, lessThanFour));

        assertTrue(IterableUtils.matchesAll(null, lessThanFour));
        assertTrue(IterableUtils.matchesAll(emptyIterable, lessThanFour));
    }

    @Test
    void testMatchesAny() {
        final List<Integer> list = new ArrayList<>();

        assertThrows(NullPointerException.class, () -> assertFalse(IterableUtils.matchesAny(null, null)), "predicate must not be null");

        assertThrows(NullPointerException.class, () -> assertFalse(IterableUtils.matchesAny(list, null)), "predicate must not be null");

        assertFalse(IterableUtils.matchesAny(null, EQUALS_TWO));
        assertFalse(IterableUtils.matchesAny(list, EQUALS_TWO));
        list.add(1);
        list.add(3);
        list.add(4);
        assertFalse(IterableUtils.matchesAny(list, EQUALS_TWO));

        list.add(2);
        assertTrue(IterableUtils.matchesAny(list, EQUALS_TWO));
    }

    @SuppressWarnings("unchecked")
    @Test
    void testPartition() {
        final List<Integer> input = new ArrayList<>();
        input.add(1);
        input.add(2);
        input.add(3);
        input.add(4);
        List<List<Integer>> partitions = IterableUtils.partition(input, EQUALS_TWO);
        assertEquals(2, partitions.size());

        // first partition contains 2
        Collection<Integer> partition = partitions.get(0);
        assertEquals(1, partition.size());
        assertEquals(2, CollectionUtils.extractSingleton(partition).intValue());

        // second partition contains 1, 3, and 4
        final Integer[] expected = { 1, 3, 4 };
        partition = partitions.get(1);
        assertArrayEquals(expected, partition.toArray());

        partitions = IterableUtils.partition((List<Integer>) null, EQUALS_TWO);
        assertEquals(2, partitions.size());
        assertTrue(partitions.get(0).isEmpty());
        assertTrue(partitions.get(1).isEmpty());

        partitions = IterableUtils.partition(input);
        assertEquals(1, partitions.size());
        assertEquals(input, partitions.get(0));

        assertThrows(NullPointerException.class, () -> IterableUtils.partition(input, (Predicate<Integer>) null), "expecting NullPointerException");
    }

    @SuppressWarnings("unchecked")
    @Test
    void testPartitionMultiplePredicates() {
        final List<Integer> input = new ArrayList<>();
        input.add(1);
        input.add(2);
        input.add(3);
        input.add(4);
        final List<List<Integer>> partitions = IterableUtils.partition(input, EQUALS_TWO, EVEN);

        // first partition contains 2
        Collection<Integer> partition = partitions.get(0);
        assertEquals(1, partition.size());
        assertEquals(2, partition.iterator().next().intValue());

        // second partition contains 4
        partition = partitions.get(1);
        assertEquals(1, partition.size());
        assertEquals(4, partition.iterator().next().intValue());

        // third partition contains 1 and 3
        final Integer[] expected = { 1, 3 };
        partition = partitions.get(2);
        assertArrayEquals(expected, partition.toArray());

        assertThrows(NullPointerException.class, () -> IterableUtils.partition(input, EQUALS_TWO, null));
    }

    @Test
    void testSize() {
        assertEquals(0, IterableUtils.size(null));
    }

    @Test
    void testToString() {
        String result = IterableUtils.toString(iterableA);
        assertEquals("[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]", result);

        result = IterableUtils.toString(new ArrayList<>());
        assertEquals("[]", result);

        result = IterableUtils.toString(null);
        assertEquals("[]", result);

        result = IterableUtils.toString(iterableA, input -> Integer.toString(input * 2));
        assertEquals("[2, 4, 4, 6, 6, 6, 8, 8, 8, 8]", result);

        result = IterableUtils.toString(new ArrayList<>(), input -> {
            fail("not supposed to reach here");
            return StringUtils.EMPTY;
        });
        assertEquals("[]", result);

        result = IterableUtils.toString(null, input -> {
            fail("not supposed to reach here");
            return StringUtils.EMPTY;
        });
        assertEquals("[]", result);
    }

    @Test
    void testToStringDelimiter() {

        final Transformer<Integer, String> transformer = input -> Integer.toString(input * 2);

        String result = IterableUtils.toString(iterableA, transformer, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY);
        assertEquals("2446668888", result);

        result = IterableUtils.toString(iterableA, transformer, ",", StringUtils.EMPTY, StringUtils.EMPTY);
        assertEquals("2,4,4,6,6,6,8,8,8,8", result);

        result = IterableUtils.toString(iterableA, transformer, StringUtils.EMPTY, "[", "]");
        assertEquals("[2446668888]", result);

        result = IterableUtils.toString(iterableA, transformer, ",", "[", "]");
        assertEquals("[2,4,4,6,6,6,8,8,8,8]", result);

        result = IterableUtils.toString(iterableA, transformer, ",", "[[", "]]");
        assertEquals("[[2,4,4,6,6,6,8,8,8,8]]", result);

        result = IterableUtils.toString(iterableA, transformer, ",,", "[", "]");
        assertEquals("[2,,4,,4,,6,,6,,6,,8,,8,,8,,8]", result);

        result = IterableUtils.toString(iterableA, transformer, ",,", "((", "))");
        assertEquals("((2,,4,,4,,6,,6,,6,,8,,8,,8,,8))", result);

        result = IterableUtils.toString(new ArrayList<>(), transformer, StringUtils.EMPTY, "(", ")");
        assertEquals("()", result);

        result = IterableUtils.toString(new ArrayList<>(), transformer, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY);
        assertEquals(StringUtils.EMPTY, result);
    }

    @Test
    void testToStringWithNullArguments() {
        final String result = IterableUtils.toString(null, input -> {
            fail("not supposed to reach here");
            return StringUtils.EMPTY;
        }, StringUtils.EMPTY, "(", ")");
        assertEquals("()", result);
        assertThrows(NullPointerException.class, () -> IterableUtils.toString(new ArrayList<>(), null, StringUtils.EMPTY, "(", ")"));
        assertThrows(NullPointerException.class, () -> IterableUtils.toString(new ArrayList<>(), input -> {
            fail("not supposed to reach here");
            return StringUtils.EMPTY;
        }, null, "(", ")"));
        assertThrows(NullPointerException.class, () -> IterableUtils.toString(new ArrayList<>(), input -> {
            fail("not supposed to reach here");
            return StringUtils.EMPTY;
        }, StringUtils.EMPTY, null, ")"));
        assertThrows(NullPointerException.class, () -> IterableUtils.toString(new ArrayList<>(), input -> {
            fail("not supposed to reach here");
            return StringUtils.EMPTY;
        }, StringUtils.EMPTY, "(", null));
    }

}
