/* * Copyright (c) 2023, 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.io.*; import java.util.*; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertThrows; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; /* * @test * @bug 8266571 * @summary Basic tests for SequencedMap * @modules java.base/java.util:open * @build SimpleSortedMap * @run testng BasicMap */ public class BasicMap { // ========== Data Providers ========== static final Class CCE = ClassCastException.class; static final Class NSEE = NoSuchElementException.class; static final Class UOE = UnsupportedOperationException.class; static final List> ORIGINAL = List.of(Map.entry("a", 1), Map.entry("b", 2), Map.entry("c", 3), Map.entry("d", 4), Map.entry("e", 5)); static > M load(M map, List> mappings) { for (var e : mappings) map.put(e.getKey(), e.getValue()); return map; } static NavigableMap cknav(NavigableMap map) { return Collections.checkedNavigableMap(map, String.class, Integer.class); } static SortedMap cksorted(SortedMap map) { return Collections.checkedSortedMap(map, String.class, Integer.class); } static SequencedMap umap(SequencedMap map) { return Collections.unmodifiableSequencedMap(map); } static SortedMap usorted(SortedMap map) { return Collections.unmodifiableSortedMap(map); } static NavigableMap unav(NavigableMap map) { return Collections.unmodifiableNavigableMap(map); } @DataProvider(name="all") public Iterator all() { var result = new ArrayList(); populated().forEachRemaining(result::add); empties().forEachRemaining(result::add); return result.iterator(); } @DataProvider(name="populated") public Iterator populated() { return Arrays.asList( new Object[] { "LinkedHashMap", load(new LinkedHashMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "SimpleSortedMap", load(new SimpleSortedMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "TreeMap", load(new TreeMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "UnmodMap", umap(load(new LinkedHashMap<>(), ORIGINAL)), ORIGINAL } ).iterator(); } @DataProvider(name="empties") public Iterator empties() { return Arrays.asList( new Object[] { "EmptyNavigableMap", Collections.emptyNavigableMap(), List.of() }, new Object[] { "EmptySortedMap", Collections.emptySortedMap(), List.of() }, new Object[] { "LinkedHashMap", new LinkedHashMap<>(), List.of() }, new Object[] { "SimpleSortedMap", new SimpleSortedMap<>(), List.of() }, new Object[] { "TreeMap", new TreeMap<>(), List.of() }, new Object[] { "UnmodMap", umap(new LinkedHashMap<>()), List.of() } ).iterator(); } @DataProvider(name="polls") public Iterator polls() { return Arrays.asList( new Object[] { "LinkedHashMap", load(new LinkedHashMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "SimpleSortedMap", load(new SimpleSortedMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "TreeMap", load(new TreeMap<>(), ORIGINAL), ORIGINAL } ).iterator(); } @DataProvider(name="emptyPolls") public Iterator emptyPolls() { return Arrays.asList( new Object[] { "LinkedHashMap", new LinkedHashMap<>(), List.of() }, new Object[] { "SimpleSortedMap", new SimpleSortedMap<>(), List.of() }, new Object[] { "TreeMap", new TreeMap<>(), List.of() } ).iterator(); } @DataProvider(name="puts") public Iterator puts() { return Arrays.asList( new Object[] { "LinkedHashMap", load(new LinkedHashMap<>(), ORIGINAL), ORIGINAL } ).iterator(); } @DataProvider(name="putUnpositioned") public Iterator putUnpositioned() { return Arrays.asList( new Object[] { "LinkedHashMap", false, load(new LinkedHashMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "LinkedHashMap", true, load(new LinkedHashMap<>(), ORIGINAL), ORIGINAL } ).iterator(); } @DataProvider(name="putThrows") public Iterator putThrows() { return Arrays.asList( new Object[] { "SimpleSortedMap", load(new SimpleSortedMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "TreeMap", load(new TreeMap<>(), ORIGINAL), ORIGINAL } ).iterator(); } @DataProvider(name="serializable") public Iterator serializable() { return Arrays.asList( new Object[] { "LinkedHashMap", load(new LinkedHashMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "TreeMap", load(new TreeMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "UnmodMap", umap(load(new LinkedHashMap<>(), ORIGINAL)), ORIGINAL } ).iterator(); } @DataProvider(name="notSerializable") public Iterator notSerializable() { return Arrays.asList( new Object[] { "LinkedHashMap", load(new LinkedHashMap<>(), ORIGINAL).reversed() }, new Object[] { "UnmodMap", umap(load(new LinkedHashMap<>(), ORIGINAL)).reversed() } ).iterator(); } @DataProvider(name="doubleReverse") public Iterator doubleReverse() { return Arrays.asList( new Object[] { "LinkedHashMap", load(new LinkedHashMap<>(), ORIGINAL) } ).iterator(); } @DataProvider(name="unmodifiable") public Iterator unmodifible() { return Arrays.asList( new Object[] { "UnmodMap", umap(load(new LinkedHashMap<>(), ORIGINAL)), ORIGINAL }, new Object[] { "UnmodNav", unav(load(new TreeMap<>(), ORIGINAL)), ORIGINAL }, new Object[] { "UnmodSorted", usorted(load(new TreeMap<>(), ORIGINAL)), ORIGINAL } ).iterator(); } @DataProvider(name="checked") public Iterator checked() { return Arrays.asList( new Object[] { "ChkNav", cknav(load(new TreeMap<>(), ORIGINAL)), ORIGINAL }, new Object[] { "ChkSorted", cksorted(load(new TreeMap<>(), ORIGINAL)), ORIGINAL } ).iterator(); } // mode bit tests boolean reverseMap(int mode) { return (mode & 1) != 0; } boolean reverseView(int mode) { return (mode & 2) != 0; } boolean callLast(int mode) { return (mode & 4) != 0; } boolean refLast(int mode) { return reverseMap(mode) ^ reverseView(mode) ^ callLast(mode); } /** * Generate cases for testing the removeFirst and removeLast methods of map views. For each * different map implementation, generate 8 cases from the three bits of the testing mode * int value: * * (bit 1) if true, the backing map is reversed * (bit 2) if true, the view is reversed * (bit 4) if true, the last element of the view is to be removed, otherwise the first * * The three bits XORed together (by refLast(), above) indicate (if true) the last * or (if false) the first element of the reference entry list is to be removed. * * @return the generated cases */ @DataProvider(name="viewRemoves") public Iterator viewRemoves() { var cases = new ArrayList(); for (int mode = 0; mode < 8; mode++) { cases.addAll(Arrays.asList( new Object[] { "LinkedHashMap", mode, load(new LinkedHashMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "SimpleSortedMap", mode, load(new SimpleSortedMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "TreeMap", mode, load(new TreeMap<>(), ORIGINAL), ORIGINAL } )); } return cases.iterator(); } @DataProvider(name="emptyViewRemoves") public Iterator emptyViewRemoves() { var cases = new ArrayList(); for (int mode = 0; mode < 8; mode++) { cases.addAll(Arrays.asList( new Object[] { "LinkedHashMap", mode, new LinkedHashMap<>(), List.of() }, new Object[] { "SimpleSortedMap", mode, new SimpleSortedMap<>(), List.of() }, new Object[] { "TreeMap", mode, new TreeMap<>(), List.of() } )); } return cases.iterator(); } @DataProvider(name="viewAddThrows") public Iterator viewAddThrows() { var cases = new ArrayList(); for (int mode = 0; mode < 8; mode++) { cases.addAll(Arrays.asList( new Object[] { "LinkedHashMap", mode, load(new LinkedHashMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "SimpleSortedMap", mode, load(new SimpleSortedMap<>(), ORIGINAL), ORIGINAL }, new Object[] { "TreeMap", mode, load(new TreeMap<>(), ORIGINAL), ORIGINAL } )); } return cases.iterator(); } @DataProvider(name="nullableEntries") public Iterator nullableEntries() { return Arrays.asList( new Object[] { "firstEntry" }, new Object[] { "lastEntry" }, new Object[] { "pollFirstEntry" }, new Object[] { "pollLastEntry" } ).iterator(); } // ========== Assertions ========== /** * Basic checks over the contents of a SequencedMap, compared to a reference List of entries, * in one direction. * * @param map the SequencedMap under test * @param ref the reference list of entries */ public void checkContents1(SequencedMap map, List> ref) { var list1 = new ArrayList>(); map.forEach((k, v) -> list1.add(Map.entry(k, v))); assertEquals(list1, ref); assertEquals(map.size(), ref.size()); assertEquals(map.isEmpty(), ref.isEmpty()); for (var e : ref) { assertTrue(map.containsKey(e.getKey())); assertTrue(map.containsValue(e.getValue())); assertEquals(map.get(e.getKey()), e.getValue()); } } public void checkContents(SequencedMap map, List> ref) { checkContents1(map, ref); var rref = new ArrayList<>(ref); Collections.reverse(rref); var rmap = map.reversed(); checkContents1(rmap, rref); var rrmap = rmap.reversed(); checkContents1(rrmap, ref); } /** * Check the entrySet, keySet, or values view of a SequencedMap in one direction. The view * collection is ordered even though the collection type is not sequenced. * * @param the element type of the view * @param mapView the actual map view * @param expElements list of the expected elements */ public void checkView1(Collection mapView, List expElements) { var list1 = new ArrayList(); for (var k : mapView) list1.add(k); assertEquals(list1, expElements); var list2 = new ArrayList(); mapView.forEach(list2::add); assertEquals(list2, expElements); var list3 = Arrays.asList(mapView.toArray()); assertEquals(list3, expElements); var list4 = Arrays.asList(mapView.toArray(new Object[0])); assertEquals(list4, expElements); var list5 = Arrays.asList(mapView.toArray(Object[]::new)); assertEquals(list5, expElements); var list6 = mapView.stream().toList(); assertEquals(list6, expElements); var list7 = mapView.parallelStream().toList(); assertEquals(list7, expElements); assertEquals(mapView.size(), expElements.size()); assertEquals(mapView.isEmpty(), expElements.isEmpty()); for (var k : expElements) { assertTrue(mapView.contains(k)); } var it = mapView.iterator(); if (expElements.isEmpty()) { assertFalse(it.hasNext()); } else { assertTrue(it.hasNext()); assertEquals(it.next(), expElements.get(0)); } } /** * Check the sequenced entrySet, keySet, or values view of a SequencedMap in one direction. * * @param the element type of the view * @param mapView the actual map view * @param expElements list of the expected elements */ public void checkSeqView1(SequencedCollection mapView, List expElements) { checkView1(mapView, expElements); if (expElements.isEmpty()) { assertThrows(NoSuchElementException.class, () -> mapView.getFirst()); assertThrows(NoSuchElementException.class, () -> mapView.getLast()); } else { assertEquals(mapView.getFirst(), expElements.get(0)); assertEquals(mapView.getLast(), expElements.get(expElements.size() - 1)); } } /** * Check the keySet and sequencedKeySet views of a map. It's possible to unify this with * the corresponding checks for values and entrySet views, but doing this adds a bunch * of generics and method references that tend to obscure more than they help. * * @param map the SequencedMap under test * @param refEntries expected contents of the map */ public void checkKeySet(SequencedMap map, List> refEntries) { List refKeys = refEntries.stream().map(Map.Entry::getKey).toList(); List rrefKeys = new ArrayList<>(refKeys); Collections.reverse(rrefKeys); SequencedMap rmap = map.reversed(); checkView1(map.keySet(), refKeys); checkSeqView1(map.sequencedKeySet(), refKeys); checkSeqView1(map.sequencedKeySet().reversed(), rrefKeys); checkView1(rmap.keySet(), rrefKeys); checkSeqView1(rmap.sequencedKeySet(), rrefKeys); checkSeqView1(rmap.sequencedKeySet().reversed(), refKeys); checkView1(rmap.reversed().keySet(), refKeys); checkSeqView1(rmap.reversed().sequencedKeySet(), refKeys); checkSeqView1(rmap.reversed().sequencedKeySet().reversed(), rrefKeys); assertEquals(map.keySet().hashCode(), rmap.keySet().hashCode()); assertEquals(map.keySet().hashCode(), map.sequencedKeySet().hashCode()); assertEquals(rmap.keySet().hashCode(), rmap.sequencedKeySet().hashCode()); // Don't use assertEquals(), as we really want to test the equals() methods. assertTrue(map.keySet().equals(map.sequencedKeySet())); assertTrue(map.sequencedKeySet().equals(map.keySet())); assertTrue(rmap.keySet().equals(map.sequencedKeySet())); assertTrue(rmap.sequencedKeySet().equals(map.keySet())); assertTrue(map.keySet().equals(rmap.sequencedKeySet())); assertTrue(map.sequencedKeySet().equals(rmap.keySet())); assertTrue(rmap.keySet().equals(rmap.sequencedKeySet())); assertTrue(rmap.sequencedKeySet().equals(rmap.keySet())); } /** * Check the values and sequencedValues views of a map. * * @param map the SequencedMap under test * @param refEntries expected contents of the map */ public void checkValues(SequencedMap map, List> refEntries) { List refValues = refEntries.stream().map(Map.Entry::getValue).toList(); List rrefValues = new ArrayList<>(refValues); Collections.reverse(rrefValues); SequencedMap rmap = map.reversed(); checkView1(map.values(), refValues); checkSeqView1(map.sequencedValues(), refValues); checkSeqView1(map.sequencedValues().reversed(), rrefValues); checkView1(rmap.values(), rrefValues); checkSeqView1(rmap.sequencedValues(), rrefValues); checkSeqView1(rmap.sequencedValues().reversed(), refValues); checkView1(rmap.reversed().values(), refValues); checkSeqView1(rmap.reversed().sequencedValues(), refValues); checkSeqView1(rmap.reversed().sequencedValues().reversed(), rrefValues); // No assertions over hashCode(), as Collection inherits Object.hashCode // which is usually but not guaranteed to give unequal results. // It's permissible for an implementation to return the same instance for values() // as for sequencedValues(). Either they're the same instance, or they must be // unequal, because distinct collections should always be unequal. var v = map.values(); var sv = map.sequencedValues(); assertTrue((v == sv) || ! (v.equals(sv) || sv.equals(v))); var rv = rmap.values(); var rsv = rmap.sequencedValues(); assertTrue((rv == rsv) || ! (rv.equals(rsv) || rsv.equals(rv))); } /** * Check the entrySet and sequencedEntrySet views of a map. * * @param map the SequencedMap under test * @param refEntries expected contents of the map */ public void checkEntrySet(SequencedMap map, List> refEntries) { List> rref = new ArrayList<>(refEntries); Collections.reverse(rref); SequencedMap rmap = map.reversed(); checkView1(map.entrySet(), refEntries); checkSeqView1(map.sequencedEntrySet(), refEntries); checkSeqView1(map.sequencedEntrySet().reversed(), rref); checkView1(rmap.entrySet(), rref); checkSeqView1(rmap.sequencedEntrySet(), rref); checkSeqView1(rmap.sequencedEntrySet().reversed(), refEntries); checkView1(rmap.reversed().entrySet(), refEntries); checkSeqView1(rmap.reversed().sequencedEntrySet(), refEntries); checkSeqView1(rmap.reversed().sequencedEntrySet().reversed(), rref); assertEquals(map.entrySet().hashCode(), rmap.entrySet().hashCode()); assertEquals(map.entrySet().hashCode(), map.sequencedEntrySet().hashCode()); assertEquals(map.sequencedEntrySet().hashCode(), map.entrySet().hashCode()); assertTrue(map.entrySet().equals(map.sequencedEntrySet())); assertTrue(map.sequencedEntrySet().equals(map.entrySet())); assertTrue(rmap.entrySet().equals(map.sequencedEntrySet())); assertTrue(rmap.sequencedEntrySet().equals(map.entrySet())); assertTrue(map.entrySet().equals(rmap.sequencedEntrySet())); assertTrue(map.sequencedEntrySet().equals(rmap.entrySet())); assertTrue(rmap.entrySet().equals(rmap.sequencedEntrySet())); assertTrue(rmap.sequencedEntrySet().equals(rmap.entrySet())); } /** * Test attempted modifications to unmodifiable map views. The only mutating operation * map views can support is removal. * * @param element type of the map view * @param view the map view */ public void checkUnmodifiableView(Collection view) { assertThrows(UOE, () -> view.clear()); assertThrows(UOE, () -> { var it = view.iterator(); it.next(); it.remove(); }); assertThrows(UOE, () -> { var t = view.iterator().next(); view.remove(t); }); // TODO these ops should throw unconditionally, but they don't in some implementations // assertThrows(UOE, () -> view.removeAll(List.of())); // assertThrows(UOE, () -> view.removeIf(x -> false)); // assertThrows(UOE, () -> view.retainAll(view)); assertThrows(UOE, () -> view.removeAll(view)); assertThrows(UOE, () -> view.removeIf(x -> true)); assertThrows(UOE, () -> view.retainAll(List.of())); } /** * Test removal methods on unmodifiable sequenced map views. * * @param element type of the map view * @param view the map view */ public void checkUnmodifiableSeqView(SequencedCollection view) { checkUnmodifiableView(view); assertThrows(UOE, () -> view.removeFirst()); assertThrows(UOE, () -> view.removeLast()); var rview = view.reversed(); checkUnmodifiableView(rview); assertThrows(UOE, () -> rview.removeFirst()); assertThrows(UOE, () -> rview.removeLast()); } public void checkUnmodifiableEntry(SequencedMap map) { assertThrows(UOE, () -> { map.firstEntry().setValue(99); }); assertThrows(UOE, () -> { map.lastEntry().setValue(99); }); assertThrows(UOE, () -> { map.sequencedEntrySet().getFirst().setValue(99); }); assertThrows(UOE, () -> { map.sequencedEntrySet().getLast().setValue(99); }); assertThrows(UOE, () -> { map.sequencedEntrySet().reversed().getFirst().setValue(99); }); assertThrows(UOE, () -> { map.sequencedEntrySet().reversed().getLast().setValue(99); }); } public void checkUnmodifiable1(SequencedMap map) { assertThrows(UOE, () -> map.putFirst("x", 99)); assertThrows(UOE, () -> map.putLast("x", 99)); assertThrows(UOE, () -> { map.pollFirstEntry(); }); assertThrows(UOE, () -> { map.pollLastEntry(); }); checkUnmodifiableEntry(map); checkUnmodifiableView(map.keySet()); checkUnmodifiableView(map.values()); checkUnmodifiableView(map.entrySet()); checkUnmodifiableSeqView(map.sequencedKeySet()); checkUnmodifiableSeqView(map.sequencedValues()); checkUnmodifiableSeqView(map.sequencedEntrySet()); } public void checkUnmodifiable(SequencedMap map) { checkUnmodifiable1(map); checkUnmodifiable1(map.reversed()); } // The putFirst/putLast operations aren't tested here, because the only instances of // checked, sequenced maps are SortedMap and NavigableMap, which don't support them. public void checkChecked(SequencedMap map) { SequencedMap objMap = (SequencedMap)(SequencedMap)map; assertThrows(CCE, () -> { objMap.put(new Object(), 99); }); assertThrows(CCE, () -> { objMap.put("x", new Object()); }); assertThrows(CCE, () -> { objMap.sequencedEntrySet().getFirst().setValue(new Object()); }); assertThrows(CCE, () -> { objMap.sequencedEntrySet().reversed().getFirst().setValue(new Object()); }); assertThrows(CCE, () -> { objMap.reversed().put(new Object(), 99); }); assertThrows(CCE, () -> { objMap.reversed().put("x", new Object()); }); assertThrows(CCE, () -> { objMap.reversed().sequencedEntrySet().getFirst().setValue(new Object()); }); assertThrows(CCE, () -> { objMap.reversed().sequencedEntrySet().reversed().getFirst().setValue(new Object()); }); } public void checkEntry(Map.Entry entry, String key, Integer value) { assertEquals(entry.getKey(), key); assertEquals(entry.getValue(), value); } // ========== Tests ========== @Test(dataProvider="all") public void testFundamentals(String label, SequencedMap map, List> ref) { checkContents(map, ref); checkEntrySet(map, ref); checkKeySet(map, ref); checkValues(map, ref); } @Test(dataProvider="populated") public void testFirstEntry(String label, SequencedMap map, List> ref) { assertEquals(map.firstEntry(), ref.get(0)); assertEquals(map.reversed().firstEntry(), ref.get(ref.size() - 1)); assertThrows(UOE, () -> { map.firstEntry().setValue(99); }); assertThrows(UOE, () -> { map.reversed().firstEntry().setValue(99); }); checkContents(map, ref); } @Test(dataProvider="populated") public void testLastEntry(String label, SequencedMap map, List> ref) { assertEquals(map.lastEntry(), ref.get(ref.size() - 1)); assertEquals(map.reversed().lastEntry(), ref.get(0)); assertThrows(UOE, () -> { map.lastEntry().setValue(99); }); assertThrows(UOE, () -> { map.reversed().lastEntry().setValue(99); }); checkContents(map, ref); } @Test(dataProvider="empties") public void testEmptyFirstEntry(String label, SequencedMap map, List> ref) { assertNull(map.firstEntry()); assertNull(map.reversed().firstEntry()); checkContents(map, ref); } @Test(dataProvider="empties") public void testEmptyLastEntry(String label, SequencedMap map, List> ref) { assertNull(map.lastEntry()); assertNull(map.reversed().lastEntry()); checkContents(map, ref); } @Test(dataProvider="puts") public void testPutFirst(String label, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(0, Map.entry("x", 99)); map.putFirst("x", 99); checkContents(map, ref); } @Test(dataProvider="puts") public void testPutFirstRev(String label, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(Map.entry("x", 99)); map.reversed().putFirst("x", 99); checkContents(map, ref); } @Test(dataProvider="puts") public void testPutLast(String label, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(Map.entry("x", 99)); map.putLast("x", 99); checkContents(map, ref); } @Test(dataProvider="puts") public void testPutLastRev(String label, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(0, Map.entry("x", 99)); map.reversed().putLast("x", 99); checkContents(map, ref); } @Test(dataProvider="putUnpositioned") public void testUnposPut(String label, boolean rev, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(Map.entry("x", 99)); (rev ? map.reversed() : map).put("x", 99); checkContents(map, ref); } @Test(dataProvider="putUnpositioned") public void testUnposPutAll(String label, boolean rev, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(Map.entry("x", 99)); (rev ? map.reversed() : map).putAll(Map.of("x", 99)); checkContents(map, ref); } @Test(dataProvider="putUnpositioned") public void testUnposPutIfAbsent(String label, boolean rev, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(Map.entry("x", 99)); (rev ? map.reversed() : map).putIfAbsent("x", 99); checkContents(map, ref); } @Test(dataProvider="putUnpositioned") public void testUnposCompute(String label, boolean rev, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(Map.entry("x", 99)); (rev ? map.reversed() : map).compute("x", (k, v) -> 99); checkContents(map, ref); } @Test(dataProvider="putUnpositioned") public void testUnposComputeIfAbsent(String label, boolean rev, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(Map.entry("x", 99)); (rev ? map.reversed() : map).computeIfAbsent("x", k -> 99); checkContents(map, ref); } @Test(dataProvider="putUnpositioned") public void testUnposMerge(String label, boolean rev, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); ref.add(Map.entry("x", 99)); (rev ? map.reversed() : map).merge("x", 99, /*unused*/ (k, v) -> -1); checkContents(map, ref); } @Test(dataProvider="putThrows") public void testPutThrows(String label, SequencedMap map, List> baseref) { assertThrows(UOE, () -> map.putFirst("x", 99)); assertThrows(UOE, () -> map.putLast("x", 99)); assertThrows(UOE, () -> map.reversed().putFirst("x", 99)); assertThrows(UOE, () -> map.reversed().putLast("x", 99)); checkContents(map, baseref); } @Test(dataProvider="polls") public void testPollFirst(String label, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); var act = map.pollFirstEntry(); assertEquals(act, ref.remove(0)); assertThrows(UOE, () -> { act.setValue(99); }); checkContents(map, ref); } @Test(dataProvider="polls") public void testPollFirstRev(String label, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); var act = map.reversed().pollFirstEntry(); assertEquals(act, ref.remove(ref.size() - 1)); assertThrows(UOE, () -> { act.setValue(99); }); checkContents(map, ref); } @Test(dataProvider="polls") public void testPollLast(String label, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); var act = map.pollLastEntry(); assertEquals(act, ref.remove(ref.size() - 1)); assertThrows(UOE, () -> { act.setValue(99); }); checkContents(map, ref); } @Test(dataProvider="polls") public void testPollLastRev(String label, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); var act = map.reversed().pollLastEntry(); assertEquals(act, ref.remove(0)); assertThrows(UOE, () -> { act.setValue(99); }); checkContents(map, ref); } @Test(dataProvider="emptyPolls") public void testEmptyPollFirst(String label, SequencedMap map, List> ref) { assertNull(map.pollFirstEntry()); assertNull(map.reversed().pollFirstEntry()); checkContents(map, ref); } @Test(dataProvider="emptyPolls") public void testEmptyPollLast(String label, SequencedMap map, List> ref) { assertNull(map.pollLastEntry()); assertNull(map.reversed().pollLastEntry()); checkContents(map, ref); } @Test(dataProvider="serializable") public void testSerializable(String label, SequencedMap map, List> ref) throws ClassNotFoundException, IOException { var baos = new ByteArrayOutputStream(); try (var oos = new ObjectOutputStream(baos)) { oos.writeObject(map); } try (var bais = new ByteArrayInputStream(baos.toByteArray()); var ois = new ObjectInputStream(bais)) { var map2 = (SequencedMap) ois.readObject(); checkContents(map2, ref); } } @Test(dataProvider="notSerializable") public void testNotSerializable(String label, SequencedMap map) throws ClassNotFoundException, IOException { var baos = new ByteArrayOutputStream(); try (var oos = new ObjectOutputStream(baos)) { assertThrows(ObjectStreamException.class, () -> oos.writeObject(map)); } } @Test(dataProvider="doubleReverse") public void testDoubleReverse(String label, SequencedMap map) { var rrmap = map.reversed().reversed(); assertSame(rrmap, map); } @Test(dataProvider="unmodifiable") public void testUnmodifiable(String label, SequencedMap map, List> ref) { checkUnmodifiable(map); checkContents(map, ref); } @Test(dataProvider="checked") public void testChecked(String label, SequencedMap map, List> ref) { checkChecked(map); checkContents(map, ref); } /** * Test that a removal from the sequenedKeySet view is properly reflected in the original * backing map. The mode value indicates whether the backing map is reversed, whether the * sequencedKeySet view is reversed, and whether the removeFirst or removeLast is called * on the view. See the viewRemoves() dataProvider for details. * * @param label the implementation label * @param mode reversed and first/last modes * @param map the original map instance * @param baseref reference contents of the original map */ @Test(dataProvider="viewRemoves") public void testKeySetRemoves(String label, int mode, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); var exp = (refLast(mode) ? ref.remove(ref.size() - 1) : ref.remove(0)).getKey(); var tempmap = reverseMap(mode) ? map.reversed() : map; var keySet = reverseView(mode) ? tempmap.sequencedKeySet().reversed() : tempmap.sequencedKeySet(); var act = callLast(mode) ? keySet.removeLast() : keySet.removeFirst(); assertEquals(act, exp); checkContents(map, ref); } // As above, but for the sequencedValues view. @Test(dataProvider="viewRemoves") public void testValuesRemoves(String label, int mode, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); var exp = (refLast(mode) ? ref.remove(ref.size() - 1) : ref.remove(0)).getValue(); var tempmap = reverseMap(mode) ? map.reversed() : map; var values = reverseView(mode) ? tempmap.sequencedValues().reversed() : tempmap.sequencedValues(); var act = callLast(mode) ? values.removeLast() : values.removeFirst(); assertEquals(act, exp); checkContents(map, ref); } // As above, but for the sequencedEntrySet view. @Test(dataProvider="viewRemoves") public void testEntrySetRemoves(String label, int mode, SequencedMap map, List> baseref) { var ref = new ArrayList<>(baseref); var exp = refLast(mode) ? ref.remove(ref.size() - 1) : ref.remove(0); var tempmap = reverseMap(mode) ? map.reversed() : map; var entrySet = reverseView(mode) ? tempmap.sequencedEntrySet().reversed() : tempmap.sequencedEntrySet(); var act = callLast(mode) ? entrySet.removeLast() : entrySet.removeFirst(); assertEquals(act, exp); checkContents(map, ref); } // As above, but for the sequencedKeySet of an empty map. @Test(dataProvider="emptyViewRemoves") public void testEmptyKeySetRemoves(String label, int mode, SequencedMap map, List> baseref) { var tempmap = reverseMap(mode) ? map.reversed() : map; var keySet = reverseView(mode) ? tempmap.sequencedKeySet().reversed() : tempmap.sequencedKeySet(); if (callLast(mode)) assertThrows(NSEE, () -> keySet.removeLast()); else assertThrows(NSEE, () -> keySet.removeFirst()); checkContents(map, baseref); } // As above, but for the sequencedValues view. @Test(dataProvider="emptyViewRemoves") public void testEmptyValuesRemoves(String label, int mode, SequencedMap map, List> baseref) { var tempmap = reverseMap(mode) ? map.reversed() : map; var values = reverseView(mode) ? tempmap.sequencedValues().reversed() : tempmap.sequencedValues(); if (callLast(mode)) assertThrows(NSEE, () -> values.removeLast()); else assertThrows(NSEE, () -> values.removeFirst()); checkContents(map, baseref); } // As above, but for the sequencedEntrySet view. @Test(dataProvider="emptyViewRemoves") public void testEmptyEntrySetRemoves(String label, int mode, SequencedMap map, List> baseref) { var tempmap = reverseMap(mode) ? map.reversed() : map; var entrySet = reverseView(mode) ? tempmap.sequencedEntrySet().reversed() : tempmap.sequencedEntrySet(); if (callLast(mode)) assertThrows(NSEE, () -> entrySet.removeLast()); else assertThrows(NSEE, () -> entrySet.removeFirst()); checkContents(map, baseref); } // Test that addFirst/addLast on the sequencedKeySetView throw UnsupportedOperationException. @Test(dataProvider="viewAddThrows") public void testKeySetAddThrows(String label, int mode, SequencedMap map, List> baseref) { var tempmap = reverseMap(mode) ? map.reversed() : map; var keySet = reverseView(mode) ? tempmap.sequencedKeySet().reversed() : tempmap.sequencedKeySet(); if (callLast(mode)) assertThrows(UOE, () -> keySet.addLast("x")); else assertThrows(UOE, () -> keySet.addFirst("x")); checkContents(map, baseref); } // As above, but for the sequencedValues view. @Test(dataProvider="viewAddThrows") public void testValuesAddThrows(String label, int mode, SequencedMap map, List> baseref) { var tempmap = reverseMap(mode) ? map.reversed() : map; var values = reverseView(mode) ? tempmap.sequencedValues().reversed() : tempmap.sequencedValues(); if (callLast(mode)) assertThrows(UOE, () -> values.addLast(99)); else assertThrows(UOE, () -> values.addFirst(99)); checkContents(map, baseref); } // As above, but for the sequencedEntrySet view. @Test(dataProvider="viewAddThrows") public void testEntrySetAddThrows(String label, int mode, SequencedMap map, List> baseref) { var tempmap = reverseMap(mode) ? map.reversed() : map; var entrySet = reverseView(mode) ? tempmap.sequencedEntrySet().reversed() : tempmap.sequencedEntrySet(); if (callLast(mode)) assertThrows(UOE, () -> entrySet.addLast(Map.entry("x", 99))); else assertThrows(UOE, () -> entrySet.addFirst(Map.entry("x", 99))); checkContents(map, baseref); } @Test(dataProvider="nullableEntries") public void testNullableKeyValue(String mode) { // TODO this relies on LHM to inherit SequencedMap default // methods which are actually being tested here. SequencedMap map = new LinkedHashMap<>(); map.put(null, 1); map.put("two", null); switch (mode) { case "firstEntry" -> checkEntry(map.firstEntry(), null, 1); case "lastEntry" -> checkEntry(map.lastEntry(), "two", null); case "pollFirstEntry" -> checkEntry(map.pollFirstEntry(), null, 1); case "pollLastEntry" -> checkEntry(map.pollLastEntry(), "two", null); default -> throw new AssertionError("illegal mode " + mode); } } }