c7d15f1fe0
Reviewed-by: liach
546 lines
22 KiB
Java
546 lines
22 KiB
Java
/*
|
|
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.LinkedList;
|
|
import java.util.Stack;
|
|
import java.util.Vector;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
import org.testng.annotations.DataProvider;
|
|
import org.testng.annotations.Test;
|
|
|
|
import static org.testng.Assert.assertEquals;
|
|
import static org.testng.Assert.assertFalse;
|
|
import static org.testng.Assert.assertTrue;
|
|
import static org.testng.Assert.fail;
|
|
|
|
import java.util.ConcurrentModificationException;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Function;
|
|
import java.util.function.Predicate;
|
|
|
|
/**
|
|
* @test
|
|
* @summary Unit tests for extension methods on List
|
|
* @bug 8023367 8037106 8325679
|
|
* @library ../Collection/testlibrary
|
|
* @build CollectionAsserts CollectionSupplier ExtendsAbstractList
|
|
* @run testng ListDefaults
|
|
*/
|
|
public class ListDefaults {
|
|
|
|
// Suppliers of lists that can support structural modifications
|
|
private static final List<Function<Collection, List>> LIST_STRUCT_MOD_SUPPLIERS = Arrays.asList(
|
|
java.util.ArrayList::new,
|
|
java.util.LinkedList::new,
|
|
java.util.Vector::new,
|
|
java.util.concurrent.CopyOnWriteArrayList::new,
|
|
ExtendsAbstractList::new
|
|
);
|
|
|
|
// Suppliers of lists that can support in place modifications
|
|
private static final List<Function<Collection, List>> LIST_SUPPLIERS = Arrays.asList(
|
|
java.util.ArrayList::new,
|
|
java.util.LinkedList::new,
|
|
java.util.Vector::new,
|
|
java.util.concurrent.CopyOnWriteArrayList::new,
|
|
ExtendsAbstractList::new,
|
|
c -> Arrays.asList(c.toArray())
|
|
);
|
|
|
|
// Suppliers of lists supporting CMEs
|
|
private static final List<Function<Collection, List>> LIST_CME_SUPPLIERS = Arrays.asList(
|
|
java.util.ArrayList::new,
|
|
java.util.Vector::new
|
|
);
|
|
|
|
private static final Predicate<Integer> pEven = x -> 0 == x % 2;
|
|
private static final Predicate<Integer> pOdd = x -> 1 == x % 2;
|
|
|
|
private static final Comparator<Integer> BIT_COUNT_COMPARATOR =
|
|
(x, y) -> Integer.bitCount(x) - Integer.bitCount(y);
|
|
|
|
private static final Comparator<AtomicInteger> ATOMIC_INTEGER_COMPARATOR =
|
|
(x, y) -> x.intValue() - y.intValue();
|
|
|
|
private static final int SIZE = 100;
|
|
private static final int SUBLIST_FROM = 20;
|
|
private static final int SUBLIST_TO = SIZE - 5;
|
|
private static final int SUBLIST_SIZE = SUBLIST_TO - SUBLIST_FROM;
|
|
|
|
// call the callback for each recursive subList
|
|
private void trimmedSubList(final List<Integer> list, final Consumer<List<Integer>> callback) {
|
|
int size = list.size();
|
|
if (size > 1) {
|
|
// trim 1 element from both ends
|
|
final List<Integer> subList = list.subList(1, size - 1);
|
|
callback.accept(subList);
|
|
trimmedSubList(subList, callback);
|
|
}
|
|
}
|
|
|
|
@DataProvider(name="listProvider", parallel=true)
|
|
public static Object[][] listCases() {
|
|
final List<Object[]> cases = new LinkedList<>();
|
|
cases.add(new Object[] { Collections.emptyList() });
|
|
cases.add(new Object[] { new ArrayList<>() });
|
|
cases.add(new Object[] { new LinkedList<>() });
|
|
cases.add(new Object[] { new Vector<>() });
|
|
cases.add(new Object[] { new Stack<>() });
|
|
cases.add(new Object[] { new CopyOnWriteArrayList<>() });
|
|
cases.add(new Object[] { Arrays.asList() });
|
|
|
|
List<Integer> l = Arrays.asList(42);
|
|
cases.add(new Object[] { new ArrayList<>(l) });
|
|
cases.add(new Object[] { new LinkedList<>(l) });
|
|
cases.add(new Object[] { new Vector<>(l) });
|
|
Stack<Integer> s = new Stack<>(); s.addAll(l);
|
|
cases.add(new Object[]{s});
|
|
cases.add(new Object[] { new CopyOnWriteArrayList<>(l) });
|
|
cases.add(new Object[] { l });
|
|
return cases.toArray(new Object[0][cases.size()]);
|
|
}
|
|
|
|
@Test(dataProvider = "listProvider")
|
|
public void testProvidedWithNull(final List<Integer> list) {
|
|
try {
|
|
list.forEach(null);
|
|
fail("expected NPE not thrown");
|
|
} catch (NullPointerException npe) {}
|
|
try {
|
|
list.replaceAll(null);
|
|
fail("expected NPE not thrown");
|
|
} catch (NullPointerException npe) {}
|
|
try {
|
|
list.removeIf(null);
|
|
fail("expected NPE not thrown");
|
|
} catch (NullPointerException npe) {}
|
|
try {
|
|
list.sort(null);
|
|
} catch (Throwable t) {
|
|
fail("Exception not expected: " + t);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testForEach() {
|
|
@SuppressWarnings("unchecked")
|
|
final CollectionSupplier<List<Integer>> supplier = new CollectionSupplier(LIST_SUPPLIERS, SIZE);
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> original = test.expected;
|
|
final List<Integer> list = test.collection;
|
|
|
|
try {
|
|
list.forEach(null);
|
|
fail("expected NPE not thrown");
|
|
} catch (NullPointerException npe) {}
|
|
CollectionAsserts.assertContents(list, original);
|
|
|
|
final List<Integer> actual = new LinkedList<>();
|
|
list.forEach(actual::add);
|
|
CollectionAsserts.assertContents(actual, list);
|
|
CollectionAsserts.assertContents(actual, original);
|
|
|
|
if (original.size() > SUBLIST_SIZE) {
|
|
final List<Integer> subList = original.subList(SUBLIST_FROM, SUBLIST_TO);
|
|
final List<Integer> actualSubList = new LinkedList<>();
|
|
subList.forEach(actualSubList::add);
|
|
assertEquals(actualSubList.size(), SUBLIST_SIZE);
|
|
for (int i = 0; i < SUBLIST_SIZE; i++) {
|
|
assertEquals(actualSubList.get(i), original.get(i + SUBLIST_FROM));
|
|
}
|
|
}
|
|
|
|
trimmedSubList(list, l -> {
|
|
final List<Integer> a = new LinkedList<>();
|
|
l.forEach(a::add);
|
|
CollectionAsserts.assertContents(a, l);
|
|
});
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testRemoveIf() {
|
|
@SuppressWarnings("unchecked")
|
|
final CollectionSupplier<List<Integer>> supplier = new CollectionSupplier(LIST_STRUCT_MOD_SUPPLIERS, SIZE);
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> original = test.expected;
|
|
final List<Integer> list = test.collection;
|
|
|
|
try {
|
|
list.removeIf(null);
|
|
fail("expected NPE not thrown");
|
|
} catch (NullPointerException npe) {}
|
|
CollectionAsserts.assertContents(list, original);
|
|
|
|
final AtomicInteger offset = new AtomicInteger(1);
|
|
while (list.size() > 0) {
|
|
removeFirst(original, list, offset);
|
|
}
|
|
}
|
|
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> original = test.expected;
|
|
final List<Integer> list = test.collection;
|
|
list.removeIf(pOdd);
|
|
for (int i : list) {
|
|
assertTrue((i % 2) == 0);
|
|
}
|
|
for (int i : original) {
|
|
if (i % 2 == 0) {
|
|
assertTrue(list.contains(i));
|
|
}
|
|
}
|
|
list.removeIf(pEven);
|
|
assertTrue(list.isEmpty());
|
|
}
|
|
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> original = test.expected;
|
|
final List<Integer> list = test.collection;
|
|
final List<Integer> listCopy = new ArrayList<>(list);
|
|
if (original.size() > SUBLIST_SIZE) {
|
|
final List<Integer> subList = list.subList(SUBLIST_FROM, SUBLIST_TO);
|
|
final List<Integer> subListCopy = new ArrayList<>(subList);
|
|
listCopy.removeAll(subList);
|
|
subList.removeIf(pOdd);
|
|
for (int i : subList) {
|
|
assertTrue((i % 2) == 0);
|
|
}
|
|
for (int i : subListCopy) {
|
|
if (i % 2 == 0) {
|
|
assertTrue(subList.contains(i));
|
|
} else {
|
|
assertFalse(subList.contains(i));
|
|
}
|
|
}
|
|
subList.removeIf(pEven);
|
|
assertTrue(subList.isEmpty());
|
|
// elements outside the view should remain
|
|
CollectionAsserts.assertContents(list, listCopy);
|
|
}
|
|
}
|
|
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> list = test.collection;
|
|
trimmedSubList(list, l -> {
|
|
final List<Integer> copy = new ArrayList<>(l);
|
|
l.removeIf(pOdd);
|
|
for (int i : l) {
|
|
assertTrue((i % 2) == 0);
|
|
}
|
|
for (int i : copy) {
|
|
if (i % 2 == 0) {
|
|
assertTrue(l.contains(i));
|
|
} else {
|
|
assertFalse(l.contains(i));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// remove the first element
|
|
private void removeFirst(final List<Integer> original, final List<Integer> list, final AtomicInteger offset) {
|
|
final AtomicBoolean first = new AtomicBoolean(true);
|
|
list.removeIf(x -> first.getAndSet(false));
|
|
CollectionAsserts.assertContents(original.subList(offset.getAndIncrement(), original.size()), list);
|
|
}
|
|
|
|
@Test
|
|
public void testReplaceAll() {
|
|
final int scale = 3;
|
|
@SuppressWarnings("unchecked")
|
|
final CollectionSupplier<List<Integer>> supplier = new CollectionSupplier(LIST_SUPPLIERS, SIZE);
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> original = test.expected;
|
|
final List<Integer> list = test.collection;
|
|
|
|
try {
|
|
list.replaceAll(null);
|
|
fail("expected NPE not thrown");
|
|
} catch (NullPointerException npe) {}
|
|
CollectionAsserts.assertContents(list, original);
|
|
|
|
list.replaceAll(x -> scale * x);
|
|
for (int i = 0; i < original.size(); i++) {
|
|
assertTrue(list.get(i) == (scale * original.get(i)), "mismatch at index " + i);
|
|
}
|
|
|
|
if (original.size() > SUBLIST_SIZE) {
|
|
final List<Integer> subList = list.subList(SUBLIST_FROM, SUBLIST_TO);
|
|
subList.replaceAll(x -> x + 1);
|
|
// verify elements in view [from, to) were replaced
|
|
for (int i = 0; i < SUBLIST_SIZE; i++) {
|
|
assertTrue(subList.get(i) == ((scale * original.get(i + SUBLIST_FROM)) + 1),
|
|
"mismatch at sublist index " + i);
|
|
}
|
|
// verify that elements [0, from) remain unmodified
|
|
for (int i = 0; i < SUBLIST_FROM; i++) {
|
|
assertTrue(list.get(i) == (scale * original.get(i)),
|
|
"mismatch at original index " + i);
|
|
}
|
|
// verify that elements [to, size) remain unmodified
|
|
for (int i = SUBLIST_TO; i < list.size(); i++) {
|
|
assertTrue(list.get(i) == (scale * original.get(i)),
|
|
"mismatch at original index " + i);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> list = test.collection;
|
|
trimmedSubList(list, l -> {
|
|
final List<Integer> copy = new ArrayList<>(l);
|
|
final int offset = 5;
|
|
l.replaceAll(x -> offset + x);
|
|
for (int i = 0; i < copy.size(); i++) {
|
|
assertTrue(l.get(i) == (offset + copy.get(i)), "mismatch at index " + i);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testSort() {
|
|
@SuppressWarnings("unchecked")
|
|
final CollectionSupplier<List<Integer>> supplier = new CollectionSupplier(LIST_SUPPLIERS, SIZE);
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> original = test.expected;
|
|
final List<Integer> list = test.collection;
|
|
CollectionSupplier.shuffle(list);
|
|
list.sort(Integer::compare);
|
|
CollectionAsserts.assertSorted(list, Integer::compare);
|
|
if (test.name.startsWith("reverse")) {
|
|
Collections.reverse(list);
|
|
}
|
|
CollectionAsserts.assertContents(list, original);
|
|
|
|
CollectionSupplier.shuffle(list);
|
|
list.sort(null);
|
|
CollectionAsserts.assertSorted(list, Comparator.naturalOrder());
|
|
if (test.name.startsWith("reverse")) {
|
|
Collections.reverse(list);
|
|
}
|
|
CollectionAsserts.assertContents(list, original);
|
|
|
|
CollectionSupplier.shuffle(list);
|
|
list.sort(Comparator.naturalOrder());
|
|
CollectionAsserts.assertSorted(list, Comparator.naturalOrder());
|
|
if (test.name.startsWith("reverse")) {
|
|
Collections.reverse(list);
|
|
}
|
|
CollectionAsserts.assertContents(list, original);
|
|
|
|
CollectionSupplier.shuffle(list);
|
|
list.sort(Comparator.reverseOrder());
|
|
CollectionAsserts.assertSorted(list, Comparator.reverseOrder());
|
|
if (!test.name.startsWith("reverse")) {
|
|
Collections.reverse(list);
|
|
}
|
|
CollectionAsserts.assertContents(list, original);
|
|
|
|
CollectionSupplier.shuffle(list);
|
|
list.sort(BIT_COUNT_COMPARATOR);
|
|
CollectionAsserts.assertSorted(list, BIT_COUNT_COMPARATOR);
|
|
// check sort by verifying that bitCount increases and never drops
|
|
int minBitCount = 0;
|
|
for (final Integer i : list) {
|
|
int bitCount = Integer.bitCount(i);
|
|
assertTrue(bitCount >= minBitCount);
|
|
minBitCount = bitCount;
|
|
}
|
|
|
|
// Reuse the supplier to store AtomicInteger instead of Integer
|
|
// Hence the use of raw type and cast
|
|
List<AtomicInteger> incomparablesData = new ArrayList<>();
|
|
for (int i = 0; i < test.expected.size(); i++) {
|
|
incomparablesData.add(new AtomicInteger(i));
|
|
}
|
|
Function f = test.supplier;
|
|
@SuppressWarnings("unchecked")
|
|
List<AtomicInteger> incomparables = (List<AtomicInteger>) f.apply(incomparablesData);
|
|
|
|
CollectionSupplier.shuffle(incomparables);
|
|
incomparables.sort(ATOMIC_INTEGER_COMPARATOR);
|
|
for (int i = 0; i < test.expected.size(); i++) {
|
|
assertEquals(i, incomparables.get(i).intValue());
|
|
}
|
|
|
|
|
|
if (original.size() > SUBLIST_SIZE) {
|
|
final List<Integer> copy = new ArrayList<>(list);
|
|
final List<Integer> subList = list.subList(SUBLIST_FROM, SUBLIST_TO);
|
|
CollectionSupplier.shuffle(subList);
|
|
subList.sort(Comparator.naturalOrder());
|
|
CollectionAsserts.assertSorted(subList, Comparator.naturalOrder());
|
|
// verify that elements [0, from) remain unmodified
|
|
for (int i = 0; i < SUBLIST_FROM; i++) {
|
|
assertTrue(list.get(i) == copy.get(i),
|
|
"mismatch at index " + i);
|
|
}
|
|
// verify that elements [to, size) remain unmodified
|
|
for (int i = SUBLIST_TO; i < list.size(); i++) {
|
|
assertTrue(list.get(i) == copy.get(i),
|
|
"mismatch at index " + i);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> list = test.collection;
|
|
trimmedSubList(list, l -> {
|
|
CollectionSupplier.shuffle(l);
|
|
l.sort(Comparator.naturalOrder());
|
|
CollectionAsserts.assertSorted(l, Comparator.naturalOrder());
|
|
});
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testForEachThrowsCME() {
|
|
@SuppressWarnings("unchecked")
|
|
final CollectionSupplier<List<Integer>> supplier = new CollectionSupplier(LIST_CME_SUPPLIERS, SIZE);
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> list = test.collection;
|
|
|
|
if (list.size() <= 1) {
|
|
continue;
|
|
}
|
|
boolean gotException = false;
|
|
try {
|
|
// bad predicate that modifies its list, should throw CME
|
|
list.forEach(list::add);
|
|
} catch (ConcurrentModificationException cme) {
|
|
gotException = true;
|
|
}
|
|
if (!gotException) {
|
|
fail("expected CME was not thrown from " + test);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testRemoveIfThrowsCME() {
|
|
@SuppressWarnings("unchecked")
|
|
final CollectionSupplier<List<Integer>> supplier = new CollectionSupplier(LIST_CME_SUPPLIERS, SIZE);
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> list = test.collection;
|
|
|
|
if (list.size() <= 1) {
|
|
continue;
|
|
}
|
|
boolean gotException = false;
|
|
try {
|
|
// bad predicate that modifies its list, should throw CME
|
|
list.removeIf(list::add);
|
|
} catch (ConcurrentModificationException cme) {
|
|
gotException = true;
|
|
}
|
|
if (!gotException) {
|
|
fail("expected CME was not thrown from " + test);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testReplaceAllThrowsCME() {
|
|
@SuppressWarnings("unchecked")
|
|
final CollectionSupplier<List<Integer>> supplier = new CollectionSupplier(LIST_CME_SUPPLIERS, SIZE);
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> list = test.collection;
|
|
|
|
if (list.size() <= 1) {
|
|
continue;
|
|
}
|
|
boolean gotException = false;
|
|
try {
|
|
// bad predicate that modifies its list, should throw CME
|
|
list.replaceAll(x -> {int n = 3 * x; list.add(n); return n;});
|
|
} catch (ConcurrentModificationException cme) {
|
|
gotException = true;
|
|
}
|
|
if (!gotException) {
|
|
fail("expected CME was not thrown from " + test);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testSortThrowsCME() {
|
|
@SuppressWarnings("unchecked")
|
|
final CollectionSupplier<List<Integer>> supplier = new CollectionSupplier(LIST_CME_SUPPLIERS, SIZE);
|
|
for (final CollectionSupplier.TestCase<List<Integer>> test : supplier.get()) {
|
|
final List<Integer> list = test.collection;
|
|
|
|
if (list.size() <= 1) {
|
|
continue;
|
|
}
|
|
boolean gotException = false;
|
|
try {
|
|
// bad predicate that modifies its list, should throw CME
|
|
list.sort((x, y) -> {list.add(x); return x - y;});
|
|
} catch (ConcurrentModificationException cme) {
|
|
gotException = true;
|
|
}
|
|
if (!gotException) {
|
|
fail("expected CME was not thrown from " + test);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final List<Integer> SLICED_EXPECTED = Arrays.asList(0, 1, 2, 3, 5, 6, 7, 8, 9);
|
|
private static final List<Integer> SLICED_EXPECTED2 = Arrays.asList(0, 1, 2, 5, 6, 7, 8, 9);
|
|
|
|
@DataProvider(name="shortIntListProvider", parallel=true)
|
|
public static Object[][] intListCases() {
|
|
final Integer[] DATA = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
|
final List<Object[]> cases = new LinkedList<>();
|
|
cases.add(new Object[] { new ArrayList<>(Arrays.asList(DATA)) });
|
|
cases.add(new Object[] { new LinkedList<>(Arrays.asList(DATA)) });
|
|
cases.add(new Object[] { new Vector<>(Arrays.asList(DATA)) });
|
|
cases.add(new Object[] { new CopyOnWriteArrayList<>(Arrays.asList(DATA)) });
|
|
cases.add(new Object[] { new ExtendsAbstractList<>(Arrays.asList(DATA)) });
|
|
return cases.toArray(new Object[0][cases.size()]);
|
|
}
|
|
|
|
@Test(dataProvider = "shortIntListProvider")
|
|
public void testRemoveIfFromSlice(final List<Integer> list) {
|
|
final List<Integer> sublist = list.subList(3, 6);
|
|
assertTrue(sublist.removeIf(x -> x == 4));
|
|
CollectionAsserts.assertContents(list, SLICED_EXPECTED);
|
|
|
|
final List<Integer> sublist2 = list.subList(2, 5);
|
|
assertTrue(sublist2.removeIf(x -> x == 3));
|
|
CollectionAsserts.assertContents(list, SLICED_EXPECTED2);
|
|
}
|
|
}
|