diff --git a/src/java.base/share/classes/java/util/AbstractMap.java b/src/java.base/share/classes/java/util/AbstractMap.java index 8e9414f8ef54f..b0d311f656565 100644 --- a/src/java.base/share/classes/java/util/AbstractMap.java +++ b/src/java.base/share/classes/java/util/AbstractMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -25,6 +25,11 @@ package java.util; +import java.util.stream.Stream; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; + /** * This class provides a skeletal implementation of the {@code Map} * interface, to minimize the effort required to implement this interface. @@ -875,7 +880,48 @@ public int hashCode() { public String toString() { return key + "=" + value; } - } + /** + * Delegates all Collection methods to the provided non-sequenced map view, + * except add() and addAll(), which throw UOE. This provides the common + * implementation of each of the sequenced views of the SequencedMap. + * Each view implementation is a subclass that provides an instance of the + * non-sequenced view as a delegate and an implementation of reversed(). + * Each view also inherits the default implementations for the sequenced + * methods from SequencedCollection or SequencedSet. + *

+ * Ideally this would be a private class within SequencedMap, but private + * classes aren't permitted within interfaces. + * + * @param the view's element type + */ + /* non-public */ abstract static class ViewCollection implements Collection { + UnsupportedOperationException uoe() { return new UnsupportedOperationException(); } + final Collection view; + + ViewCollection(Collection view) { this.view = view; } + + public boolean add(E t) { throw uoe(); } + public boolean addAll(Collection c) { throw uoe(); } + public void clear() { view.clear(); } + public boolean contains(Object o) { return view.contains(o); } + public boolean containsAll(Collection c) { return view.containsAll(c); } + public boolean equals(Object o) { return view.equals(o); } + public void forEach(Consumer c) { view.forEach(c); } + public int hashCode() { return view.hashCode(); } + public boolean isEmpty() { return view.isEmpty(); } + public Iterator iterator() { return view.iterator(); } + public Stream parallelStream() { return view.parallelStream(); } + public boolean remove(Object o) { return view.remove(o); } + public boolean removeAll(Collection c) { return view.removeAll(c); } + public boolean removeIf(Predicate filter) { return view.removeIf(filter); } + public boolean retainAll(Collection c) { return view.retainAll(c); } + public int size() { return view.size(); } + public Spliterator spliterator() { return view.spliterator(); } + public Stream stream() { return view.stream(); } + public Object[] toArray() { return view.toArray(); } + public T[] toArray(IntFunction generator) { return view.toArray(generator); } + public T[] toArray(T[] a) { return view.toArray(a); } + } } diff --git a/src/java.base/share/classes/java/util/ArrayDeque.java b/src/java.base/share/classes/java/util/ArrayDeque.java index 603ebceb757df..eb459b805174b 100644 --- a/src/java.base/share/classes/java/util/ArrayDeque.java +++ b/src/java.base/share/classes/java/util/ArrayDeque.java @@ -74,9 +74,8 @@ * exception for its correctness: the fail-fast behavior of iterators * should be used only to detect bugs. * - *

This class and its iterator implement all of the - * optional methods of the {@link Collection} and {@link - * Iterator} interfaces. + *

This class and its iterator implement all of the optional methods of the + * {@link Collection}, {@link SequencedCollection}, and {@link Iterator} interfaces. * *

This class is a member of the * diff --git a/src/java.base/share/classes/java/util/ArrayList.java b/src/java.base/share/classes/java/util/ArrayList.java index a36dcd8a79695..1f27572ef167b 100644 --- a/src/java.base/share/classes/java/util/ArrayList.java +++ b/src/java.base/share/classes/java/util/ArrayList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -428,6 +428,35 @@ public E get(int index) { return elementData(index); } + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E getFirst() { + if (size == 0) { + throw new NoSuchElementException(); + } else { + return elementData(0); + } + } + + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E getLast() { + int last = size - 1; + if (last < 0) { + throw new NoSuchElementException(); + } else { + return elementData(last); + } + } + /** * Replaces the element at the specified position in this list with * the specified element. @@ -491,6 +520,24 @@ public void add(int index, E element) { size = s + 1; } + /** + * {@inheritDoc} + * + * @since 21 + */ + public void addFirst(E element) { + add(0, element); + } + + /** + * {@inheritDoc} + * + * @since 21 + */ + public void addLast(E element) { + add(element); + } + /** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their @@ -510,6 +557,41 @@ public E remove(int index) { return oldValue; } + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E removeFirst() { + if (size == 0) { + throw new NoSuchElementException(); + } else { + Object[] es = elementData; + @SuppressWarnings("unchecked") E oldValue = (E) es[0]; + fastRemove(es, 0); + return oldValue; + } + } + + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E removeLast() { + int last = size - 1; + if (last < 0) { + throw new NoSuchElementException(); + } else { + Object[] es = elementData; + @SuppressWarnings("unchecked") E oldValue = (E) es[last]; + fastRemove(es, last); + return oldValue; + } + } + /** * {@inheritDoc} */ diff --git a/src/java.base/share/classes/java/util/Collection.java b/src/java.base/share/classes/java/util/Collection.java index fbaa1881a899e..0f63f0b26a46b 100644 --- a/src/java.base/share/classes/java/util/Collection.java +++ b/src/java.base/share/classes/java/util/Collection.java @@ -33,8 +33,11 @@ /** * The root interface in the collection hierarchy. A collection * represents a group of objects, known as its elements. Some - * collections allow duplicate elements and others do not. Some are ordered - * and others unordered. The JDK does not provide any direct + * collections allow duplicate elements and others do not. Some are ordered, + * and others are unordered. Collections that have a defined + * encounter order + * are generally subtypes of the {@link SequencedCollection} interface. + * The JDK does not provide any direct * implementations of this interface: it provides implementations of more * specific subinterfaces like {@code Set} and {@code List}. This interface * is typically used to pass collections around and manipulate them where @@ -121,8 +124,9 @@ * Other examples of view collections include collections that provide a * different representation of the same elements, for example, as * provided by {@link List#subList List.subList}, - * {@link NavigableSet#subSet NavigableSet.subSet}, or - * {@link Map#entrySet Map.entrySet}. + * {@link NavigableSet#subSet NavigableSet.subSet}, + * {@link Map#entrySet Map.entrySet}, or + * {@link SequencedCollection#reversed SequencedCollection.reversed}. * Any changes made to the backing collection are visible in the view collection. * Correspondingly, any changes made to the view collection — if changes * are permitted — are written through to the backing collection. @@ -202,7 +206,8 @@ * serializability of such collections is described in the specification of the method * that creates them, or in some other suitable place. In cases where the serializability * of a collection is not specified, there is no guarantee about the serializability of such - * collections. In particular, many view collections are not serializable. + * collections. In particular, many view collections are not serializable, + * even if the original collection is serializable. * *

A collection implementation that implements the {@code Serializable} interface cannot * be guaranteed to be serializable. The reason is that in general, collections @@ -501,7 +506,9 @@ default T[] toArray(IntFunction generator) { * the specified collection is modified while the operation is in progress. * (This implies that the behavior of this call is undefined if the * specified collection is this collection, and this collection is - * nonempty.) + * nonempty.) If the specified collection has a defined + * encounter order, + * processing of its elements generally occurs in that order. * * @param c collection containing elements to be added to this collection * @return {@code true} if this collection changed as a result of the call diff --git a/src/java.base/share/classes/java/util/Collections.java b/src/java.base/share/classes/java/util/Collections.java index 4425b4483c0bc..f45d297f3007b 100644 --- a/src/java.base/share/classes/java/util/Collections.java +++ b/src/java.base/share/classes/java/util/Collections.java @@ -369,9 +369,15 @@ else if (cmp > 0) * * This method runs in linear time. * + * @apiNote + * This method mutates the specified list in-place. To obtain a + * reverse-ordered view of a list without mutating it, use the + * {@link List#reversed List.reversed} method. + * * @param list the list whose elements are to be reversed. * @throws UnsupportedOperationException if the specified list or * its list-iterator does not support the {@code set} operation. + * @see List#reversed List.reversed */ @SuppressWarnings({"rawtypes", "unchecked"}) public static void reverse(List list) { @@ -1130,6 +1136,87 @@ public Stream parallelStream() { } } + /** + * Returns an unmodifiable view of the + * specified {@code SequencedCollection}. Query operations on the returned collection + * "read through" to the specified collection, and attempts to modify the returned + * collection, whether direct or via its iterator, result in an + * {@code UnsupportedOperationException}.

+ * + * The returned collection does not pass the {@code hashCode} and + * {@code equals} operations through to the backing collection, but relies on + * {@code Object}'s {@code equals} and {@code hashCode} methods. This + * is necessary to preserve the contracts of these operations in the case + * that the backing collection is a set or a list.

+ * + * The returned collection will be serializable if the specified collection + * is serializable. + * + * @implNote This method may return its argument if the argument is already unmodifiable. + * @param the class of the objects in the collection + * @param c the collection for which an unmodifiable view is to be + * returned. + * @return an unmodifiable view of the specified collection. + * @since 21 + */ + @SuppressWarnings("unchecked") + public static SequencedCollection unmodifiableSequencedCollection(SequencedCollection c) { + if (c.getClass() == UnmodifiableSequencedCollection.class) { + return (SequencedCollection) c; + } + return new UnmodifiableSequencedCollection<>(c); + } + + /** + * @serial include + */ + static class UnmodifiableSequencedCollection extends UnmodifiableCollection + implements SequencedCollection, Serializable { + + @java.io.Serial + private static final long serialVersionUID = -6060065079711684830L; + + UnmodifiableSequencedCollection(SequencedCollection c) { + super(c); + } + + @SuppressWarnings("unchecked") + private SequencedCollection sc() { + return (SequencedCollection) c; + } + + // Even though this wrapper class is serializable, the reversed view is effectively + // not serializable because it points to the reversed collection view, which usually isn't + // serializable. + public SequencedCollection reversed() { + return new UnmodifiableSequencedCollection<>(sc().reversed()); + } + + public void addFirst(E e) { + throw new UnsupportedOperationException(); + } + + public void addLast(E e) { + throw new UnsupportedOperationException(); + } + + public E getFirst() { + return sc().getFirst(); + } + + public E getLast() { + return sc().getLast(); + } + + public E removeFirst() { + throw new UnsupportedOperationException(); + } + + public E removeLast() { + throw new UnsupportedOperationException(); + } + } + /** * Returns an unmodifiable view of the * specified set. Query operations on the returned set "read through" to the specified @@ -1166,6 +1253,56 @@ static class UnmodifiableSet extends UnmodifiableCollection public int hashCode() {return c.hashCode();} } + /** + * Returns an unmodifiable view of the + * specified {@code SequencedSet}. Query operations on the returned set + * "read through" to the specified set, and attempts to modify the returned + * set, whether direct or via its iterator, result in an + * {@code UnsupportedOperationException}.

+ * + * The returned set will be serializable if the specified set + * is serializable. + * + * @implNote This method may return its argument if the argument is already unmodifiable. + * @param the class of the objects in the set + * @param s the set for which an unmodifiable view is to be returned. + * @return an unmodifiable view of the specified sequenced set. + * @since 21 + */ + @SuppressWarnings("unchecked") + public static SequencedSet unmodifiableSequencedSet(SequencedSet s) { + // Not checking for subclasses because of heap pollution and information leakage. + if (s.getClass() == UnmodifiableSequencedSet.class) { + return (SequencedSet) s; + } + return new UnmodifiableSequencedSet<>(s); + } + + /** + * @serial include + */ + static class UnmodifiableSequencedSet extends UnmodifiableSequencedCollection + implements SequencedSet, Serializable { + @java.io.Serial + private static final long serialVersionUID = -2153469532349793522L; + + UnmodifiableSequencedSet(SequencedSet s) {super(s);} + public boolean equals(Object o) {return o == this || c.equals(o);} + public int hashCode() {return c.hashCode();} + + @SuppressWarnings("unchecked") + private SequencedSet ss() { + return (SequencedSet) c; + } + + // Even though this wrapper class is serializable, the reversed view is effectively + // not serializable because it points to the reversed set view, which usually isn't + // serializable. + public SequencedSet reversed() { + return new UnmodifiableSequencedSet<>(ss().reversed()); + } + } + /** * Returns an unmodifiable view of the * specified sorted set. Query operations on the returned sorted set "read @@ -1504,7 +1641,7 @@ private static class UnmodifiableMap implements Map, Serializable { private static final long serialVersionUID = -1034234728574286014L; @SuppressWarnings("serial") // Conditionally serializable - private final Map m; + final Map m; UnmodifiableMap(Map m) { if (m==null) @@ -1828,6 +1965,72 @@ && eq(e.getKey(), t.getKey()) } } + /** + * Returns an unmodifiable view of the + * specified {@code SequencedMap}. Query operations on the returned map + * "read through" to the specified map, and attempts to modify the returned + * map, whether direct or via its collection views, result in an + * {@code UnsupportedOperationException}.

+ * + * The returned map will be serializable if the specified map + * is serializable. + * + * @implNote This method may return its argument if the argument is already unmodifiable. + * @param the class of the map keys + * @param the class of the map values + * @param m the map for which an unmodifiable view is to be returned. + * @return an unmodifiable view of the specified map. + * @since 21 + */ + @SuppressWarnings("unchecked") + public static SequencedMap unmodifiableSequencedMap(SequencedMap m) { + // Not checking for subclasses because of heap pollution and information leakage. + if (m.getClass() == UnmodifiableSequencedMap.class) { + return (SequencedMap) m; + } + return new UnmodifiableSequencedMap<>(m); + } + + /** + * @serial include + */ + private static class UnmodifiableSequencedMap extends UnmodifiableMap implements SequencedMap, Serializable { + @java.io.Serial + private static final long serialVersionUID = -8171676257373950636L; + + UnmodifiableSequencedMap(Map m) { + super(m); + } + + @SuppressWarnings("unchecked") + private SequencedMap sm() { + return (SequencedMap) m; + } + + // Even though this wrapper class is serializable, the reversed view is effectively + // not serializable because it points to the reversed map view, which usually isn't + // serializable. + public SequencedMap reversed() { + return new UnmodifiableSequencedMap<>(sm().reversed()); + } + + public Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + public Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + public V putFirst(K k, V v) { + throw new UnsupportedOperationException(); + } + + public V putLast(K k, V v) { + throw new UnsupportedOperationException(); + } + } + /** * Returns an unmodifiable view of the * specified sorted map. Query operations on the returned sorted map "read through" @@ -5326,6 +5529,14 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound * * The returned comparator is serializable. * + * @apiNote + * This method returns a {@code Comparator} that is suitable for sorting + * elements in reverse order. To obtain a reverse-ordered view of a + * sequenced collection, use the {@link SequencedCollection#reversed + * SequencedCollection.reversed} method. Or, to obtain a reverse-ordered + * view of a sequenced map, use the {@link SequencedMap#reversed + * SequencedMap.reversed} method. + * * @param the class of the objects compared by the comparator * @return A comparator that imposes the reverse of the natural * ordering on a collection of objects that implement @@ -5372,6 +5583,14 @@ public Comparator> reversed() { *

The returned comparator is serializable (assuming the specified * comparator is also serializable or {@code null}). * + * @apiNote + * This method returns a {@code Comparator} that is suitable for sorting + * elements in reverse order. To obtain a reverse-ordered view of a + * sequenced collection, use the {@link SequencedCollection#reversed + * SequencedCollection.reversed} method. Or, to obtain a reverse-ordered + * view of a sequenced map, use the {@link SequencedMap#reversed + * SequencedMap.reversed} method. + * * @param the class of the objects compared by the comparator * @param cmp a comparator who's ordering is to be reversed by the returned * comparator or {@code null} @@ -5682,6 +5901,8 @@ public static boolean addAll(Collection c, T... elements) { * @since 1.6 */ public static Set newSetFromMap(Map map) { + if (! map.isEmpty()) // implicit null check + throw new IllegalArgumentException("Map is non-empty"); return new SetFromMap<>(map); } @@ -5692,12 +5913,10 @@ private static class SetFromMap extends AbstractSet implements Set, Serializable { @SuppressWarnings("serial") // Conditionally serializable - private final Map m; // The backing map + final Map m; // The backing map private transient Set s; // Its keySet SetFromMap(Map map) { - if (!map.isEmpty()) - throw new IllegalArgumentException("Map is non-empty"); m = map; s = map.keySet(); } @@ -5746,6 +5965,91 @@ private void readObject(java.io.ObjectInputStream stream) stream.defaultReadObject(); s = m.keySet(); } + + @java.io.Serial + private void readObjectNoData() throws java.io.ObjectStreamException { + throw new java.io.InvalidObjectException("missing SetFromMap data"); + } + } + + /** + * Returns a sequenced set backed by the specified map. The resulting set displays + * the same ordering, concurrency, and performance characteristics as the + * backing map. In essence, this factory method provides a {@link SequencedSet} + * implementation corresponding to any {@link SequencedMap} implementation. + * + *

Each method invocation on the set returned by this method results in + * exactly one method invocation on the backing map or its {@code keySet} + * view, with one exception. The {@code addAll} method is implemented + * as a sequence of {@code put} invocations on the backing map. + * + *

The specified map must be empty at the time this method is invoked, + * and should not be accessed directly after this method returns. These + * conditions are ensured if the map is created empty, passed directly + * to this method, and no reference to the map is retained. + * + * @apiNote + * The following example code creates a {@code SequencedSet} from a + * {@code LinkedHashMap}. This differs from a {@code LinkedHashSet} + * in that the map's {@code removeEldestEntry} is overridden to provide + * an eviction policy, which is not possible with a {@code LinkedHashSet}. + * + * {@snippet : + * SequencedSet set = Collections.newSequencedSetFromMap( + * new LinkedHashMap() { + * protected boolean removeEldestEntry(Map.Entry e) { + * return this.size() > 5; + * } + * }); + * } + * + * @param the class of the map keys and of the objects in the + * returned set + * @param map the backing map + * @return the set backed by the map + * @throws IllegalArgumentException if {@code map} is not empty + * @since 21 + */ + public static SequencedSet newSequencedSetFromMap(SequencedMap map) { + if (! map.isEmpty()) // implicit null check + throw new IllegalArgumentException("Map is non-empty"); + return new SequencedSetFromMap<>(map); + } + + /** + * @serial include + */ + private static class SequencedSetFromMap extends SetFromMap implements SequencedSet { + private E nsee(Map.Entry e) { + if (e == null) { + throw new NoSuchElementException(); + } else { + return e.getKey(); + } + } + + private SequencedMap map() { + return (SequencedMap) super.m; + } + + SequencedSetFromMap(SequencedMap map) { + super(map); + } + + // Even though this wrapper class is serializable, the reversed view is effectively + // not serializable because it points to the reversed map view, which usually isn't + // serializable. + public SequencedSet reversed() { return new SequencedSetFromMap<>(map().reversed()); } + + public void addFirst(E e) { map().putFirst(e, Boolean.TRUE); } + public void addLast(E e) { map().putLast(e, Boolean.TRUE); } + public E getFirst() { return nsee(map().firstEntry()); } + public E getLast() { return nsee(map().lastEntry()); } + public E removeFirst() { return nsee(map().pollFirstEntry()); } + public E removeLast() { return nsee(map().pollLastEntry()); } + + @java.io.Serial + private static final long serialVersionUID = -3943479744841433802L; } /** @@ -5761,6 +6065,11 @@ private void readObject(java.io.ObjectInputStream stream) * implemented as a sequence of {@link Deque#addFirst addFirst} * invocations on the backing deque. * + * @apiNote + * This method provides a view that inverts the sense of certain operations, + * but it doesn't reverse the encounter order. To obtain a reverse-ordered + * view, use the {@link Deque#reversed Deque.reversed} method. + * * @param the class of the objects in the deque * @param deque the deque * @return the queue diff --git a/src/java.base/share/classes/java/util/Deque.java b/src/java.base/share/classes/java/util/Deque.java index 90d7292c31a90..13a2e150d97c6 100644 --- a/src/java.base/share/classes/java/util/Deque.java +++ b/src/java.base/share/classes/java/util/Deque.java @@ -201,7 +201,7 @@ * @since 1.6 * @param the type of elements held in this deque */ -public interface Deque extends Queue { +public interface Deque extends Queue, SequencedCollection { /** * Inserts the specified element at the front of this deque if it is * possible to do so immediately without violating capacity restrictions, @@ -613,4 +613,17 @@ public interface Deque extends Queue { */ Iterator descendingIterator(); + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface returns an instance of a reverse-ordered + * Deque that delegates its operations to this Deque. + * + * @return a reverse-ordered view of this collection, as a {@code Deque} + * @since 21 + */ + default Deque reversed() { + return ReverseOrderDequeView.of(this); + } } diff --git a/src/java.base/share/classes/java/util/HashSet.java b/src/java.base/share/classes/java/util/HashSet.java index 9d4cdb346f2ac..e59bf7b089a6a 100644 --- a/src/java.base/share/classes/java/util/HashSet.java +++ b/src/java.base/share/classes/java/util/HashSet.java @@ -94,10 +94,10 @@ public class HashSet @java.io.Serial static final long serialVersionUID = -5024744406713321676L; - private transient HashMap map; + transient HashMap map; // Dummy value to associate with an Object in the backing Map - private static final Object PRESENT = new Object(); + static final Object PRESENT = new Object(); /** * Constructs a new, empty set; the backing {@code HashMap} instance has diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 3de7e1d5eae79..398ebb14a54da 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -331,6 +331,11 @@ public boolean contains(Object o) { return indexOf(o) >= 0; } + @Override + public List reversed() { + return ReverseOrderListView.of(this, false); + } + IndexOutOfBoundsException outOfBounds(int index) { return new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); } diff --git a/src/java.base/share/classes/java/util/LinkedHashMap.java b/src/java.base/share/classes/java/util/LinkedHashMap.java index 1984043f0d0b3..d4052f396bfa9 100644 --- a/src/java.base/share/classes/java/util/LinkedHashMap.java +++ b/src/java.base/share/classes/java/util/LinkedHashMap.java @@ -29,18 +29,23 @@ import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.io.IOException; +import java.util.function.Function; /** *

Hash table and linked list implementation of the {@code Map} interface, - * with predictable iteration order. This implementation differs from - * {@code HashMap} in that it maintains a doubly-linked list running through - * all of its entries. This linked list defines the iteration ordering, + * with well-defined encounter order. This implementation differs from + * {@code HashMap} in that it maintains a doubly-linked list running through all of + * its entries. This linked list defines the encounter order (the order of iteration), * which is normally the order in which keys were inserted into the map - * (insertion-order). Note that insertion order is not affected - * if a key is re-inserted into the map. (A key {@code k} is - * reinserted into a map {@code m} if {@code m.put(k, v)} is invoked when + * (insertion-order). The least recently inserted entry (the eldest) is + * first, and the youngest entry is last. Note that encounter order is not affected + * if a key is re-inserted into the map with the {@code put} method. (A key + * {@code k} is reinserted into a map {@code m} if {@code m.put(k, v)} is invoked when * {@code m.containsKey(k)} would return {@code true} immediately prior to - * the invocation.) + * the invocation.) The reverse-ordered view of this map is in the opposite order, with + * the youngest entry appearing first and the eldest entry appearing last. + * The encounter order of entries already in the map can be changed by using + * the {@link #putFirst putFirst} and {@link #putLast putLast} methods. * *

This implementation spares its clients from the unspecified, generally * chaotic ordering provided by {@link HashMap} (and {@link Hashtable}), @@ -59,7 +64,7 @@ * order they were presented.) * *

A special {@link #LinkedHashMap(int,float,boolean) constructor} is - * provided to create a linked hash map whose order of iteration is the order + * provided to create a linked hash map whose encounter order is the order * in which its entries were last accessed, from least-recently accessed to * most-recently (access-order). This kind of map is well-suited to * building LRU caches. Invoking the {@code put}, {@code putIfAbsent}, @@ -70,16 +75,24 @@ * of the entry if the value is replaced. The {@code putAll} method generates one * entry access for each mapping in the specified map, in the order that * key-value mappings are provided by the specified map's entry set iterator. - * No other methods generate entry accesses. In particular, operations - * on collection-views do not affect the order of iteration of the - * backing map. + * No other methods generate entry accesses. Invoking these methods on the + * reversed view generates accesses to entries on the backing map. Note that in the + * reversed view, an access to an entry moves it first in encounter order. + * Explicit-positioning methods such as {@code putFirst} or {@code lastEntry}, whether on + * the map or on its reverse-ordered view, perform the positioning operation and + * do not generate entry accesses. Operations on the {@code keySet}, {@code values}, + * and {@code entrySet} views or on their sequenced counterparts do not affect + * the encounter order of the backing map. * *

The {@link #removeEldestEntry(Map.Entry)} method may be overridden to * impose a policy for removing stale mappings automatically when new mappings - * are added to the map. + * are added to the map. Alternatively, since the "eldest" entry is the first + * entry in encounter order, programs can inspect and remove stale mappings through + * use of the {@link #firstEntry firstEntry} and {@link #pollFirstEntry pollFirstEntry} + * methods. * - *

This class provides all of the optional {@code Map} operations, and - * permits null elements. Like {@code HashMap}, it provides constant-time + *

This class provides all of the optional {@code Map} and {@code SequencedMap} operations, + * and it permits null elements. Like {@code HashMap}, it provides constant-time * performance for the basic operations ({@code add}, {@code contains} and * {@code remove}), assuming the hash function disperses elements * properly among the buckets. Performance is likely to be just slightly @@ -162,7 +175,7 @@ */ public class LinkedHashMap extends HashMap - implements Map + implements SequencedMap { /* @@ -220,14 +233,25 @@ static class Entry extends HashMap.Node { // internal utilities // link at the end of list - private void linkNodeLast(LinkedHashMap.Entry p) { - LinkedHashMap.Entry last = tail; - tail = p; - if (last == null) + private void linkNodeAtEnd(LinkedHashMap.Entry p) { + if (putMode == PUT_FIRST) { + LinkedHashMap.Entry first = head; head = p; - else { - p.before = last; - last.after = p; + if (first == null) + tail = p; + else { + p.after = first; + first.before = p; + } + } else { + LinkedHashMap.Entry last = tail; + tail = p; + if (last == null) + head = p; + else { + p.before = last; + last.after = p; + } } } @@ -256,7 +280,7 @@ void reinitialize() { Node newNode(int hash, K key, V value, Node e) { LinkedHashMap.Entry p = new LinkedHashMap.Entry<>(hash, key, value, e); - linkNodeLast(p); + linkNodeAtEnd(p); return p; } @@ -270,7 +294,7 @@ Node replacementNode(Node p, Node next) { TreeNode newTreeNode(int hash, K key, V value, Node next) { TreeNode p = new TreeNode<>(hash, key, value, next); - linkNodeLast(p); + linkNodeAtEnd(p); return p; } @@ -303,9 +327,17 @@ void afterNodeInsertion(boolean evict) { // possibly remove eldest } } - void afterNodeAccess(Node e) { // move node to last + static final int PUT_NORM = 0; + static final int PUT_FIRST = 1; + static final int PUT_LAST = 2; + int putMode = PUT_NORM; + + // Called after update, but not after insertion + void afterNodeAccess(Node e) { LinkedHashMap.Entry last; - if (accessOrder && (last = tail) != e) { + LinkedHashMap.Entry first; + if ((putMode == PUT_LAST || (putMode == PUT_NORM && accessOrder)) && (last = tail) != e) { + // move node to last LinkedHashMap.Entry p = (LinkedHashMap.Entry)e, b = p.before, a = p.after; p.after = null; @@ -325,6 +357,61 @@ void afterNodeAccess(Node e) { // move node to last } tail = p; ++modCount; + } else if (putMode == PUT_FIRST && (first = head) != e) { + // move node to first + LinkedHashMap.Entry p = + (LinkedHashMap.Entry)e, b = p.before, a = p.after; + p.before = null; + if (a == null) + tail = b; + else + a.before = b; + if (b != null) + b.after = a; + else + first = a; + if (first == null) + tail = p; + else { + p.after = first; + first.before = p; + } + head = p; + ++modCount; + } + } + + /** + * {@inheritDoc} + *

+ * If this map already contains a mapping for this key, the mapping is relocated if necessary + * so that it is first in encounter order. + * + * @since 21 + */ + public V putFirst(K k, V v) { + try { + putMode = PUT_FIRST; + return this.put(k, v); + } finally { + putMode = PUT_NORM; + } + } + + /** + * {@inheritDoc} + *

+ * If this map already contains a mapping for this key, the mapping is relocated if necessary + * so that it is last in encounter order. + * + * @since 21 + */ + public V putLast(K k, V v) { + try { + putMode = PUT_LAST; + return this.put(k, v); + } finally { + putMode = PUT_NORM; } } @@ -519,8 +606,9 @@ protected boolean removeEldestEntry(Map.Entry eldest) { } /** - * Returns a {@link Set} view of the keys contained in this map. - * The set is backed by the map, so changes to the map are + * Returns a {@link Set} view of the keys contained in this map. The encounter + * order of the keys in the view matches the encounter order of mappings of + * this map. The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. If the map is modified * while an iteration over the set is in progress (except through * the iterator's own {@code remove} operation), the results of @@ -537,39 +625,79 @@ protected boolean removeEldestEntry(Map.Entry eldest) { * @return a set view of the keys contained in this map */ public Set keySet() { + return sequencedKeySet(); + } + + /** + * {@inheritDoc} + *

+ * The returned view has the same characteristics as specified for the view + * returned by the {@link #keySet keySet} method. + * + * @return {@inheritDoc} + * @since 21 + */ + public SequencedSet sequencedKeySet() { Set ks = keySet; if (ks == null) { - ks = new LinkedKeySet(); - keySet = ks; + SequencedSet sks = new LinkedKeySet(false); + keySet = sks; + return sks; + } else { + // The cast should never fail, since the only assignment of non-null to keySet is + // above, and assignments in AbstractMap and HashMap are in overridden methods. + return (SequencedSet) ks; } - return ks; } - @Override + static Node nsee(Node node) { + if (node == null) + throw new NoSuchElementException(); + else + return node; + } + final T[] keysToArray(T[] a) { + return keysToArray(a, false); + } + + final T[] keysToArray(T[] a, boolean reversed) { Object[] r = a; int idx = 0; - for (LinkedHashMap.Entry e = head; e != null; e = e.after) { - r[idx++] = e.key; + if (reversed) { + for (LinkedHashMap.Entry e = tail; e != null; e = e.before) { + r[idx++] = e.key; + } + } else { + for (LinkedHashMap.Entry e = head; e != null; e = e.after) { + r[idx++] = e.key; + } } return a; } - @Override - final T[] valuesToArray(T[] a) { + final T[] valuesToArray(T[] a, boolean reversed) { Object[] r = a; int idx = 0; - for (LinkedHashMap.Entry e = head; e != null; e = e.after) { - r[idx++] = e.value; + if (reversed) { + for (LinkedHashMap.Entry e = tail; e != null; e = e.before) { + r[idx++] = e.value; + } + } else { + for (LinkedHashMap.Entry e = head; e != null; e = e.after) { + r[idx++] = e.value; + } } return a; } - final class LinkedKeySet extends AbstractSet { + final class LinkedKeySet extends AbstractSet implements SequencedSet { + final boolean reversed; + LinkedKeySet(boolean reversed) { this.reversed = reversed; } public final int size() { return size; } public final void clear() { LinkedHashMap.this.clear(); } public final Iterator iterator() { - return new LinkedKeyIterator(); + return new LinkedKeyIterator(reversed); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { @@ -582,27 +710,54 @@ public final Spliterator spliterator() { } public Object[] toArray() { - return keysToArray(new Object[size]); + return keysToArray(new Object[size], reversed); } public T[] toArray(T[] a) { - return keysToArray(prepareArray(a)); + return keysToArray(prepareArray(a), reversed); } public final void forEach(Consumer action) { if (action == null) throw new NullPointerException(); int mc = modCount; - for (LinkedHashMap.Entry e = head; e != null; e = e.after) - action.accept(e.key); + if (reversed) { + for (LinkedHashMap.Entry e = tail; e != null; e = e.before) + action.accept(e.key); + } else { + for (LinkedHashMap.Entry e = head; e != null; e = e.after) + action.accept(e.key); + } if (modCount != mc) throw new ConcurrentModificationException(); } + public final void addFirst(K k) { throw new UnsupportedOperationException(); } + public final void addLast(K k) { throw new UnsupportedOperationException(); } + public final K getFirst() { return nsee(reversed ? tail : head).key; } + public final K getLast() { return nsee(reversed ? head : tail).key; } + public final K removeFirst() { + var node = nsee(reversed ? tail : head); + removeNode(node.hash, node.key, null, false, false); + return node.key; + } + public final K removeLast() { + var node = nsee(reversed ? head : tail); + removeNode(node.hash, node.key, null, false, false); + return node.key; + } + public SequencedSet reversed() { + if (reversed) { + return LinkedHashMap.this.sequencedKeySet(); + } else { + return new LinkedKeySet(true); + } + } } /** - * Returns a {@link Collection} view of the values contained in this map. - * The collection is backed by the map, so changes to the map are + * Returns a {@link Collection} view of the values contained in this map. The + * encounter order of values in the view matches the encounter order of entries in + * this map. The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa. If the map is * modified while an iteration over the collection is in progress * (except through the iterator's own {@code remove} operation), @@ -619,19 +774,38 @@ public final void forEach(Consumer action) { * @return a view of the values contained in this map */ public Collection values() { + return sequencedValues(); + } + + /** + * {@inheritDoc} + *

+ * The returned view has the same characteristics as specified for the view + * returned by the {@link #values values} method. + * + * @return {@inheritDoc} + * @since 21 + */ + public SequencedCollection sequencedValues() { Collection vs = values; if (vs == null) { - vs = new LinkedValues(); - values = vs; + SequencedCollection svs = new LinkedValues(false); + values = svs; + return svs; + } else { + // The cast should never fail, since the only assignment of non-null to values is + // above, and assignments in AbstractMap and HashMap are in overridden methods. + return (SequencedCollection) vs; } - return vs; } - final class LinkedValues extends AbstractCollection { + final class LinkedValues extends AbstractCollection implements SequencedCollection { + final boolean reversed; + LinkedValues(boolean reversed) { this.reversed = reversed; } public final int size() { return size; } public final void clear() { LinkedHashMap.this.clear(); } public final Iterator iterator() { - return new LinkedValueIterator(); + return new LinkedValueIterator(reversed); } public final boolean contains(Object o) { return containsValue(o); } public final Spliterator spliterator() { @@ -640,26 +814,53 @@ public final Spliterator spliterator() { } public Object[] toArray() { - return valuesToArray(new Object[size]); + return valuesToArray(new Object[size], reversed); } public T[] toArray(T[] a) { - return valuesToArray(prepareArray(a)); + return valuesToArray(prepareArray(a), reversed); } public final void forEach(Consumer action) { if (action == null) throw new NullPointerException(); int mc = modCount; - for (LinkedHashMap.Entry e = head; e != null; e = e.after) - action.accept(e.value); + if (reversed) { + for (LinkedHashMap.Entry e = tail; e != null; e = e.before) + action.accept(e.value); + } else { + for (LinkedHashMap.Entry e = head; e != null; e = e.after) + action.accept(e.value); + } if (modCount != mc) throw new ConcurrentModificationException(); } + public final void addFirst(V v) { throw new UnsupportedOperationException(); } + public final void addLast(V v) { throw new UnsupportedOperationException(); } + public final V getFirst() { return nsee(reversed ? tail : head).value; } + public final V getLast() { return nsee(reversed ? head : tail).value; } + public final V removeFirst() { + var node = nsee(reversed ? tail : head); + removeNode(node.hash, node.key, null, false, false); + return node.value; + } + public final V removeLast() { + var node = nsee(reversed ? head : tail); + removeNode(node.hash, node.key, null, false, false); + return node.value; + } + public SequencedCollection reversed() { + if (reversed) { + return LinkedHashMap.this.sequencedValues(); + } else { + return new LinkedValues(true); + } + } } /** - * Returns a {@link Set} view of the mappings contained in this map. + * Returns a {@link Set} view of the mappings contained in this map. The encounter + * order of the view matches the encounter order of entries of this map. * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. If the map is modified * while an iteration over the set is in progress (except through @@ -678,15 +879,39 @@ public final void forEach(Consumer action) { * @return a set view of the mappings contained in this map */ public Set> entrySet() { - Set> es; - return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es; + return sequencedEntrySet(); } - final class LinkedEntrySet extends AbstractSet> { + /** + * {@inheritDoc} + *

+ * The returned view has the same characteristics as specified for the view + * returned by the {@link #entrySet entrySet} method. + * + * @return {@inheritDoc} + * @since 21 + */ + public SequencedSet> sequencedEntrySet() { + Set> es = entrySet; + if (es == null) { + SequencedSet> ses = new LinkedEntrySet(false); + entrySet = ses; + return ses; + } else { + // The cast should never fail, since the only assignment of non-null to entrySet is + // above, and assignments in HashMap are in overridden methods. + return (SequencedSet>) es; + } + } + + final class LinkedEntrySet extends AbstractSet> + implements SequencedSet> { + final boolean reversed; + LinkedEntrySet(boolean reversed) { this.reversed = reversed; } public final int size() { return size; } public final void clear() { LinkedHashMap.this.clear(); } public final Iterator> iterator() { - return new LinkedEntryIterator(); + return new LinkedEntryIterator(reversed); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry e)) @@ -712,11 +937,43 @@ public final void forEach(Consumer> action) { if (action == null) throw new NullPointerException(); int mc = modCount; - for (LinkedHashMap.Entry e = head; e != null; e = e.after) - action.accept(e); + if (reversed) { + for (LinkedHashMap.Entry e = tail; e != null; e = e.before) + action.accept(e); + } else { + for (LinkedHashMap.Entry e = head; e != null; e = e.after) + action.accept(e); + } if (modCount != mc) throw new ConcurrentModificationException(); } + final Node nsee(Node e) { + if (e == null) + throw new NoSuchElementException(); + else + return e; + } + public final void addFirst(Map.Entry e) { throw new UnsupportedOperationException(); } + public final void addLast(Map.Entry e) { throw new UnsupportedOperationException(); } + public final Map.Entry getFirst() { return nsee(reversed ? tail : head); } + public final Map.Entry getLast() { return nsee(reversed ? head : tail); } + public final Map.Entry removeFirst() { + var node = nsee(reversed ? tail : head); + removeNode(node.hash, node.key, null, false, false); + return node; + } + public final Map.Entry removeLast() { + var node = nsee(reversed ? head : tail); + removeNode(node.hash, node.key, null, false, false); + return node; + } + public SequencedSet> reversed() { + if (reversed) { + return LinkedHashMap.this.sequencedEntrySet(); + } else { + return new LinkedEntrySet(true); + } + } } // Map overrides @@ -747,9 +1004,11 @@ abstract class LinkedHashIterator { LinkedHashMap.Entry next; LinkedHashMap.Entry current; int expectedModCount; + boolean reversed; - LinkedHashIterator() { - next = head; + LinkedHashIterator(boolean reversed) { + this.reversed = reversed; + next = reversed ? tail : head; expectedModCount = modCount; current = null; } @@ -765,7 +1024,7 @@ final LinkedHashMap.Entry nextNode() { if (e == null) throw new NoSuchElementException(); current = e; - next = e.after; + next = reversed ? e.before : e.after; return e; } @@ -783,16 +1042,19 @@ public final void remove() { final class LinkedKeyIterator extends LinkedHashIterator implements Iterator { + LinkedKeyIterator(boolean reversed) { super(reversed); } public final K next() { return nextNode().getKey(); } } final class LinkedValueIterator extends LinkedHashIterator implements Iterator { + LinkedValueIterator(boolean reversed) { super(reversed); } public final V next() { return nextNode().value; } } final class LinkedEntryIterator extends LinkedHashIterator implements Iterator> { + LinkedEntryIterator(boolean reversed) { super(reversed); } public final Map.Entry next() { return nextNode(); } } @@ -816,4 +1078,175 @@ public static LinkedHashMap newLinkedHashMap(int numMappings) { return new LinkedHashMap<>(HashMap.calculateHashMapCapacity(numMappings)); } + // Reversed View + + /** + * {@inheritDoc} + *

+ * Modifications to the reversed view and its map views are permitted and will be + * propagated to this map. In addition, modifications to this map will be visible + * in the reversed view and its map views. + * + * @return {@inheritDoc} + * @since 21 + */ + public SequencedMap reversed() { + return new ReversedLinkedHashMapView<>(this); + } + + static class ReversedLinkedHashMapView extends AbstractMap + implements SequencedMap { + final LinkedHashMap base; + + ReversedLinkedHashMapView(LinkedHashMap lhm) { + base = lhm; + } + + // Object + // inherit toString() from AbstractMap; it depends on entrySet() + + public boolean equals(Object o) { + return base.equals(o); + } + + public int hashCode() { + return base.hashCode(); + } + + // Map + + public int size() { + return base.size(); + } + + public boolean isEmpty() { + return base.isEmpty(); + } + + public boolean containsKey(Object key) { + return base.containsKey(key); + } + + public boolean containsValue(Object value) { + return base.containsValue(value); + } + + public V get(Object key) { + return base.get(key); + } + + public V put(K key, V value) { + return base.put(key, value); + } + + public V remove(Object key) { + return base.remove(key); + } + + public void putAll(Map m) { + base.putAll(m); + } + + public void clear() { + base.clear(); + } + + public Set keySet() { + return base.sequencedKeySet().reversed(); + } + + public Collection values() { + return base.sequencedValues().reversed(); + } + + public Set> entrySet() { + return base.sequencedEntrySet().reversed(); + } + + public V getOrDefault(Object key, V defaultValue) { + return base.getOrDefault(key, defaultValue); + } + + public void forEach(BiConsumer action) { + if (action == null) + throw new NullPointerException(); + int mc = base.modCount; + for (LinkedHashMap.Entry e = base.tail; e != null; e = e.before) + action.accept(e.key, e.value); + if (base.modCount != mc) + throw new ConcurrentModificationException(); + } + + public void replaceAll(BiFunction function) { + if (function == null) + throw new NullPointerException(); + int mc = base.modCount; + for (LinkedHashMap.Entry e = base.tail; e != null; e = e.before) + e.value = function.apply(e.key, e.value); + if (base.modCount != mc) + throw new ConcurrentModificationException(); + } + + public V putIfAbsent(K key, V value) { + return base.putIfAbsent(key, value); + } + + public boolean remove(Object key, Object value) { + return base.remove(key, value); + } + + public boolean replace(K key, V oldValue, V newValue) { + return base.replace(key, oldValue, newValue); + } + + public V replace(K key, V value) { + return base.replace(key, value); + } + + public V computeIfAbsent(K key, Function mappingFunction) { + return base.computeIfAbsent(key, mappingFunction); + } + + public V computeIfPresent(K key, BiFunction remappingFunction) { + return base.computeIfPresent(key, remappingFunction); + } + + public V compute(K key, BiFunction remappingFunction) { + return base.compute(key, remappingFunction); + } + + public V merge(K key, V value, BiFunction remappingFunction) { + return base.merge(key, value, remappingFunction); + } + + // SequencedMap + + public SequencedMap reversed() { + return base; + } + + public Entry firstEntry() { + return base.lastEntry(); + } + + public Entry lastEntry() { + return base.firstEntry(); + } + + public Entry pollFirstEntry() { + return base.pollLastEntry(); + } + + public Entry pollLastEntry() { + return base.pollFirstEntry(); + } + + public V putFirst(K k, V v) { + return base.putLast(k, v); + } + + public V putLast(K k, V v) { + return base.putFirst(k, v); + } + } } diff --git a/src/java.base/share/classes/java/util/LinkedHashSet.java b/src/java.base/share/classes/java/util/LinkedHashSet.java index 330e37f8be3e0..fd9b52e258915 100644 --- a/src/java.base/share/classes/java/util/LinkedHashSet.java +++ b/src/java.base/share/classes/java/util/LinkedHashSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -27,15 +27,19 @@ /** *

Hash table and linked list implementation of the {@code Set} interface, - * with predictable iteration order. This implementation differs from + * with well-defined encounter order. This implementation differs from * {@code HashSet} in that it maintains a doubly-linked list running through - * all of its entries. This linked list defines the iteration ordering, - * which is the order in which elements were inserted into the set - * (insertion-order). Note that insertion order is not affected - * if an element is re-inserted into the set. (An element {@code e} - * is reinserted into a set {@code s} if {@code s.add(e)} is invoked when - * {@code s.contains(e)} would return {@code true} immediately prior to - * the invocation.) + * all of its entries. This linked list defines the encounter order (iteration + * order), which is the order in which elements were inserted into the set + * (insertion-order). The least recently inserted element (the eldest) is + * first, and the youngest element is last. Note that encounter order is not affected + * if an element is re-inserted into the set with the {@code add} method. + * (An element {@code e} is reinserted into a set {@code s} if {@code s.add(e)} is + * invoked when {@code s.contains(e)} would return {@code true} immediately prior to + * the invocation.) The reverse-ordered view of this set is in the opposite order, with + * the youngest element appearing first and the eldest element appearing last. The encounter + * order of elements already in the set can be changed by using the + * {@link #addFirst addFirst} and {@link #addLast addLast} methods. * *

This implementation spares its clients from the unspecified, generally * chaotic ordering provided by {@link HashSet}, without incurring the @@ -53,8 +57,8 @@ * the copy. (Clients generally appreciate having things returned in the same * order they were presented.) * - *

This class provides all of the optional {@code Set} operations, and - * permits null elements. Like {@code HashSet}, it provides constant-time + *

This class provides all of the optional {@link Set} and {@link SequencedSet} + * operations, and it permits null elements. Like {@code HashSet}, it provides constant-time * performance for the basic operations ({@code add}, {@code contains} and * {@code remove}), assuming the hash function disperses elements * properly among the buckets. Performance is likely to be just slightly @@ -117,7 +121,7 @@ public class LinkedHashSet extends HashSet - implements Set, Cloneable, java.io.Serializable { + implements SequencedSet, Cloneable, java.io.Serializable { @java.io.Serial private static final long serialVersionUID = -2851667679971038690L; @@ -221,4 +225,100 @@ public static LinkedHashSet newLinkedHashSet(int numElements) { return new LinkedHashSet<>(HashMap.calculateHashMapCapacity(numElements)); } + @SuppressWarnings("unchecked") + LinkedHashMap map() { + return (LinkedHashMap) map; + } + + /** + * {@inheritDoc} + *

+ * If this set already contains the element, it is relocated if necessary so that it is + * first in encounter order. + * + * @since 21 + */ + public void addFirst(E e) { + map().putFirst(e, PRESENT); + } + + /** + * {@inheritDoc} + *

+ * If this set already contains the element, it is relocated if necessary so that it is + * last in encounter order. + * + * @since 21 + */ + public void addLast(E e) { + map().putLast(e, PRESENT); + } + + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E getFirst() { + return map().sequencedKeySet().getFirst(); + } + + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E getLast() { + return map().sequencedKeySet().getLast(); + } + + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E removeFirst() { + return map().sequencedKeySet().removeFirst(); + } + + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E removeLast() { + return map().sequencedKeySet().removeLast(); + } + + /** + * {@inheritDoc} + *

+ * Modifications to the reversed view are permitted and will be propagated to this set. + * In addition, modifications to this set will be visible in the reversed view. + * + * @return {@inheritDoc} + * @since 21 + */ + public SequencedSet reversed() { + class ReverseLinkedHashSetView extends AbstractSet implements SequencedSet { + public int size() { return LinkedHashSet.this.size(); } + public Iterator iterator() { return map().sequencedKeySet().reversed().iterator(); } + public boolean add(E e) { return LinkedHashSet.this.add(e); } + public void addFirst(E e) { LinkedHashSet.this.addLast(e); } + public void addLast(E e) { LinkedHashSet.this.addFirst(e); } + public E getFirst() { return LinkedHashSet.this.getLast(); } + public E getLast() { return LinkedHashSet.this.getFirst(); } + public E removeFirst() { return LinkedHashSet.this.removeLast(); } + public E removeLast() { return LinkedHashSet.this.removeFirst(); } + public SequencedSet reversed() { return LinkedHashSet.this; } + public Object[] toArray() { return map().keysToArray(new Object[map.size()], true); } + public T[] toArray(T[] a) { return map().keysToArray(map.prepareArray(a), true); } + } + + return new ReverseLinkedHashSetView(); + } } diff --git a/src/java.base/share/classes/java/util/LinkedList.java b/src/java.base/share/classes/java/util/LinkedList.java index a504b4e83907c..d7c67c9862839 100644 --- a/src/java.base/share/classes/java/util/LinkedList.java +++ b/src/java.base/share/classes/java/util/LinkedList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -25,7 +25,14 @@ package java.util; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; /** * Doubly-linked list implementation of the {@code List} and {@code Deque} @@ -1266,4 +1273,267 @@ public int characteristics() { } } + /** + * {@inheritDoc} + *

+ * Modifications to the reversed view are permitted and will be propagated to this list. + * In addition, modifications to this list will be visible in the reversed view. + * + * @return {@inheritDoc} + * @since 21 + */ + public LinkedList reversed() { + return new ReverseOrderLinkedListView<>(this, super.reversed(), Deque.super.reversed()); + } + + // all operations are delegated to the reverse-ordered views. + // TODO audit all overridden methods + @SuppressWarnings("serial") + static class ReverseOrderLinkedListView extends LinkedList implements java.io.Externalizable { + final LinkedList list; + final List rlist; + final Deque rdeque; + + ReverseOrderLinkedListView(LinkedList list, List rlist, Deque rdeque) { + this.list = list; + this.rlist = rlist; + this.rdeque = rdeque; + } + + public String toString() { + return rlist.toString(); + } + + public boolean retainAll(Collection c) { + return rlist.retainAll(c); + } + + public boolean removeAll(Collection c) { + return rlist.removeAll(c); + } + + public boolean containsAll(Collection c) { + return rlist.containsAll(c); + } + + public boolean isEmpty() { + return rlist.isEmpty(); + } + + public Stream parallelStream() { + return rlist.parallelStream(); + } + + public Stream stream() { + return rlist.stream(); + } + + public boolean removeIf(Predicate filter) { + return rlist.removeIf(filter); + } + + public T[] toArray(IntFunction generator) { + return rlist.toArray(generator); + } + + public void forEach(Consumer action) { + rlist.forEach(action); + } + + public Iterator iterator() { + return rlist.iterator(); + } + + public int hashCode() { + return rlist.hashCode(); + } + + public boolean equals(Object o) { + return rlist.equals(o); + } + + public List subList(int fromIndex, int toIndex) { + return rlist.subList(fromIndex, toIndex); + } + + public ListIterator listIterator() { + return rlist.listIterator(); + } + + public void sort(Comparator c) { + rlist.sort(c); + } + + public void replaceAll(UnaryOperator operator) { + rlist.replaceAll(operator); + } + + public LinkedList reversed() { + return list; + } + + public Spliterator spliterator() { + return rlist.spliterator(); + } + + public T[] toArray(T[] a) { + return rlist.toArray(a); + } + + public Object[] toArray() { + return rlist.toArray(); + } + + public Iterator descendingIterator() { + return rdeque.descendingIterator(); + } + + public ListIterator listIterator(int index) { + return rlist.listIterator(index); + } + + public boolean removeLastOccurrence(Object o) { + return rdeque.removeLastOccurrence(o); + } + + public boolean removeFirstOccurrence(Object o) { + return rdeque.removeFirstOccurrence(o); + } + + public E pop() { + return rdeque.pop(); + } + + public void push(E e) { + rdeque.push(e); + } + + public E pollLast() { + return rdeque.pollLast(); + } + + public E pollFirst() { + return rdeque.pollFirst(); + } + + public E peekLast() { + return rdeque.peekLast(); + } + + public E peekFirst() { + return rdeque.peekFirst(); + } + + public boolean offerLast(E e) { + return rdeque.offerLast(e); + } + + public boolean offerFirst(E e) { + return rdeque.offerFirst(e); + } + + public boolean offer(E e) { + return rdeque.offer(e); + } + + public E remove() { + return rdeque.remove(); + } + + public E poll() { + return rdeque.poll(); + } + + public E element() { + return rdeque.element(); + } + + public E peek() { + return rdeque.peek(); + } + + public int lastIndexOf(Object o) { + return rlist.lastIndexOf(o); + } + + public int indexOf(Object o) { + return rlist.indexOf(o); + } + + public E remove(int index) { + return rlist.remove(index); + } + + public void add(int index, E element) { + rlist.add(index, element); + } + + public E set(int index, E element) { + return rlist.set(index, element); + } + + public E get(int index) { + return rlist.get(index); + } + + public void clear() { + rlist.clear(); + } + + public boolean addAll(int index, Collection c) { + return rlist.addAll(index, c); + } + + public boolean addAll(Collection c) { + return rlist.addAll(c); + } + + public boolean remove(Object o) { + return rlist.remove(o); + } + + public boolean add(E e) { + return rlist.add(e); + } + + public int size() { + return rlist.size(); + } + + public boolean contains(Object o) { + return rlist.contains(o); + } + + public void addLast(E e) { + rdeque.addLast(e); + } + + public void addFirst(E e) { + rdeque.addFirst(e); + } + + public E removeLast() { + return rdeque.removeLast(); + } + + public E removeFirst() { + return rdeque.removeFirst(); + } + + public E getLast() { + return rdeque.getLast(); + } + + public E getFirst() { + return rdeque.getFirst(); + } + + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + throw new java.io.InvalidObjectException("not serializable"); + } + + public void writeExternal(ObjectOutput out) throws IOException { + throw new java.io.InvalidObjectException("not serializable"); + } + } } diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java index 285aa8b536d4c..e9576455ca70a 100644 --- a/src/java.base/share/classes/java/util/List.java +++ b/src/java.base/share/classes/java/util/List.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -28,10 +28,9 @@ import java.util.function.UnaryOperator; /** - * An ordered collection (also known as a sequence). The user of this - * interface has precise control over where in the list each element is - * inserted. The user can access elements by their integer index (position in - * the list), and search for elements in the list.

+ * An ordered collection, where the user has precise control over where in the + * list each element is inserted. The user can access elements by their integer + * index (position in the list), and search for elements in the list.

* * Unlike sets, lists typically allow duplicate elements. More formally, * lists typically allow pairs of elements {@code e1} and {@code e2} @@ -139,7 +138,7 @@ * @since 1.2 */ -public interface List extends Collection { +public interface List extends SequencedCollection { // Query Operations /** @@ -781,6 +780,126 @@ default Spliterator spliterator() { } } + // ========== SequencedCollection ========== + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface calls {@code add(0, e)}. + * + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @since 21 + */ + default void addFirst(E e) { + this.add(0, e); + } + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface calls {@code add(e)}. + * + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @since 21 + */ + default void addLast(E e) { + this.add(e); + } + + /** + * {@inheritDoc} + * + * @implSpec + * If this List is not empty, the implementation in this interface returns the result + * of calling {@code get(0)}. Otherwise, it throws {@code NoSuchElementException}. + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + default E getFirst() { + if (this.isEmpty()) { + throw new NoSuchElementException(); + } else { + return this.get(0); + } + } + + /** + * {@inheritDoc} + * + * @implSpec + * If this List is not empty, the implementation in this interface returns the result + * of calling {@code get(size() - 1)}. Otherwise, it throws {@code NoSuchElementException}. + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + default E getLast() { + if (this.isEmpty()) { + throw new NoSuchElementException(); + } else { + return this.get(this.size() - 1); + } + } + + /** + * {@inheritDoc} + * + * @implSpec + * If this List is not empty, the implementation in this interface returns the result + * of calling {@code remove(0)}. Otherwise, it throws {@code NoSuchElementException}. + * + * @throws NoSuchElementException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @since 21 + */ + default E removeFirst() { + if (this.isEmpty()) { + throw new NoSuchElementException(); + } else { + return this.remove(0); + } + } + + /** + * {@inheritDoc} + * + * @implSpec + * If this List is not empty, the implementation in this interface returns the result + * of calling {@code remove(size() - 1)}. Otherwise, it throws {@code NoSuchElementException}. + * + * @throws NoSuchElementException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @since 21 + */ + default E removeLast() { + if (this.isEmpty()) { + throw new NoSuchElementException(); + } else { + return this.remove(this.size() - 1); + } + } + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface returns an instance of a reverse-ordered + * List that delegates its operations to this List. + * + * @return a reverse-ordered view of this collection, as a {@code List} + * @since 21 + */ + default List reversed() { + return ReverseOrderListView.of(this, true); // we must assume it's modifiable + } + + // ========== static methods ========== + /** * Returns an unmodifiable list containing zero elements. * diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java index 4e8a2496e2a4d..a36b818710666 100644 --- a/src/java.base/share/classes/java/util/Map.java +++ b/src/java.base/share/classes/java/util/Map.java @@ -42,8 +42,10 @@ * or set of key-value mappings. The order of a map is defined as * the order in which the iterators on the map's collection views return their * elements. Some map implementations, like the {@code TreeMap} class, make - * specific guarantees as to their order; others, like the {@code HashMap} - * class, do not. + * specific guarantees as to their encounter order; others, like the + * {@code HashMap} class, do not. Maps with a defined + * encounter order + * are generally subtypes of the {@link SequencedMap} interface. * *

Note: great care must be exercised if mutable objects are used as map * keys. The behavior of a map is not specified if the value of an object is @@ -304,8 +306,10 @@ public interface Map { * (optional operation). The effect of this call is equivalent to that * of calling {@link #put(Object,Object) put(k, v)} on this map once * for each mapping from key {@code k} to value {@code v} in the - * specified map. The behavior of this operation is undefined if the - * specified map is modified while the operation is in progress. + * specified map. The behavior of this operation is undefined if the specified map + * is modified while the operation is in progress. If the specified map has a defined + * encounter order, + * processing of its mappings generally occurs in that order. * * @param m mappings to be stored in this map * @throws UnsupportedOperationException if the {@code putAll} operation diff --git a/src/java.base/share/classes/java/util/NavigableMap.java b/src/java.base/share/classes/java/util/NavigableMap.java index 2e5b03b3474a9..92abc278dd8be 100644 --- a/src/java.base/share/classes/java/util/NavigableMap.java +++ b/src/java.base/share/classes/java/util/NavigableMap.java @@ -429,4 +429,20 @@ NavigableMap subMap(K fromKey, boolean fromInclusive, * @throws IllegalArgumentException {@inheritDoc} */ SortedMap tailMap(K fromKey); + + /** + * {@inheritDoc} + *

+ * This method is equivalent to {@link #descendingMap descendingMap}. + * + * @implSpec + * The implementation in this interface returns the result of calling the + * {@code descendingMap} method. + * + * @return a reverse-ordered view of this map, as a {@code NavigableMap} + * @since 21 + */ + default NavigableMap reversed() { + return this.descendingMap(); + } } diff --git a/src/java.base/share/classes/java/util/NavigableSet.java b/src/java.base/share/classes/java/util/NavigableSet.java index 49cd130243b8e..8d7da4eddd32e 100644 --- a/src/java.base/share/classes/java/util/NavigableSet.java +++ b/src/java.base/share/classes/java/util/NavigableSet.java @@ -320,4 +320,58 @@ NavigableSet subSet(E fromElement, boolean fromInclusive, * @throws IllegalArgumentException {@inheritDoc} */ SortedSet tailSet(E fromElement); + + /** + * {@inheritDoc} + * + * @implSpec + * If this set is not empty, the implementation in this interface returns the result of calling + * the {@code pollFirst} method. Otherwise, it throws {@code NoSuchElementException}. + * + * @throws NoSuchElementException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @since 21 + */ + default E removeFirst() { + if (this.isEmpty()) { + throw new NoSuchElementException(); + } else { + return this.pollFirst(); + } + } + + /** + * {@inheritDoc} + * + * @implSpec + * If this set is not empty, the implementation in this interface returns the result of calling + * the {@code pollLast} method. Otherwise, it throws {@code NoSuchElementException}. + * + * @throws NoSuchElementException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @since 21 + */ + default E removeLast() { + if (this.isEmpty()) { + throw new NoSuchElementException(); + } else { + return this.pollLast(); + } + } + + /** + * {@inheritDoc} + *

+ * This method is equivalent to {@link #descendingSet descendingSet}. + * + * @implSpec + * The implementation in this interface returns the result of calling the + * {@code descendingSet} method. + * + * @return a reverse-ordered view of this collection, as a {@code NavigableSet} + * @since 21 + */ + default NavigableSet reversed() { + return this.descendingSet(); + } } diff --git a/src/java.base/share/classes/java/util/ReverseOrderDequeView.java b/src/java.base/share/classes/java/util/ReverseOrderDequeView.java new file mode 100644 index 0000000000000..a580ad8f7db3c --- /dev/null +++ b/src/java.base/share/classes/java/util/ReverseOrderDequeView.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package java.util; + +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import jdk.internal.util.ArraysSupport; + +/** + * Provides a reverse-ordered view of any Deque. Not serializable. + */ +class ReverseOrderDequeView implements Deque { + final Deque base; + + private ReverseOrderDequeView(Deque deque) { + base = deque; + } + + public static Deque of(Deque deque) { + if (deque instanceof ReverseOrderDequeView rodv) { + return rodv.base; + } else { + return new ReverseOrderDequeView<>(deque); + } + } + + // ========== Iterable ========== + + public void forEach(Consumer action) { + for (E e : this) + action.accept(e); + } + + public Iterator iterator() { + return base.descendingIterator(); + } + + public Spliterator spliterator() { + return Spliterators.spliteratorUnknownSize(base.descendingIterator(), 0); + } + + // ========== Collection ========== + + public boolean add(E e) { + base.addFirst(e); + return true; + } + + public boolean addAll(Collection c) { + boolean modified = false; + for (E e : c) { + base.addFirst(e); + modified = true; + } + return modified; + } + + public void clear() { + base.clear(); + } + + public boolean contains(Object o) { + return base.contains(o); + } + + public boolean containsAll(Collection c) { + return base.containsAll(c); + } + + public boolean isEmpty() { + return base.isEmpty(); + } + + public Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + + // copied from AbstractCollection + public boolean remove(Object o) { + Iterator it = iterator(); + if (o==null) { + while (it.hasNext()) { + if (it.next()==null) { + it.remove(); + return true; + } + } + } else { + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; + } + + // copied from AbstractCollection + public boolean removeAll(Collection c) { + Objects.requireNonNull(c); + boolean modified = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + // copied from AbstractCollection + public boolean retainAll(Collection c) { + Objects.requireNonNull(c); + boolean modified = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + public int size() { + return base.size(); + } + + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + public Object[] toArray() { + return ArraysSupport.reverse(base.toArray()); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + return ArraysSupport.toArrayReversed(base, a); + } + + public T[] toArray(IntFunction generator) { + return ArraysSupport.reverse(base.toArray(generator)); + } + + // copied from AbstractCollection + public String toString() { + Iterator it = iterator(); + if (! it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + E e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (! it.hasNext()) + return sb.append(']').toString(); + sb.append(',').append(' '); + } + } + + // ========== Deque and Queue ========== + + public void addFirst(E e) { + base.addLast(e); + } + + public void addLast(E e) { + base.addFirst(e); + } + + public Iterator descendingIterator() { + return base.iterator(); + } + + public E element() { + return base.getLast(); + } + + public E getFirst() { + return base.getLast(); + } + + public E getLast() { + return base.getFirst(); + } + + public boolean offer(E e) { + return base.offerFirst(e); + } + + public boolean offerFirst(E e) { + return base.offerLast(e); + } + + public boolean offerLast(E e) { + return base.offerFirst(e); + } + + public E peek() { + return base.peekLast(); + } + + public E peekFirst() { + return base.peekLast(); + } + + public E peekLast() { + return base.peekFirst(); + } + + public E poll() { + return base.pollLast(); + } + + public E pollFirst() { + return base.pollLast(); + } + + public E pollLast() { + return base.pollFirst(); + } + + public E pop() { + return base.removeLast(); + } + + public void push(E e) { + base.addLast(e); + } + + public E remove() { + return base.removeLast(); + } + + public E removeFirst() { + return base.removeLast(); + } + + public E removeLast() { + return base.removeFirst(); + } + + public boolean removeFirstOccurrence(Object o) { + return base.removeLastOccurrence(o); + } + + public boolean removeLastOccurrence(Object o) { + return base.removeFirstOccurrence(o); + } +} diff --git a/src/java.base/share/classes/java/util/ReverseOrderListView.java b/src/java.base/share/classes/java/util/ReverseOrderListView.java new file mode 100644 index 0000000000000..0f7409bef16d0 --- /dev/null +++ b/src/java.base/share/classes/java/util/ReverseOrderListView.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package java.util; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import jdk.internal.util.ArraysSupport; + +/** + * Provides a reverse-ordered view of a List. Not serializable. + */ +class ReverseOrderListView implements List { + + final List base; + final boolean modifiable; + + public static List of(List list, boolean modifiable) { + if (list instanceof ReverseOrderListView rolv) { + return rolv.base; + } else if (list instanceof RandomAccess) { + return new ReverseOrderListView.Rand<>(list, modifiable); + } else { + return new ReverseOrderListView<>(list, modifiable); + } + } + + static class Rand extends ReverseOrderListView implements RandomAccess { + Rand(List list, boolean modifiable) { + super(list, modifiable); + } + } + + private ReverseOrderListView(List list, boolean modifiable) { + this.base = list; + this.modifiable = modifiable; + } + + /** + * Throws if this list is unmodifiable. This should be called from every mutator + * method. For bulk ops (addAll, removeAll, etc.) this throws unconditionally. + * In contrast, if the base list inherits a bulk op implementation from AbstractList, + * it might not throw if no actual mutation would be attempted (e.g., addAll on an + * empty collection). Arguably calling this is unnecessary for individual ops, + * for which the base list should always throw, but it's easier to verify the right + * behavior if every mutator of this class always checks. + */ + void checkModifiable() { + if (! modifiable) { + throw new UnsupportedOperationException(); + } + } + + class DescendingIterator implements Iterator { + final ListIterator it = base.listIterator(base.size()); + public boolean hasNext() { return it.hasPrevious(); } + public E next() { return it.previous(); } + public void remove() { + checkModifiable(); + it.remove(); + // TODO - make sure ListIterator is positioned correctly afterward + } + } + + class DescendingListIterator implements ListIterator { + final ListIterator it; + + DescendingListIterator(int size, int pos) { + if (pos < 0 || pos > size) + throw new IndexOutOfBoundsException(); + it = base.listIterator(size - pos); + } + + public boolean hasNext() { + return it.hasPrevious(); + } + + public E next() { + return it.previous(); + } + + public boolean hasPrevious() { + return it.hasNext(); + } + + public E previous() { + return it.next(); + } + + public int nextIndex() { + return base.size() - it.nextIndex(); + } + + public int previousIndex() { + return nextIndex() - 1; + } + + public void remove() { + checkModifiable(); + it.remove(); + } + + public void set(E e) { + checkModifiable(); + it.set(e); + } + + public void add(E e) { + checkModifiable(); + it.add(e); + it.previous(); + } + } + + // ========== Iterable ========== + + public void forEach(Consumer action) { + for (E e : this) + action.accept(e); + } + + public Iterator iterator() { + return new DescendingIterator(); + } + + public Spliterator spliterator() { + // TODO can probably improve this + return Spliterators.spliteratorUnknownSize(new DescendingIterator(), 0); + } + + // ========== Collection ========== + + public boolean add(E e) { + checkModifiable(); + base.add(0, e); + return true; + } + + public boolean addAll(Collection c) { + checkModifiable(); + + @SuppressWarnings("unchecked") + E[] adds = (E[]) c.toArray(); + if (adds.length == 0) { + return false; + } else { + base.addAll(0, Arrays.asList(ArraysSupport.reverse(adds))); + return true; + } + } + + public void clear() { + checkModifiable(); + base.clear(); + } + + public boolean contains(Object o) { + return base.contains(o); + } + + public boolean containsAll(Collection c) { + return base.containsAll(c); + } + + // copied from AbstractList + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof List)) + return false; + + ListIterator e1 = listIterator(); + ListIterator e2 = ((List) o).listIterator(); + while (e1.hasNext() && e2.hasNext()) { + E o1 = e1.next(); + Object o2 = e2.next(); + if (!(o1==null ? o2==null : o1.equals(o2))) + return false; + } + return !(e1.hasNext() || e2.hasNext()); + } + + // copied from AbstractList + public int hashCode() { + int hashCode = 1; + for (E e : this) + hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); + return hashCode; + } + + public boolean isEmpty() { + return base.isEmpty(); + } + + public Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + + // copied from AbstractCollection + public boolean remove(Object o) { + checkModifiable(); + Iterator it = iterator(); + if (o==null) { + while (it.hasNext()) { + if (it.next()==null) { + it.remove(); + return true; + } + } + } else { + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; + } + + // copied from AbstractCollection + public boolean removeAll(Collection c) { + checkModifiable(); + Objects.requireNonNull(c); + boolean modified = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + // copied from AbstractCollection + public boolean retainAll(Collection c) { + checkModifiable(); + Objects.requireNonNull(c); + boolean modified = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + public int size() { + return base.size(); + } + + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + public Object[] toArray() { + return ArraysSupport.reverse(base.toArray()); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + return ArraysSupport.toArrayReversed(base, a); + } + + public T[] toArray(IntFunction generator) { + return ArraysSupport.reverse(base.toArray(generator)); + } + + // copied from AbstractCollection + public String toString() { + Iterator it = iterator(); + if (! it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + E e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (! it.hasNext()) + return sb.append(']').toString(); + sb.append(',').append(' '); + } + } + + // ========== List ========== + + public void add(int index, E element) { + checkModifiable(); + int size = base.size(); + checkClosedRange(index, size); + base.add(size - index, element); + } + + public boolean addAll(int index, Collection c) { + checkModifiable(); + int size = base.size(); + checkClosedRange(index, size); + @SuppressWarnings("unchecked") + E[] adds = (E[]) c.toArray(); + if (adds.length == 0) { + return false; + } else { + base.addAll(size - index, Arrays.asList(ArraysSupport.reverse(adds))); + return true; + } + } + + public E get(int i) { + int size = base.size(); + Objects.checkIndex(i, size); + return base.get(size - i - 1); + } + + public int indexOf(Object o) { + int i = base.lastIndexOf(o); + return i == -1 ? -1 : base.size() - i - 1; + } + + public int lastIndexOf(Object o) { + int i = base.indexOf(o); + return i == -1 ? -1 : base.size() - i - 1; + } + + public ListIterator listIterator() { + return new DescendingListIterator(base.size(), 0); + } + + public ListIterator listIterator(int index) { + int size = base.size(); + checkClosedRange(index, size); + return new DescendingListIterator(size, index); + } + + public E remove(int index) { + checkModifiable(); + int size = base.size(); + Objects.checkIndex(index, size); + return base.remove(size - index - 1); + } + + public boolean removeIf(Predicate filter) { + checkModifiable(); + return base.removeIf(filter); + } + + public void replaceAll(UnaryOperator operator) { + checkModifiable(); + base.replaceAll(operator); + } + + public void sort(Comparator c) { + checkModifiable(); + base.sort(Collections.reverseOrder(c)); + } + + public E set(int index, E element) { + checkModifiable(); + int size = base.size(); + Objects.checkIndex(index, size); + return base.set(size - index - 1, element); + } + + public List subList(int fromIndex, int toIndex) { + int size = base.size(); + Objects.checkFromToIndex(fromIndex, toIndex, size); + return new ReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex), modifiable); + } + + static void checkClosedRange(int index, int size) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + } + } +} diff --git a/src/java.base/share/classes/java/util/ReverseOrderSortedMapView.java b/src/java.base/share/classes/java/util/ReverseOrderSortedMapView.java new file mode 100644 index 0000000000000..404950e8fd54e --- /dev/null +++ b/src/java.base/share/classes/java/util/ReverseOrderSortedMapView.java @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package java.util; + +/** + * Provides a reversed-ordered view of a SortedMap. Not serializable. + * + * TODO: copy in equals and hashCode from AbstractMap + */ +class ReverseOrderSortedMapView extends AbstractMap implements SortedMap { + final SortedMap base; + final Comparator cmp; + + private ReverseOrderSortedMapView(SortedMap map) { + base = map; + cmp = Collections.reverseOrder(map.comparator()); + } + + public static SortedMap of(SortedMap map) { + if (map instanceof ReverseOrderSortedMapView rosmv) { + return rosmv.base; + } else { + return new ReverseOrderSortedMapView<>(map); + } + } + + // ========== Object ========== + + // equals: inherited from AbstractMap + + // hashCode: inherited from AbstractMap + + public String toString() { + return toString(this, descendingEntryIterator(base)); + } + + // ========== Map ========== + + public void clear() { + base.clear(); + } + + public boolean containsKey(Object key) { + return base.containsKey(key); + } + + public boolean containsValue(Object value) { + return base.containsValue(value); + } + + public V get(Object key) { + return base.get(key); + } + + public boolean isEmpty() { + return base.isEmpty(); + } + + public V put(K key, V value) { + return base.put(key, value); + } + + public void putAll(Map m) { + base.putAll(m); + } + + public V remove(Object key) { + return base.remove(key); + } + + public int size() { + return base.size(); + } + + public Set keySet() { + return new AbstractSet<>() { + // inherit add(), which throws UOE + public Iterator iterator() { return descendingKeyIterator(base); } + public int size() { return base.size(); } + public void clear() { base.keySet().clear(); } + public boolean contains(Object o) { return base.keySet().contains(o); } + public boolean remove(Object o) { return base.keySet().remove(o); } + }; + } + + public Collection values() { + return new AbstractCollection<>() { + // inherit add(), which throws UOE + public Iterator iterator() { return descendingValueIterator(base); } + public int size() { return base.size(); } + public void clear() { base.values().clear(); } + public boolean contains(Object o) { return base.values().contains(o); } + public boolean remove(Object o) { return base.values().remove(o); } + }; + } + + public Set> entrySet() { + return new AbstractSet<>() { + // inherit add(), which throws UOE + public Iterator> iterator() { return descendingEntryIterator(base); } + public int size() { return base.size(); } + public void clear() { base.entrySet().clear(); } + public boolean contains(Object o) { return base.entrySet().contains(o); } + public boolean remove(Object o) { return base.entrySet().remove(o); } + }; + } + + // ========== SequencedMap ========== + + public SortedMap reversed() { + return base; + } + + public K firstKey() { + return base.lastKey(); + } + + public K lastKey() { + return base.firstKey(); + } + + public Map.Entry firstEntry() { + return base.lastEntry(); + } + + public Map.Entry lastEntry() { + return base.firstEntry(); + } + + public Map.Entry pollFirstEntry() { + return base.pollLastEntry(); + } + + public Map.Entry pollLastEntry() { + return base.pollFirstEntry(); + } + + public V putFirst(K k, V v) { + return base.putLast(k, v); + } + + public V putLast(K k, V v) { + return base.putFirst(k, v); + } + + // ========== SortedMap ========== + + public Comparator comparator() { + return cmp; + } + + public SortedMap subMap(K fromKey, K toKey) { + if (cmp.compare(fromKey, toKey) <= 0) { + return new Submap(fromKey, toKey); + } else { + throw new IllegalArgumentException(); + } + } + + public SortedMap headMap(K toKey) { + return new Submap(null, toKey); + } + + public SortedMap tailMap(K fromKey) { + return new Submap(fromKey, null); + } + + // ========== Infrastructure ========== + + static Iterator descendingKeyIterator(SortedMap map) { + return new Iterator<>() { + SortedMap root = map; + SortedMap view = map; + K prev = null; + + public boolean hasNext() { + return ! view.isEmpty(); + } + + public K next() { + if (view.isEmpty()) + throw new NoSuchElementException(); + K k = prev = view.lastKey(); + view = root.headMap(k); + return k; + } + + public void remove() { + if (prev == null) { + throw new IllegalStateException(); + } else { + root.remove(prev); + prev = null; + } + } + }; + } + + static Iterator descendingValueIterator(SortedMap map) { + return new Iterator<>() { + Iterator keyIterator = descendingKeyIterator(map); + + public boolean hasNext() { + return keyIterator.hasNext(); + } + + public V next() { + return map.get(keyIterator.next()); + } + + public void remove() { + keyIterator.remove(); + } + }; + } + + static Iterator> descendingEntryIterator(SortedMap map) { + return new Iterator<>() { + Iterator keyIterator = descendingKeyIterator(map); + + public boolean hasNext() { + return keyIterator.hasNext(); + } + + public Map.Entry next() { + K key = keyIterator.next(); + return new ViewEntry<>(map, key, map.get(key)); + } + + public void remove() { + keyIterator.remove(); + } + }; + } + + static class ViewEntry implements Map.Entry { + final Map map; + final K key; + final V value; + + ViewEntry(Map map, K key, V value) { + this.map = map; + this.key = key; + this.value = value; + } + + public K getKey() { return key; } + public V getValue() { return value; } + public V setValue(V newValue) { return map.put(key, newValue); } + + public boolean equals(Object o) { + return o instanceof Map.Entry e + && Objects.equals(key, e.getKey()) + && Objects.equals(value, e.getValue()); + } + + public int hashCode() { + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + + public String toString() { + return key + "=" + value; + } + } + + // copied and modified from AbstractMap + static String toString(Map thisMap, Iterator> i) { + if (! i.hasNext()) + return "{}"; + + StringBuilder sb = new StringBuilder(); + sb.append('{'); + for (;;) { + Entry e = i.next(); + K key = e.getKey(); + V value = e.getValue(); + sb.append(key == thisMap ? "(this Map)" : key); + sb.append('='); + sb.append(value == thisMap ? "(this Map)" : value); + if (! i.hasNext()) + return sb.append('}').toString(); + sb.append(',').append(' '); + } + } + + /** + * Used for various submap views. We can't use the base SortedMap's subMap, + * because of the asymmetry between from-inclusive and to-exclusive. + */ + class Submap extends AbstractMap implements SortedMap { + final K head; // head key, or negative infinity if null + final K tail; // tail key, or positive infinity if null + + @SuppressWarnings("unchecked") + Submap(K head, K tail) { + this.head = head; + this.tail = tail; + } + + // returns whether e is above the head, inclusive + boolean aboveHead(K k) { + return head == null || cmp.compare(k, head) >= 0; + } + + // returns whether e is below the tail, exclusive + boolean belowTail(K k) { + return tail == null || cmp.compare(k, tail) < 0; + } + + Iterator> entryIterator() { + return new Iterator<>() { + Entry cache = null; + K prevKey = null; + boolean dead = false; + Iterator> it = descendingEntryIterator(base); + + public boolean hasNext() { + if (dead) + return false; + + if (cache != null) + return true; + + while (it.hasNext()) { + Entry e = it.next(); + + if (! aboveHead(e.getKey())) + continue; + + if (! belowTail(e.getKey())) { + dead = true; + return false; + } + + cache = e; + return true; + } + + return false; + } + + public Entry next() { + if (hasNext()) { + Entry e = cache; + cache = null; + prevKey = e.getKey(); + return e; + } else { + throw new NoSuchElementException(); + } + } + + public void remove() { + if (prevKey == null) { + throw new IllegalStateException(); + } else { + base.remove(prevKey); + } + } + }; + } + + // equals: inherited from AbstractMap + + // hashCode: inherited from AbstractMap + + public String toString() { + return ReverseOrderSortedMapView.toString(this, entryIterator()); + } + + public Set> entrySet() { + return new AbstractSet<>() { + public Iterator> iterator() { + return entryIterator(); + } + + public int size() { + int sz = 0; + for (var it = entryIterator(); it.hasNext();) { + it.next(); + sz++; + } + return sz; + } + }; + } + + public V put(K key, V value) { + if (aboveHead(key) && belowTail(key)) + return base.put(key, value); + else + throw new IllegalArgumentException(); + } + + public V remove(Object o) { + @SuppressWarnings("unchecked") + K key = (K) o; + if (aboveHead(key) && belowTail(key)) + return base.remove(o); + else + return null; + } + + public int size() { + return entrySet().size(); + } + + public Comparator comparator() { + return cmp; + } + + public K firstKey() { + return this.entryIterator().next().getKey(); + } + + public K lastKey() { + var it = this.entryIterator(); + if (! it.hasNext()) + throw new NoSuchElementException(); + var last = it.next(); + while (it.hasNext()) + last = it.next(); + return last.getKey(); + } + + public SortedMap subMap(K from, K to) { + if (aboveHead(from) && belowTail(from) && + aboveHead(to) && belowTail(to) && + cmp.compare(from, to) <= 0) { + return new Submap(from, to); + } else { + throw new IllegalArgumentException(); + } + } + + public SortedMap headMap(K to) { + if (aboveHead(to) && belowTail(to)) + return new Submap(head, to); + else + throw new IllegalArgumentException(); + } + + public SortedMap tailMap(K from) { + if (aboveHead(from) && belowTail(from)) + return new Submap(from, tail); + else + throw new IllegalArgumentException(); + } + } +} diff --git a/src/java.base/share/classes/java/util/ReverseOrderSortedSetView.java b/src/java.base/share/classes/java/util/ReverseOrderSortedSetView.java new file mode 100644 index 0000000000000..678c82fc311e4 --- /dev/null +++ b/src/java.base/share/classes/java/util/ReverseOrderSortedSetView.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package java.util; + +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import jdk.internal.util.ArraysSupport; + +/** + * Provides a reversed-ordered view of a SortedSet. Not serializable. + */ +class ReverseOrderSortedSetView implements SortedSet { + final SortedSet base; + final Comparator comp; + + private ReverseOrderSortedSetView(SortedSet set) { + base = set; + comp = Collections.reverseOrder(set.comparator()); + } + + public static SortedSet of(SortedSet set) { + if (set instanceof ReverseOrderSortedSetView rossv) { + return rossv.base; + } else { + return new ReverseOrderSortedSetView<>(set); + } + } + + // ========== Object ========== + + // copied from AbstractSet + public boolean equals(Object o) { + if (o == this) + return true; + + if (!(o instanceof Set)) + return false; + Collection c = (Collection) o; + if (c.size() != size()) + return false; + try { + return containsAll(c); + } catch (ClassCastException | NullPointerException unused) { + return false; + } + } + + // copied from AbstractSet + public int hashCode() { + int h = 0; + Iterator i = iterator(); + while (i.hasNext()) { + E obj = i.next(); + if (obj != null) + h += obj.hashCode(); + } + return h; + } + + // copied from AbstractCollection + public String toString() { + Iterator it = iterator(); + if (! it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + E e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (! it.hasNext()) + return sb.append(']').toString(); + sb.append(',').append(' '); + } + } + + // ========== Iterable ========== + + public void forEach(Consumer action) { + for (E e : this) + action.accept(e); + } + + public Iterator iterator() { + return descendingIterator(base); + } + + public Spliterator spliterator() { + return Spliterators.spliteratorUnknownSize(descendingIterator(base), 0); + } + + // ========== Collection ========== + + public boolean add(E e) { + base.add(e); + return true; + } + + public boolean addAll(Collection c) { + return base.addAll(c); + } + + public void clear() { + base.clear(); + } + + public boolean contains(Object o) { + return base.contains(o); + } + + public boolean containsAll(Collection c) { + return base.containsAll(c); + } + + public boolean isEmpty() { + return base.isEmpty(); + } + + public Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + + public boolean remove(Object o) { + return base.remove(o); + } + + public boolean removeAll(Collection c) { + return base.removeAll(c); + } + + // copied from AbstractCollection + public boolean retainAll(Collection c) { + return base.retainAll(c); + } + + public int size() { + return base.size(); + } + + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + public Object[] toArray() { + return ArraysSupport.reverse(base.toArray()); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + return ArraysSupport.toArrayReversed(base, a); + } + + public T[] toArray(IntFunction generator) { + return ArraysSupport.reverse(base.toArray(generator)); + } + + // ========== SortedSet ========== + + public Comparator comparator() { + return comp; + } + + public E first() { return base.last(); } + + public E last() { return base.first(); } + + public SortedSet headSet(E to) { + return new Subset(null, to); + } + + public SortedSet subSet(E from, E to) { + return new Subset(from, to); + } + + public SortedSet tailSet(E from) { + return new Subset(from, null); + } + + // ========== Infrastructure ========== + + static Iterator descendingIterator(SortedSet set) { + return new Iterator<>() { + SortedSet root = set; + SortedSet view = set; + T prev = null; + + public boolean hasNext() { + return ! view.isEmpty(); + } + + public T next() { + if (view.isEmpty()) + throw new NoSuchElementException(); + T t = prev = view.last(); + view = root.headSet(t); + return t; + } + + public void remove() { + if (prev == null) { + throw new IllegalStateException(); + } else { + root.remove(prev); + prev = null; + } + } + }; + } + + /** + * Used for various subset views. We can't use the base SortedSet's subset, + * because of the asymmetry between from-inclusive and to-exclusive. + */ + class Subset extends AbstractSet implements SortedSet { + final E head; // head element, or negative infinity if null + final E tail; // tail element, or positive infinity if null + final Comparator cmp; + + @SuppressWarnings("unchecked") + Subset(E head, E tail) { + this.head = head; + this.tail = tail; + Comparator c = (Comparator) ReverseOrderSortedSetView.this.comparator(); + if (c == null) + c = (Comparator) Comparator.naturalOrder(); + cmp = c; + } + + // returns whether e is above the head, inclusive + boolean aboveHead(E e) { + return head == null || cmp.compare(e, head) >= 0; + } + + // returns whether e is below the tail, exclusive + boolean belowTail(E e) { + return tail == null || cmp.compare(e, tail) < 0; + } + + public Iterator iterator() { + return new Iterator<>() { + E cache = null; + boolean dead = false; + Iterator it = descendingIterator(base); + + public boolean hasNext() { + if (dead) + return false; + + if (cache != null) + return true; + + while (it.hasNext()) { + E e = it.next(); + + if (! aboveHead(e)) + continue; + + if (! belowTail(e)) { + dead = true; + return false; + } + + cache = e; + return true; + } + + return false; + } + + public E next() { + if (hasNext()) { + E e = cache; + cache = null; + return e; + } else { + throw new NoSuchElementException(); + } + } + }; + } + + public boolean add(E e) { + if (aboveHead(e) && belowTail(e)) + return base.add(e); + else + throw new IllegalArgumentException(); + } + + public boolean remove(Object o) { + @SuppressWarnings("unchecked") + E e = (E) o; + if (aboveHead(e) && belowTail(e)) + return base.remove(o); + else + return false; + } + + public int size() { + int sz = 0; + for (E e : this) + sz++; + return sz; + } + + public Comparator comparator() { + return ReverseOrderSortedSetView.this.comparator(); + } + + public E first() { + return this.iterator().next(); + } + + public E last() { + var it = this.iterator(); + if (! it.hasNext()) + throw new NoSuchElementException(); + E last = it.next(); + while (it.hasNext()) + last = it.next(); + return last; + } + + public SortedSet subSet(E from, E to) { + if (aboveHead(from) && belowTail(from) && + aboveHead(to) && belowTail(to) && + cmp.compare(from, to) <= 0) { + return ReverseOrderSortedSetView.this.new Subset(from, to); + } else { + throw new IllegalArgumentException(); + } + } + + public SortedSet headSet(E to) { + if (aboveHead(to) && belowTail(to)) + return ReverseOrderSortedSetView.this.new Subset(head, to); + else + throw new IllegalArgumentException(); + } + + public SortedSet tailSet(E from) { + if (aboveHead(from) && belowTail(from)) + return ReverseOrderSortedSetView.this.new Subset(null, tail); + else + throw new IllegalArgumentException(); + } + + } +} diff --git a/src/java.base/share/classes/java/util/SequencedCollection.java b/src/java.base/share/classes/java/util/SequencedCollection.java new file mode 100644 index 0000000000000..54237c7a3f775 --- /dev/null +++ b/src/java.base/share/classes/java/util/SequencedCollection.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package java.util; + +/** + * A collection that has a well-defined encounter order, that supports operations at both ends, + * and that is reversible. The elements of a sequenced collection have an + * encounter order, where conceptually the elements have a linear arrangement + * from the first element to the last element. Given any two elements, one element is + * either before (closer to the first element) or after (closer to the last element) + * the other element. + *

+ * (Note that this definition does not imply anything about physical positioning + * of elements, such as their locations in a computer's memory.) + *

+ * Several methods inherited from the {@link Collection} interface are required to operate + * on elements according to this collection's encounter order. For instance, the + * {@link Collection#iterator iterator} method provides elements starting from the first element, + * proceeding through successive elements, until the last element. Other methods that are + * required to operate on elements in encounter order include the following: + * {@link Iterable#forEach forEach}, {@link Collection#parallelStream parallelStream}, + * {@link Collection#spliterator spliterator}, {@link Collection#stream stream}, + * and all overloads of the {@link Collection#toArray toArray} method. + *

+ * This interface provides methods to add, retrieve, and remove elements at either end + * of the collection. + *

+ * This interface also defines the {@link #reversed reversed} method, which provides + * a reverse-ordered view of this collection. + * In the reverse-ordered view, the concepts of first and last are inverted, as are + * the concepts of successor and predecessor. The first element of this collection is + * the last element of the reverse-ordered view, and vice-versa. The successor of some + * element in this collection is its predecessor in the reversed view, and vice-versa. All + * methods that respect the encounter order of the collection operate as if the encounter order + * is inverted. For instance, the {@link #iterator} method of the reversed view reports the + * elements in order from the last element of this collection to the first. The availability of + * the {@code reversed} method, and its impact on the ordering semantics of all applicable + * methods, allow convenient iteration, searching, copying, and streaming of the elements of + * this collection in either forward order or reverse order. + *

+ * This class is a member of the + * + * Java Collections Framework. + * + * @apiNote + * This interface does not impose any requirements on the {@code equals} and {@code hashCode} + * methods, because requirements imposed by sub-interfaces {@link List} and {@link SequencedSet} + * (which inherits requirements from {@link Set}) would be in conflict. See the specifications for + * {@link Collection#equals Collection.equals} and {@link Collection#hashCode Collection.hashCode} + * for further information. + * + * @param the type of elements in this collection + * @since 21 + */ +public interface SequencedCollection extends Collection { + /** + * Returns a reverse-ordered view of this collection. + * The encounter order of elements in the returned view is the inverse of the encounter + * order of elements in this collection. The reverse ordering affects all order-sensitive + * operations, including those on the view collections of the returned view. If the collection + * implementation permits modifications to this view, the modifications "write through" to the + * underlying collection. Changes to the underlying collection might or might not be visible + * in this reversed view, depending upon the implementation. + * + * @return a reverse-ordered view of this collection + */ + SequencedCollection reversed(); + + /** + * Adds an element as the first element of this collection (optional operation). + * After this operation completes normally, the given element will be a member of + * this collection, and it will be the first element in encounter order. + * + * @implSpec + * The implementation in this interface always throws {@code UnsupportedOperationException}. + * + * @param e the element to be added + * @throws NullPointerException if the specified element is null and this + * collection does not permit null elements + * @throws UnsupportedOperationException if this collection implementation + * does not support this operation + */ + default void addFirst(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Adds an element as the last element of this collection (optional operation). + * After this operation completes normally, the given element will be a member of + * this collection, and it will be the last element in encounter order. + * + * @implSpec + * The implementation in this interface always throws {@code UnsupportedOperationException}. + * + * @param e the element to be added. + * @throws NullPointerException if the specified element is null and this + * collection does not permit null elements + * @throws UnsupportedOperationException if this collection implementation + * does not support this operation + */ + default void addLast(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Gets the first element of this collection. + * + * @implSpec + * The implementation in this interface obtains an iterator of this collection, and + * then it obtains an element by calling the iterator's {@code next} method. Any + * {@code NoSuchElementException} thrown is propagated. Otherwise, it returns + * the element. + * + * @return the retrieved element + * @throws NoSuchElementException if this collection is empty + */ + default E getFirst() { + return this.iterator().next(); + } + + /** + * Gets the last element of this collection. + * + * @implSpec + * The implementation in this interface obtains an iterator of the reversed view + * of this collection, and then it obtains an element by calling the iterator's + * {@code next} method. Any {@code NoSuchElementException} thrown is propagated. + * Otherwise, it returns the element. + * + * @return the retrieved element + * @throws NoSuchElementException if this collection is empty + */ + default E getLast() { + return this.reversed().iterator().next(); + } + + /** + * Removes and returns the first element of this collection (optional operation). + * + * @implSpec + * The implementation in this interface obtains an iterator of this collection, and then + * it obtains an element by calling the iterator's {@code next} method. Any + * {@code NoSuchElementException} thrown is propagated. It then calls the iterator's + * {@code remove} method. Any {@code UnsupportedOperationException} thrown is propagated. + * Then, it returns the element. + * + * @return the removed element + * @throws NoSuchElementException if this collection is empty + * @throws UnsupportedOperationException if this collection implementation + * does not support this operation + */ + default E removeFirst() { + var it = this.iterator(); + E e = it.next(); + it.remove(); + return e; + } + + /** + * Removes and returns the last element of this collection (optional operation). + * + * @implSpec + * The implementation in this interface obtains an iterator of the reversed view of this + * collection, and then it obtains an element by calling the iterator's {@code next} method. + * Any {@code NoSuchElementException} thrown is propagated. It then calls the iterator's + * {@code remove} method. Any {@code UnsupportedOperationException} thrown is propagated. + * Then, it returns the element. + * + * @return the removed element + * @throws NoSuchElementException if this collection is empty + * @throws UnsupportedOperationException if this collection implementation + * does not support this operation + */ + default E removeLast() { + var it = this.reversed().iterator(); + E e = it.next(); + it.remove(); + return e; + } +} diff --git a/src/java.base/share/classes/java/util/SequencedMap.java b/src/java.base/share/classes/java/util/SequencedMap.java new file mode 100644 index 0000000000000..c8f88d004bc3d --- /dev/null +++ b/src/java.base/share/classes/java/util/SequencedMap.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package java.util; + +/** + * A Map that has a well-defined encounter order, that supports operations at both ends, and + * that is reversible. The encounter order + * of a {@code SequencedMap} is similar to that of the elements of a {@link SequencedCollection}, + * but the ordering applies to mappings instead of individual elements. + *

+ * The bulk operations on this map, including the {@link #forEach forEach} and the + * {@link #replaceAll replaceAll} methods, operate on this map's mappings in + * encounter order. + *

+ * The view collections provided by the + * {@link #keySet keySet}, + * {@link #values values}, + * {@link #entrySet entrySet}, + * {@link #sequencedKeySet sequencedKeySet}, + * {@link #sequencedValues sequencedValues}, + * and + * {@link #sequencedEntrySet sequencedEntrySet} methods all reflect the encounter order + * of this map. Even though the return values of the {@code keySet}, {@code values}, and + * {@code entrySet} methods are not sequenced types, the elements + * in those view collections do reflect the encounter order of this map. Thus, the + * iterators returned by the statements + * {@snippet : + * var it1 = sequencedMap.entrySet().iterator(); + * var it2 = sequencedMap.sequencedEntrySet().iterator(); + * } + * both provide the mappings of {@code sequencedMap} in that map's encounter order. + *

+ * This interface provides methods to add mappings, to retrieve mappings, and to remove + * mappings at either end of the map's encounter order. + *

+ * This interface also defines the {@link #reversed} method, which provides a + * reverse-ordered view of this map. + * In the reverse-ordered view, the concepts of first and last are inverted, as + * are the concepts of successor and predecessor. The first mapping of this map + * is the last mapping of the reverse-ordered view, and vice-versa. The successor of some + * mapping in this map is its predecessor in the reversed view, and vice-versa. All + * methods that respect the encounter order of the map operate as if the encounter order + * is inverted. For instance, the {@link #forEach forEach} method of the reversed view reports + * the mappings in order from the last mapping of this map to the first. In addition, all of + * the view collections of the reversed view also reflect the inverse of this map's + * encounter order. For example, + * {@snippet : + * var itr = sequencedMap.reversed().entrySet().iterator(); + * } + * provides the mappings of this map in the inverse of the encounter order, that is, from + * the last mapping to the first mapping. The availability of the {@code reversed} method, + * and its impact on the ordering semantics of all applicable methods and views, allow convenient + * iteration, searching, copying, and streaming of this map's mappings in either forward order or + * reverse order. + *

+ * A map's reverse-ordered view is generally not serializable, even if the original + * map is serializable. + *

+ * The {@link Map.Entry} instances obtained by iterating the {@link #entrySet} view, the + * {@link #sequencedEntrySet} view, and its reverse-ordered view, maintain a connection to the + * underlying map. This connection is guaranteed only during the iteration. It is unspecified + * whether the connection is maintained outside of the iteration. If the underlying map permits + * it, calling an Entry's {@link Map.Entry#setValue setValue} method will modify the value of the + * underlying mapping. It is, however, unspecified whether modifications to the value in the + * underlying mapping are visible in the {@code Entry} instance. + *

+ * The methods + * {@link #firstEntry}, + * {@link #lastEntry}, + * {@link #pollFirstEntry}, and + * {@link #pollLastEntry} + * return {@link Map.Entry} instances that represent snapshots of mappings as + * of the time of the call. They do not support mutation of the + * underlying map via the optional {@link Map.Entry#setValue setValue} method. + *

+ * Depending upon the implementation, the {@code Entry} instances returned by other + * means might or might not be connected to the underlying map. For example, consider + * an {@code Entry} obtained in the following manner: + * {@snippet : + * var entry = sequencedMap.sequencedEntrySet().getFirst(); + * } + * It is not specified by this interface whether the {@code setValue} method of the + * {@code Entry} thus obtained will update a mapping in the underlying map, or whether + * it will throw an exception, or whether changes to the underlying map are visible in + * that {@code Entry}. + *

+ * This interface has the same requirements on the {@code equals} and {@code hashCode} + * methods as defined by {@link Map#equals Map.equals} and {@link Map#hashCode Map.hashCode}. + * Thus, a {@code Map} and a {@code SequencedMap} will compare equals if and only + * if they have equal mappings, irrespective of ordering. + *

+ * This class is a member of the + * + * Java Collections Framework. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + * @since 21 + */ +public interface SequencedMap extends Map { + /** + * Returns a reverse-ordered view of this map. + * The encounter order of mappings in the returned view is the inverse of the encounter + * order of mappings in this map. The reverse ordering affects all order-sensitive operations, + * including those on the view collections of the returned view. If the implementation permits + * modifications to this view, the modifications "write through" to the underlying map. + * Changes to the underlying map might or might not be visible in this reversed view, + * depending upon the implementation. + * + * @return a reverse-ordered view of this map + */ + SequencedMap reversed(); + + /** + * Returns the first key-value mapping in this map, + * or {@code null} if the map is empty. + * + * @implSpec + * The implementation in this interface obtains the iterator of this map's entrySet. + * If the iterator has an element, it returns an unmodifiable copy of that element. + * Otherwise, it returns null. + * + * @return the first key-value mapping, + * or {@code null} if this map is empty + */ + default Map.Entry firstEntry() { + var it = entrySet().iterator(); + return it.hasNext() ? Map.Entry.copyOf(it.next()) : null; + } + + /** + * Returns the last key-value mapping in this map, + * or {@code null} if the map is empty. + * + * @implSpec + * The implementation in this interface obtains the iterator of the entrySet of this map's + * reversed view. If the iterator has an element, it returns an unmodifiable copy of + * that element. Otherwise, it returns null. + * + * @return the last key-value mapping, + * or {@code null} if this map is empty + */ + default Map.Entry lastEntry() { + var it = reversed().entrySet().iterator(); + return it.hasNext() ? Map.Entry.copyOf(it.next()) : null; + } + + /** + * Removes and returns the first key-value mapping in this map, + * or {@code null} if the map is empty (optional operation). + * + * @implSpec + * The implementation in this interface obtains the iterator of this map's entrySet. + * If the iterator has an element, it calls {@code remove} on the iterator and + * then returns an unmodifiable copy of that element. Otherwise, it returns null. + * + * @return the removed first entry of this map, + * or {@code null} if this map is empty + * @throws UnsupportedOperationException if this collection implementation does not + * support this operation + */ + default Map.Entry pollFirstEntry() { + var it = entrySet().iterator(); + if (it.hasNext()) { + var entry = Map.Entry.copyOf(it.next()); + it.remove(); + return entry; + } else { + return null; + } + } + + /** + * Removes and returns the last key-value mapping in this map, + * or {@code null} if the map is empty (optional operation). + * + * @implSpec + * The implementation in this interface obtains the iterator of the entrySet of this map's + * reversed view. If the iterator has an element, it calls {@code remove} on the iterator + * and then returns an unmodifiable copy of that element. Otherwise, it returns null. + * + * @return the removed last entry of this map, + * or {@code null} if this map is empty + * @throws UnsupportedOperationException if this collection implementation does not + * support this operation + */ + default Map.Entry pollLastEntry() { + var it = reversed().entrySet().iterator(); + if (it.hasNext()) { + var entry = Map.Entry.copyOf(it.next()); + it.remove(); + return entry; + } else { + return null; + } + } + + /** + * Inserts the given mapping into the map if it is not already present, or replaces the + * value of a mapping if it is already present (optional operation). After this operation + * completes normally, the given mapping will be present in this map, and it will be the + * first mapping in this map's encounter order. + * + * @implSpec The implementation in this interface always throws + * {@code UnsupportedOperationException}. + * + * @param k the key + * @param v the value + * @return the value previously associated with k, or null if none + * @throws UnsupportedOperationException if this collection implementation does not + * support this operation + */ + default V putFirst(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Inserts the given mapping into the map if it is not already present, or replaces the + * value of a mapping if it is already present (optional operation). After this operation + * completes normally, the given mapping will be present in this map, and it will be the + * last mapping in this map's encounter order. + * + * @implSpec The implementation in this interface always throws + * {@code UnsupportedOperationException}. + * + * @param k the key + * @param v the value + * @return the value previously associated with k, or null if none + * @throws UnsupportedOperationException if this collection implementation does not + * support this operation + */ + default V putLast(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Returns a {@link SequencedSet} view of this map's keySet. + * + * @implSpec + * The implementation in this interface returns a {@code SequencedSet} + * implementation that delegates all operations either to this map or to this map's + * {@link #keySet}, except for its {@link SequencedSet#reversed reversed} method, + * which instead returns the result of calling {@code sequencedKeySet} on this map's + * reverse-ordered view. + * + * @return a SequencedSet view of this map's keySet + */ + default SequencedSet sequencedKeySet() { + class SeqKeySet extends AbstractMap.ViewCollection implements SequencedSet { + SeqKeySet() { + super(SequencedMap.this.keySet()); + } + public SequencedSet reversed() { + return SequencedMap.this.reversed().sequencedKeySet(); + } + } + return new SeqKeySet(); + } + + /** + * Returns a {@link SequencedCollection} view of this map's values collection. + * + * @implSpec + * The implementation in this interface returns a {@code SequencedCollection} + * implementation that delegates all operations either to this map or to this map's + * {@link #values} collection, except for its {@link SequencedCollection#reversed reversed} + * method, which instead returns the result of calling {@code sequencedValues} on this map's + * reverse-ordered view. + * + * @return a SequencedCollection view of this map's values collection + */ + default SequencedCollection sequencedValues() { + class SeqValues extends AbstractMap.ViewCollection implements SequencedCollection { + SeqValues() { + super(SequencedMap.this.values()); + } + public SequencedCollection reversed() { + return SequencedMap.this.reversed().sequencedValues(); + } + } + return new SeqValues(); + } + + /** + * Returns a {@link SequencedSet} view of this map's entrySet. + * + * @implSpec + * The implementation in this interface returns a {@code SequencedSet} + * implementation that delegates all operations either to this map or to this map's + * {@link #entrySet}, except for its {@link SequencedSet#reversed reversed} method, + * which instead returns the result of calling {@code sequencedEntrySet} on this map's + * reverse-ordered view. + * + * @return a SequencedSet view of this map's entrySet + */ + default SequencedSet> sequencedEntrySet() { + class SeqEntrySet extends AbstractMap.ViewCollection> + implements SequencedSet> { + SeqEntrySet() { + super(SequencedMap.this.entrySet()); + } + public SequencedSet> reversed() { + return SequencedMap.this.reversed().sequencedEntrySet(); + } + } + return new SeqEntrySet(); + } +} diff --git a/src/java.base/share/classes/java/util/SequencedSet.java b/src/java.base/share/classes/java/util/SequencedSet.java new file mode 100644 index 0000000000000..c02bfc123ff8f --- /dev/null +++ b/src/java.base/share/classes/java/util/SequencedSet.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package java.util; + +/** + * A collection that is both a {@link SequencedCollection} and a {@link Set}. As such, + * it can be thought of either as a {@code Set} that also has a well-defined + * encounter order, or as a + * {@code SequencedCollection} that also has unique elements. + *

+ * This interface has the same requirements on the {@code equals} and {@code hashCode} + * methods as defined by {@link Set#equals Set.equals} and {@link Set#hashCode Set.hashCode}. + * Thus, a {@code Set} and a {@code SequencedSet} will compare equals if and only + * if they have equal elements, irrespective of ordering. + *

+ * {@code SequencedSet} defines the {@link #reversed} method, which provides a + * reverse-ordered view of this set. The only difference + * from the {@link SequencedCollection#reversed SequencedCollection.reversed} method is + * that the return type of {@code SequencedSet.reversed} is {@code SequencedSet}. + *

+ * This class is a member of the + * + * Java Collections Framework. + * + * @param the type of elements in this sequenced set + * @since 21 + */ +public interface SequencedSet extends SequencedCollection, Set { + /** + * {@inheritDoc} + * + * @return a reverse-ordered view of this collection, as a {@code SequencedSet} + */ + SequencedSet reversed(); +} diff --git a/src/java.base/share/classes/java/util/SortedMap.java b/src/java.base/share/classes/java/util/SortedMap.java index 77553bcc578c7..1af9d59e83e14 100644 --- a/src/java.base/share/classes/java/util/SortedMap.java +++ b/src/java.base/share/classes/java/util/SortedMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -110,7 +110,7 @@ * @since 1.2 */ -public interface SortedMap extends Map { +public interface SortedMap extends SequencedMap { /** * Returns the comparator used to order the keys in this map, or * {@code null} if this map uses the {@linkplain Comparable @@ -281,4 +281,48 @@ public interface SortedMap extends Map { * sorted in ascending key order */ Set> entrySet(); + + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * map's comparison method determines the position of mappings, so explicit positioning + * is not supported. + * + * @implSpec + * The implementation in this interface always throws {@code UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + default V putFirst(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * map's comparison method determines the position of mappings, so explicit positioning + * is not supported. + * + * @implSpec + * The implementation in this interface always throws {@code UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + default V putLast(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface returns an instance of a reverse-ordered + * SortedMap that delegates its operations to this SortedMap. + * + * @return a reverse-ordered view of this map, as a {@code SortedMap} + * @since 21 + */ + default SortedMap reversed() { + return ReverseOrderSortedMapView.of(this); + } } diff --git a/src/java.base/share/classes/java/util/SortedSet.java b/src/java.base/share/classes/java/util/SortedSet.java index c3ef264a2afec..48473a1fed531 100644 --- a/src/java.base/share/classes/java/util/SortedSet.java +++ b/src/java.base/share/classes/java/util/SortedSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -105,7 +105,7 @@ * @since 1.2 */ -public interface SortedSet extends Set { +public interface SortedSet extends Set, SequencedSet { /** * Returns the comparator used to order the elements in this set, * or {@code null} if this set uses the {@linkplain Comparable @@ -261,4 +261,112 @@ public Comparator getComparator() { } }; } + + // ========== SequencedCollection ========== + + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * set's comparison method determines the position of elements, so explicit positioning + * is not supported. + * + * @implSpec + * The implementation in this interface always throws {@code UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + default void addFirst(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * set's comparison method determines the position of elements, so explicit positioning + * is not supported. + * + * @implSpec + * The implementation in this interface always throws {@code UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + default void addLast(E e) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface returns the result of calling the {@code first} method. + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + default E getFirst() { + return this.first(); + } + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface returns the result of calling the {@code last} method. + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + default E getLast() { + return this.last(); + } + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface calls the {@code first} method to obtain the first + * element, then it calls {@code remove(element)} to remove the element, and then it returns + * the element. + * + * @throws NoSuchElementException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @since 21 + */ + default E removeFirst() { + E e = this.first(); + this.remove(e); + return e; + } + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface calls the {@code last} method to obtain the last + * element, then it calls {@code remove(element)} to remove the element, and then it returns + * the element. + * + * @throws NoSuchElementException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @since 21 + */ + default E removeLast() { + E e = this.last(); + this.remove(e); + return e; + } + + /** + * {@inheritDoc} + * + * @implSpec + * The implementation in this interface returns an instance of a reverse-ordered + * SortedSet that delegates its operations to this SortedSet. + * + * @return a reverse-ordered view of this collection, as a {@code SortedSet} + * @since 21 + */ + default SortedSet reversed() { + return ReverseOrderSortedSetView.of(this); + } } diff --git a/src/java.base/share/classes/java/util/TreeMap.java b/src/java.base/share/classes/java/util/TreeMap.java index e33c95e44d237..a91a56a5900c8 100644 --- a/src/java.base/share/classes/java/util/TreeMap.java +++ b/src/java.base/share/classes/java/util/TreeMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -99,6 +99,10 @@ * of the time of the call. They do not support mutation of the * underlying map via the optional {@link Map.Entry#setValue setValue} method. * + *

The {@link #putFirst putFirst} and {@link #putLast putLast} methods of this class + * throw {@code UnsupportedOperationException}. The encounter order of mappings is determined + * by the comparison method; therefore, explicit positioning is not supported. + * *

This class is a member of the * * Java Collections Framework. @@ -305,6 +309,30 @@ public K lastKey() { return key(getLastEntry()); } + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * map's comparison method determines the position of mappings, so explicit positioning + * is not supported. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + public V putFirst(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * map's comparison method determines the position of mappings, so explicit positioning + * is not supported. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + public V putLast(K k, V v) { + throw new UnsupportedOperationException(); + } + /** * Copies all of the mappings from the specified map to this map. * These mappings replace any mappings that this map had for any diff --git a/src/java.base/share/classes/java/util/TreeSet.java b/src/java.base/share/classes/java/util/TreeSet.java index f189bb4a9f2fe..a68c157283eba 100644 --- a/src/java.base/share/classes/java/util/TreeSet.java +++ b/src/java.base/share/classes/java/util/TreeSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -73,6 +73,10 @@ * exception for its correctness: the fail-fast behavior of iterators * should be used only to detect bugs. * + *

The {@link #addFirst addFirst} and {@link #addLast addLast} methods of this class + * throw {@code UnsupportedOperationException}. The encounter order of elements is determined + * by the comparison method; therefore, explicit positioning is not supported. + * *

This class is a member of the * * Java Collections Framework. @@ -460,6 +464,30 @@ public E pollLast() { return (e == null) ? null : e.getKey(); } + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * set's comparison method determines the position of elements, so explicit positioning + * is not supported. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + public void addFirst(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * set's comparison method determines the position of elements, so explicit positioning + * is not supported. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + public void addLast(E e) { + throw new UnsupportedOperationException(); + } + /** * Returns a shallow copy of this {@code TreeSet} instance. (The elements * themselves are not cloned.) diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java index 4c2add084abe2..bccbccf57258c 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java @@ -1870,6 +1870,30 @@ public K lastKey() { return n.key; } + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * map's comparison method determines the position of mappings, so explicit positioning + * is not supported. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + public V putFirst(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * map's comparison method determines the position of mappings, so explicit positioning + * is not supported. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + public V putLast(K k, V v) { + throw new UnsupportedOperationException(); + } + /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if {@code fromKey} or {@code toKey} is null diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java index 4d79c4213315e..0db76e03e05c4 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java @@ -403,6 +403,30 @@ public E last() { return m.lastKey(); } + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * set's comparison method determines the position of elements, so explicit positioning + * is not supported. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + public void addFirst(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Throws {@code UnsupportedOperationException}. The encounter order induced by this + * set's comparison method determines the position of elements, so explicit positioning + * is not supported. + * + * @throws UnsupportedOperationException always + * @since 21 + */ + public void addLast(E e) { + throw new UnsupportedOperationException(); + } + /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if {@code fromElement} or diff --git a/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java b/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java index ee8e7b81728f2..909621bd5babb 100644 --- a/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java +++ b/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java @@ -39,6 +39,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.Iterator; @@ -50,9 +51,13 @@ import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Consumer; +import java.util.function.IntFunction; import java.util.function.Predicate; import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import jdk.internal.access.SharedSecrets; +import jdk.internal.util.ArraysSupport; /** * A thread-safe variant of {@link java.util.ArrayList} in which all mutative @@ -398,6 +403,34 @@ public E get(int index) { return elementAt(getArray(), index); } + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E getFirst() { + Object[] es = getArray(); + if (es.length == 0) + throw new NoSuchElementException(); + else + return elementAt(es, 0); + } + + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E getLast() { + Object[] es = getArray(); + if (es.length == 0) + throw new NoSuchElementException(); + else + return elementAt(es, es.length - 1); + } + /** * Replaces the element at the specified position in this list with the * specified element. @@ -464,6 +497,26 @@ public void add(int index, E element) { } } + /** + * {@inheritDoc} + * + * @since 21 + */ + public void addFirst(E e) { + add(0, e); + } + + /** + * {@inheritDoc} + * + * @since 21 + */ + public void addLast(E e) { + synchronized (lock) { + add(getArray().length, e); + } + } + /** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their @@ -491,6 +544,37 @@ public E remove(int index) { } } + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E removeFirst() { + synchronized (lock) { + if (getArray().length == 0) + throw new NoSuchElementException(); + else + return remove(0); + } + } + + /** + * {@inheritDoc} + * + * @throws NoSuchElementException {@inheritDoc} + * @since 21 + */ + public E removeLast() { + synchronized (lock) { + int size = getArray().length; + if (size == 0) + throw new NoSuchElementException(); + else + return remove(size - 1); + } + } + /** * Removes the first occurrence of the specified element from this list, * if it is present. If this list does not contain the element, it is @@ -1358,6 +1442,24 @@ public E get(int index) { } } + public E getFirst() { + synchronized (lock) { + if (size == 0) + throw new NoSuchElementException(); + else + return get(0); + } + } + + public E getLast() { + synchronized (lock) { + if (size == 0) + throw new NoSuchElementException(); + else + return get(size - 1); + } + } + public int size() { synchronized (lock) { checkForComodification(); @@ -1385,6 +1487,16 @@ public void add(int index, E element) { } } + public void addFirst(E e) { + add(0, e); + } + + public void addLast(E e) { + synchronized (lock) { + add(size, e); + } + } + public boolean addAll(Collection c) { synchronized (lock) { final Object[] oldArray = getArrayChecked(); @@ -1426,6 +1538,24 @@ public E remove(int index) { } } + public E removeFirst() { + synchronized (lock) { + if (size == 0) + throw new NoSuchElementException(); + else + return remove(0); + } + } + + public E removeLast() { + synchronized (lock) { + if (size == 0) + throw new NoSuchElementException(); + else + return remove(size - 1); + } + } + public boolean remove(Object o) { synchronized (lock) { checkForComodification(); @@ -1524,6 +1654,9 @@ public Spliterator spliterator() { } } + public List reversed() { + return new Reversed<>(this, lock); + } } private static class COWSubListIterator implements ListIterator { @@ -1589,6 +1722,365 @@ public void forEachRemaining(Consumer action) { } } + /** + * {@inheritDoc} + *

+ * Modifications to the reversed view are permitted and will be propagated + * to this list. In addition, modifications to this list will be visible + * in the reversed view. Sublists and iterators of the reversed view have + * the same restrictions as those of this list. + * + * @since 21 + */ + public List reversed() { + return new Reversed<>(this, lock); + } + + /** + * Reversed view for CopyOnWriteArrayList and its sublists. + */ + private static class Reversed implements List, RandomAccess { + final List base; + final Object lock; + + Reversed(List base, Object lock) { + this.base = base; + this.lock = lock; + } + + class DescendingIterator implements Iterator { + final ListIterator it; + DescendingIterator() { + synchronized (lock) { + it = base.listIterator(base.size()); + } + } + public boolean hasNext() { return it.hasPrevious(); } + public E next() { return it.previous(); } + public void remove() { it.remove(); } + } + + class DescendingListIterator implements ListIterator { + final ListIterator it; + final int size; // iterator holds a snapshot of the array so this is constant + + DescendingListIterator(int pos) { + synchronized (lock) { + size = base.size(); + if (pos < 0 || pos > size) + throw new IndexOutOfBoundsException(); + it = base.listIterator(size - pos); + } + } + + public boolean hasNext() { + return it.hasPrevious(); + } + + public E next() { + return it.previous(); + } + + public boolean hasPrevious() { + return it.hasNext(); + } + + public E previous() { + return it.next(); + } + + public int nextIndex() { + return size - it.nextIndex(); + } + + public int previousIndex() { + return nextIndex() - 1; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public void set(E e) { + throw new UnsupportedOperationException(); + } + + public void add(E e) { + throw new UnsupportedOperationException(); + } + } + + // ========== Iterable ========== + + public void forEach(Consumer action) { + for (E e : this) + action.accept(e); + } + + public Iterator iterator() { + return new DescendingIterator(); + } + + public Spliterator spliterator() { + // TODO can probably improve this + return Spliterators.spliteratorUnknownSize(new DescendingIterator(), 0); + } + + // ========== Collection ========== + + public boolean add(E e) { + base.add(0, e); + return true; + } + + public boolean addAll(Collection c) { + @SuppressWarnings("unchecked") + E[] es = (E[]) c.toArray(); + if (es.length > 0) { + ArraysSupport.reverse(es); + base.addAll(0, Arrays.asList(es)); + return true; + } else { + return false; + } + } + + public void clear() { + base.clear(); + } + + public boolean contains(Object o) { + return base.contains(o); + } + + public boolean containsAll(Collection c) { + return base.containsAll(c); + } + + // copied from AbstractList + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof List)) + return false; + + ListIterator e1 = listIterator(); + ListIterator e2 = ((List) o).listIterator(); + while (e1.hasNext() && e2.hasNext()) { + E o1 = e1.next(); + Object o2 = e2.next(); + if (!(o1==null ? o2==null : o1.equals(o2))) + return false; + } + return !(e1.hasNext() || e2.hasNext()); + } + + // copied from AbstractList + public int hashCode() { + int hashCode = 1; + for (E e : this) + hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); + return hashCode; + } + + public boolean isEmpty() { + return base.isEmpty(); + } + + public Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + + public boolean remove(Object o) { + synchronized (lock) { + int index = indexOf(o); + if (index == -1) + return false; + remove(index); + return true; + } + } + + public boolean removeAll(Collection c) { + return base.removeAll(c); + } + + public boolean retainAll(Collection c) { + return base.retainAll(c); + } + + public int size() { + return base.size(); + } + + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + public Object[] toArray() { + return ArraysSupport.reverse(base.toArray()); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + // TODO optimize this + return toArray(i -> (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), i)); + } + + public T[] toArray(IntFunction generator) { + return ArraysSupport.reverse(base.toArray(generator)); + } + + // copied from AbstractCollection + public String toString() { + Iterator it = iterator(); + if (! it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + E e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (! it.hasNext()) + return sb.append(']').toString(); + sb.append(',').append(' '); + } + } + + // ========== List ========== + + public void add(int index, E element) { + synchronized (lock) { + base.add(base.size() - index, element); + } + } + + public void addFirst(E e) { + base.add(e); + } + + public void addLast(E e) { + base.add(0, e); + } + + public boolean addAll(int index, Collection c) { + @SuppressWarnings("unchecked") + E[] es = (E[]) c.toArray(); + if (es.length > 0) { + ArraysSupport.reverse(es); + synchronized (lock) { + base.addAll(base.size() - index, Arrays.asList(es)); + } + return true; + } else { + return false; + } + } + + public E get(int i) { + synchronized (lock) { + return base.get(base.size() - i - 1); + } + } + + public E getFirst() { + synchronized (lock) { + int size = base.size(); + if (size == 0) + throw new NoSuchElementException(); + else + return base.get(size - 1); + } + } + + public E getLast() { + synchronized (lock) { + if (base.size() == 0) + throw new NoSuchElementException(); + else + return base.get(0); + } + } + + public int indexOf(Object o) { + synchronized (lock) { + int i = base.lastIndexOf(o); + return i == -1 ? -1 : base.size() - i - 1; + } + } + + public int lastIndexOf(Object o) { + synchronized (lock) { + int i = base.indexOf(o); + return i == -1 ? -1 : base.size() - i - 1; + } + } + + public ListIterator listIterator() { + return new DescendingListIterator(0); + } + + public ListIterator listIterator(int index) { + return new DescendingListIterator(index); + } + + public E remove(int index) { + synchronized (lock) { + return base.remove(base.size() - index - 1); + } + } + + public E removeFirst() { + synchronized (lock) { + int size = base.size(); + if (size == 0) + throw new NoSuchElementException(); + else + return base.remove(size - 1); + } + } + + public E removeLast() { + synchronized (lock) { + if (base.size() == 0) + throw new NoSuchElementException(); + else + return base.remove(0); + } + } + + public boolean removeIf(Predicate filter) { + return base.removeIf(filter); + } + + public void replaceAll(UnaryOperator operator) { + base.replaceAll(operator); + } + + public void sort(Comparator c) { + base.sort(Collections.reverseOrder(c)); + } + + public E set(int index, E element) { + synchronized (lock) { + return base.set(base.size() - index - 1, element); + } + } + + public List subList(int fromIndex, int toIndex) { + synchronized (lock) { + int size = base.size(); + var sub = base.subList(size - toIndex, size - fromIndex); + return new Reversed<>(sub, lock); + } + } + + public List reversed() { + return base; + } + } + /** Initializes the lock; for use when deserializing or cloning. */ private void resetLock() { @SuppressWarnings("removal") diff --git a/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java b/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java index b9d0e29e449f5..61be28519fc24 100644 --- a/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java +++ b/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java @@ -24,6 +24,8 @@ */ package jdk.internal.util; +import java.util.Arrays; +import java.util.Collection; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; @@ -755,4 +757,53 @@ private static int hugeLength(int oldLength, int minGrowth) { return minLength; } } + + /** + * Reverses the elements of an array in-place. + * + * @param the array component type + * @param a the array to be reversed + * @return the reversed array, always the same array as the argument + */ + public static T[] reverse(T[] a) { + int limit = a.length / 2; + for (int i = 0, j = a.length - 1; i < limit; i++, j--) { + T t = a[i]; + a[i] = a[j]; + a[j] = t; + } + return a; + } + + /** + * Dump the contents of the given collection into the given array, in reverse order. + * This mirrors the semantics of Collection.toArray(T[]) in regard to reusing the given + * array, appending null if necessary, or allocating a new array of the same component type. + *

+ * A constraint is that this method should issue exactly one method call on the collection + * to obtain the elements and the size. Having a separate size() call or using an Iterator + * could result in errors if the collection changes size between calls. This implies that + * the elements need to be obtained via a single call to one of the toArray() methods. + * This further implies allocating memory proportional to the number of elements and + * making an extra copy, but this seems unavoidable. + *

+ * An obvious approach would be simply to call coll.toArray(array) and then reverse the + * order of the elements. This doesn't work, because if given array is sufficiently long, + * we cannot tell how many elements were copied into it and thus there is no way to reverse + * the right set of elements while leaving the remaining array elements undisturbed. + * + * @throws ArrayStoreException if coll contains elements that can't be stored in the array + */ + public static T[] toArrayReversed(Collection coll, T[] array) { + T[] newArray = reverse(coll.toArray(Arrays.copyOfRange(array, 0, 0))); + if (newArray.length > array.length) { + return newArray; + } else { + System.arraycopy(newArray, 0, array, 0, newArray.length); + if (array.length > newArray.length) { + array[newArray.length] = null; + } + return array; + } + } } diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index c4a9fe9dcd773..035f855a728a9 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -171,6 +171,7 @@ jdk_collections_core = \ java/util/Map \ java/util/NavigableMap \ java/util/PriorityQueue \ + java/util/SequencedCollection \ java/util/TimSort \ java/util/TreeMap \ java/util/Vector \ diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index ccaca2fc934ea..bbc332cc21109 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -137,9 +137,13 @@ public static void realMain(String[] args) { testMap(Collections.synchronizedNavigableMap(new TreeMap())); // Unmodifiable wrappers - testImmutableSet(unmodifiableSet(new HashSet<>(Arrays.asList(1,2,3)))); + testImmutableSet(unmodifiableSet(new HashSet<>(Arrays.asList(1,2,3))), 99); testImmutableList(unmodifiableList(Arrays.asList(1,2,3))); testImmutableMap(unmodifiableMap(Collections.singletonMap(1,2))); + testImmutableSeqColl(unmodifiableSequencedCollection(Arrays.asList(1,2,3)), 99); + testImmutableSeqColl(unmodifiableSequencedSet(new LinkedHashSet<>(Arrays.asList(1,2,3))), 99); + var lhm = new LinkedHashMap(); lhm.put(1,2); lhm.put(3, 4); + testImmutableSeqMap(unmodifiableSequencedMap(lhm)); testCollMutatorsAlwaysThrow(unmodifiableSet(new HashSet<>(Arrays.asList(1,2,3)))); testCollMutatorsAlwaysThrow(unmodifiableSet(Collections.emptySet())); testEmptyCollMutatorsAlwaysThrow(unmodifiableSet(Collections.emptySet())); @@ -169,7 +173,7 @@ public static void realMain(String[] args) { testEmptySet(Collections.emptySet()); testEmptySet(Collections.emptySortedSet()); testEmptySet(Collections.emptyNavigableSet()); - testImmutableSet(emptySet); + testImmutableSet(emptySet, 99); List emptyList = emptyList(); testCollection(emptyList); @@ -194,7 +198,7 @@ public static void realMain(String[] args) { Set singletonSet = singleton(1); equal(singletonSet.size(), 1); testCollection(singletonSet); - testImmutableSet(singletonSet); + testImmutableSet(singletonSet, 99); List singletonList = singletonList(1); equal(singletonList.size(), 1); @@ -322,20 +326,20 @@ public static void realMain(String[] args) { Set.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), Set.of(integerArray))) { testCollection(set); - testImmutableSet(set); + testImmutableSet(set, 99); testCollMutatorsAlwaysThrow(set); } Set setCopy = Set.copyOf(Arrays.asList(1, 2, 3)); testCollection(setCopy); - testImmutableSet(setCopy); + testImmutableSet(setCopy, 99); testCollMutatorsAlwaysThrow(setCopy); Set setCollected = Stream.of(1, 1, 2, 3, 2, 3) .collect(Collectors.toUnmodifiableSet()); equal(setCollected, Set.of(1, 2, 3)); testCollection(setCollected); - testImmutableSet(setCollected); + testImmutableSet(setCollected, 99); testCollMutatorsAlwaysThrow(setCollected); // Immutable Map @@ -462,12 +466,12 @@ private static void testEmptySet(Set c) { testEmptyIterator(((NavigableSet)c).descendingIterator()); } - private static void testImmutableCollection(final Collection c) { + private static void testImmutableCollection(final Collection c, T t) { THROWS(UnsupportedOperationException.class, - () -> c.add(99), - () -> c.addAll(singleton(99))); + () -> c.add(t), + () -> c.addAll(singleton(t))); if (! c.isEmpty()) { - final Integer first = c.iterator().next(); + final T first = c.iterator().next(); THROWS(UnsupportedOperationException.class, () -> c.clear(), () -> c.remove(first), @@ -476,13 +480,36 @@ private static void testImmutableCollection(final Collection c) { } } - private static void testImmutableSet(final Set c) { - testImmutableCollection(c); + private static void testImmutableSeqColl(final SequencedCollection c, T t) { + SequencedCollection r = c.reversed(); + testImmutableCollection(c, t); + testImmutableCollection(r, t); + THROWS(UnsupportedOperationException.class, + () -> c.addFirst(t), + () -> c.addLast(t), + () -> r.addFirst(t), + () -> r.addLast(t)); + if (! c.isEmpty()) { + THROWS(UnsupportedOperationException.class, + () -> c.removeFirst(), + () -> c.removeLast(), + () -> r.removeFirst(), + () -> r.removeLast()); + } + } + + private static void testImmutableSet(final Set c, T t) { + testImmutableCollection(c, t); + } + + private static void testImmutableSeqSet(final SequencedSet c, T t) { + testImmutableSeqColl(c, t); } private static void testImmutableList(final List c) { testList(c); - testImmutableCollection(c); + testImmutableCollection(c, 42); + testImmutableSeqColl(c, 42); THROWS(UnsupportedOperationException.class, () -> c.set(0,42), () -> c.add(0,42), @@ -606,6 +633,15 @@ private static void testEmptyMap(final Map m) { check(! m.containsKey(1)); } + private static void testImmutableMapEntry(final Map.Entry me) { + Integer key = me.getKey(); + Integer val = me.getValue(); + THROWS(UnsupportedOperationException.class, + () -> me.setValue(3)); + equal(key, me.getKey()); + equal(val, me.getValue()); + } + private static void testImmutableMap(final Map m) { THROWS(UnsupportedOperationException.class, () -> m.put(1,1), @@ -615,18 +651,39 @@ private static void testImmutableMap(final Map m) { THROWS(UnsupportedOperationException.class, () -> m.remove(first), () -> m.clear()); - final Map.Entry me - = m.entrySet().iterator().next(); - Integer key = me.getKey(); - Integer val = me.getValue(); + testImmutableMapEntry(m.entrySet().iterator().next()); + } + testImmutableSet(m.keySet(), 99); + testImmutableCollection(m.values(), 99); + testImmutableSet(m.entrySet(), Map.entry(42, 43)); + } + + private static void testImmutableSeqMap(final SequencedMap m) { + SequencedMap r = m.reversed(); + testImmutableMap(m); + testImmutableMap(r); + THROWS(UnsupportedOperationException.class, + () -> m.putFirst(0, 0), + () -> m.putLast(0, 0), + () -> r.putFirst(0, 0), + () -> r.putLast(0, 0)); + if (! m.isEmpty()) { THROWS(UnsupportedOperationException.class, - () -> me.setValue(3)); - equal(key, me.getKey()); - equal(val, me.getValue()); + () -> m.pollFirstEntry(), + () -> m.pollLastEntry(), + () -> r.pollFirstEntry(), + () -> r.pollLastEntry()); + testImmutableMapEntry(m.sequencedEntrySet().getFirst()); + testImmutableMapEntry(r.sequencedEntrySet().getFirst()); + testImmutableMapEntry(m.sequencedEntrySet().reversed().getFirst()); + testImmutableMapEntry(r.sequencedEntrySet().reversed().getFirst()); } - testImmutableSet(m.keySet()); - testImmutableCollection(m.values()); - //testImmutableSet(m.entrySet()); + testImmutableSeqSet(m.sequencedKeySet(), 99); + testImmutableSeqColl(m.sequencedValues(), 99); + testImmutableSeqSet(m.sequencedEntrySet(), Map.entry(42, 43)); + testImmutableSeqSet(r.sequencedKeySet(), 99); + testImmutableSeqColl(r.sequencedValues(), 99); + testImmutableSeqSet(r.sequencedEntrySet(), Map.entry(42, 43)); } private static void clear(Map m) { diff --git a/test/jdk/java/util/Collections/Wrappers.java b/test/jdk/java/util/Collections/Wrappers.java index 3882a4efdd1ad..22a96b7462348 100644 --- a/test/jdk/java/util/Collections/Wrappers.java +++ b/test/jdk/java/util/Collections/Wrappers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -33,14 +33,13 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Objects; import java.util.TreeMap; import java.util.TreeSet; import org.testng.annotations.Test; import org.testng.annotations.DataProvider; -import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; @Test(groups = "unit") public class Wrappers { @@ -66,9 +65,11 @@ public static Object[][] collectionCases() { } cases.add(new Object[] { Collections.unmodifiableCollection(seedList) }); + cases.add(new Object[] { Collections.unmodifiableSequencedCollection(seedList) }); cases.add(new Object[] { Collections.unmodifiableList(seedList) }); cases.add(new Object[] { Collections.unmodifiableList(seedRandomAccess) }); cases.add(new Object[] { Collections.unmodifiableSet(seedSet) }); + cases.add(new Object[] { Collections.unmodifiableSequencedSet(seedSet) }); cases.add(new Object[] { Collections.unmodifiableSortedSet(seedSet) }); cases.add(new Object[] { Collections.unmodifiableNavigableSet(seedSet) }); @@ -77,6 +78,24 @@ public static Object[][] collectionCases() { cases.add(new Object[] { Collections.unmodifiableMap(seedMap).entrySet() }); cases.add(new Object[] { Collections.unmodifiableMap(seedMap).keySet() }); cases.add(new Object[] { Collections.unmodifiableMap(seedMap).values() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).entrySet() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).keySet() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).values() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().entrySet() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().keySet() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().values() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).sequencedEntrySet() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).sequencedKeySet() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).sequencedValues() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).sequencedEntrySet().reversed() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).sequencedKeySet().reversed() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).sequencedValues().reversed() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().sequencedEntrySet() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().sequencedKeySet() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().sequencedValues() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().sequencedEntrySet().reversed() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().sequencedKeySet().reversed() }); + cases.add(new Object[] { Collections.unmodifiableSequencedMap(seedMap).reversed().sequencedValues().reversed() }); cases.add(new Object[] { Collections.unmodifiableSortedMap(seedMap).entrySet() }); cases.add(new Object[] { Collections.unmodifiableSortedMap(seedMap).keySet() }); cases.add(new Object[] { Collections.unmodifiableSortedMap(seedMap).values() }); @@ -136,11 +155,14 @@ public static Object[][] collectionCases() { @Test(dataProvider = "collections") public static void testAllDefaultMethodsOverridden(Collection c) throws NoSuchMethodException { Class cls = c.getClass(); + var notOverridden = new ArrayList(); for (Method m: defaultMethods) { Method m2 = cls.getMethod(m.getName(), m.getParameterTypes()); - // default had been override - assertFalse(m2.isDefault(), cls.getCanonicalName()); + if (m2.isDefault()) { + notOverridden.add(m); + } } + assertTrue(notOverridden.isEmpty(), cls.getName() + " does not override " + notOverridden); } } diff --git a/test/jdk/java/util/SequencedCollection/Basic.java b/test/jdk/java/util/SequencedCollection/Basic.java new file mode 100644 index 0000000000000..32a5d4e5c9155 --- /dev/null +++ b/test/jdk/java/util/SequencedCollection/Basic.java @@ -0,0 +1,857 @@ +/* + * 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 java.util.concurrent.CopyOnWriteArrayList; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.assertTrue; + +/* + * @test + * @bug 8266571 + * @summary Basic tests for SequencedCollection + * @modules java.base/java.util:open + * @build SimpleDeque SimpleList SimpleSortedSet + * @run testng Basic + */ + +// TODO test that remove(obj) with duplicates removes the right element + +public class Basic { + + // ========== Data Providers ========== + + static final List ORIGINAL = List.of("a", "b", "c", "d", "e", "f", "g"); + + static List cklist(List contents) { + return Collections.checkedList(contents, String.class); + } + + static NavigableSet cknav(NavigableSet set) { + return Collections.checkedNavigableSet(set, String.class); + } + + static SortedSet cksorted(SortedSet set) { + return Collections.checkedSortedSet(set, String.class); + } + + static SequencedSet setFromMap(List contents) { + var lhm = new LinkedHashMap(); + var ss = Collections.newSequencedSetFromMap(lhm); + ss.addAll(contents); + return ss; + } + + static SequencedCollection ucoll(SequencedCollection coll) { + return Collections.unmodifiableSequencedCollection(coll); + } + + static SequencedCollection ulist(List list) { + return Collections.unmodifiableList(list); + } + + static NavigableSet unav(NavigableSet set) { + return Collections.unmodifiableNavigableSet(set); + } + + static SequencedSet uset(SequencedSet set) { + return Collections.unmodifiableSequencedSet(set); + } + + static SortedSet usorted(SortedSet set) { + return Collections.unmodifiableSortedSet(set); + } + + static List copyReversed(List list) { + var r = new ArrayList(list); + Collections.reverse(r); + return r; + } + + @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[] { "ArrayDeque", new ArrayDeque<>(ORIGINAL), ORIGINAL }, + new Object[] { "ArrayList", new ArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "AsList", Arrays.asList(ORIGINAL.toArray()), ORIGINAL }, + new Object[] { "COWAL", new CopyOnWriteArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedHashSet", new LinkedHashSet<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedList", new LinkedList<>(ORIGINAL), ORIGINAL }, + new Object[] { "ListOf", ORIGINAL, ORIGINAL }, + new Object[] { "SetFromMap", setFromMap(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleDeque", new SimpleDeque<>(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleList", new SimpleList<>(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleSortedSet", new SimpleSortedSet<>(ORIGINAL), ORIGINAL }, + new Object[] { "TreeSet", new TreeSet<>(ORIGINAL), ORIGINAL }, + new Object[] { "UnmodColl", ucoll(new ArrayList<>(ORIGINAL)), ORIGINAL }, + new Object[] { "UnmodSet", uset(new LinkedHashSet<>(ORIGINAL)), ORIGINAL } + ).iterator(); + } + + @DataProvider(name="empties") + public Iterator empties() { + return Arrays.asList( + new Object[] { "ArrayDeque", new ArrayDeque<>(), List.of() }, + new Object[] { "ArrayList", new ArrayList<>(), List.of() }, + new Object[] { "AsList", Arrays.asList(new String[0]), List.of() }, + new Object[] { "COWAL", new CopyOnWriteArrayList<>(), List.of() }, + new Object[] { "EmptyList", Collections.emptyList(), List.of() }, + new Object[] { "EmptyNavigableSet", Collections.emptyNavigableSet(), List.of() }, + new Object[] { "EmptySortedSet", Collections.emptySortedSet(), List.of() }, + new Object[] { "LinkedHashSet", new LinkedHashSet<>(), List.of() }, + new Object[] { "LinkedList", new LinkedList<>(), List.of() }, + new Object[] { "ListOf", List.of(), List.of() }, + new Object[] { "SetFromMap", setFromMap(List.of()), List.of() }, + new Object[] { "SimpleDeque", new SimpleDeque<>(), List.of() }, + new Object[] { "SimpleList", new SimpleList<>(), List.of() }, + new Object[] { "SimpleSortedSet", new SimpleSortedSet<>(), List.of() }, + new Object[] { "TreeSet", new TreeSet<>(), List.of() }, + new Object[] { "UnmodColl", ucoll(new ArrayList<>()), List.of() }, + new Object[] { "UnmodSet", uset(new LinkedHashSet<>()), List.of() } + ).iterator(); + } + + @DataProvider(name="adds") + public Iterator adds() { + return Arrays.asList( + new Object[] { "ArrayDeque", new ArrayDeque<>(ORIGINAL), ORIGINAL }, + new Object[] { "ArrayList", new ArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "COWAL", new CopyOnWriteArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedHashSet", new LinkedHashSet<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedList", new LinkedList<>(ORIGINAL), ORIGINAL }, + new Object[] { "SetFromMap", setFromMap(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleDeque", new SimpleDeque<>(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleList", new SimpleList<>(ORIGINAL), ORIGINAL } + ).iterator(); + } + + @DataProvider(name="unpositionedAdd") + public Iterator unpositionedAdd() { + return Arrays.asList( + new Object[] { "LinkedHashSet", new LinkedHashSet<>(ORIGINAL), ORIGINAL } + ).iterator(); + } + + @DataProvider(name="removes") + public Iterator removes() { + return Arrays.asList( + new Object[] { "ArrayDeque", new ArrayDeque<>(ORIGINAL), ORIGINAL }, + new Object[] { "ArrayList", new ArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "COWAL", new CopyOnWriteArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedHashSet", new LinkedHashSet<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedList", new LinkedList<>(ORIGINAL), ORIGINAL }, + new Object[] { "SetFromMap", setFromMap(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleDeque", new SimpleDeque<>(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleList", new SimpleList<>(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleSortedSet", new SimpleSortedSet<>(ORIGINAL), ORIGINAL }, + new Object[] { "TreeSet", new TreeSet<>(ORIGINAL), ORIGINAL } + ).iterator(); + } + + @DataProvider(name="emptyRemoves") + public Iterator emptyRemoves() { + return Arrays.asList( + new Object[] { "ArrayDeque", new ArrayDeque<>(), List.of() }, + new Object[] { "ArrayList", new ArrayList<>(), List.of() }, + new Object[] { "COWAL", new CopyOnWriteArrayList<>(), List.of() }, + new Object[] { "LinkedHashSet", new LinkedHashSet<>(), List.of() }, + new Object[] { "LinkedList", new LinkedList<>(), List.of() }, + new Object[] { "SetFromMap", setFromMap(List.of()), List.of() }, + new Object[] { "SimpleDeque", new SimpleDeque<>(), List.of() }, + new Object[] { "SimpleList", new SimpleList<>(), List.of() }, + new Object[] { "SimpleSortedSet", new SimpleSortedSet<>(), List.of() }, + new Object[] { "TreeSet", new TreeSet<>(), List.of() } + ).iterator(); + } + + @DataProvider(name="serializable") + public Iterator serializable() { + return Arrays.asList( + new Object[] { "ArrayDeque", new ArrayDeque<>(ORIGINAL), ORIGINAL }, + new Object[] { "ArrayList", new ArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "AsList", Arrays.asList(ORIGINAL.toArray()), ORIGINAL }, + new Object[] { "COWAL", new CopyOnWriteArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedHashSet", new LinkedHashSet<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedList", new LinkedList<>(ORIGINAL), ORIGINAL }, + new Object[] { "ListOf", ORIGINAL, ORIGINAL }, + new Object[] { "SetFromMap", setFromMap(ORIGINAL), ORIGINAL }, + new Object[] { "TreeSet", new TreeSet<>(ORIGINAL), ORIGINAL }, + new Object[] { "UnmodColl", ucoll(new ArrayList<>(ORIGINAL)), ORIGINAL }, + new Object[] { "UnmodSet", uset(new LinkedHashSet<>(ORIGINAL)), ORIGINAL } + ).iterator(); + } + + @DataProvider(name="notSerializable") + public Iterator notSerializable() { + return Arrays.asList( + new Object[] { "ArrayDeque", new ArrayDeque<>(ORIGINAL).reversed() }, + new Object[] { "ArrayList", new ArrayList<>(ORIGINAL).reversed() }, + new Object[] { "AsList", Arrays.asList(ORIGINAL.toArray()).reversed() }, + new Object[] { "COWAL", new CopyOnWriteArrayList<>(ORIGINAL).reversed() }, + new Object[] { "LinkedHashSet", new LinkedHashSet<>(ORIGINAL).reversed() }, + new Object[] { "LinkedList", new LinkedList<>(ORIGINAL).reversed() }, + new Object[] { "ListOf", ORIGINAL.reversed() }, + new Object[] { "SetFromMap", setFromMap(ORIGINAL).reversed() }, + new Object[] { "UnmodColl", ucoll(new ArrayList<>(ORIGINAL)).reversed() }, + new Object[] { "UnmodSet", uset(new LinkedHashSet<>(ORIGINAL)).reversed() } + ).iterator(); + } + + @DataProvider(name="doubleReverse") + public Iterator doubleReverse() { + return Arrays.asList( + new Object[] { "ArrayDeque", new ArrayDeque<>(ORIGINAL) }, + new Object[] { "ArrayList", new ArrayList<>(ORIGINAL) }, + new Object[] { "AsList", Arrays.asList(ORIGINAL.toArray()) }, + new Object[] { "COWAL", new CopyOnWriteArrayList<>(ORIGINAL) }, + new Object[] { "LinkedHashSet", new LinkedHashSet<>(ORIGINAL) }, + new Object[] { "LinkedList", new LinkedList<>(ORIGINAL) }, + new Object[] { "ListOf", ORIGINAL }, + new Object[] { "SimpleDeque", new SimpleDeque<>(ORIGINAL) }, + new Object[] { "SimpleList", new SimpleList<>(ORIGINAL) }, + new Object[] { "SimpleSortedSet", new SimpleSortedSet<>(ORIGINAL) } + ).iterator(); + } + + @DataProvider(name="unmodifiable") + public Iterator unmodifiable() { + return Arrays.asList( + new Object[] { "ListOf", ORIGINAL, ORIGINAL }, + new Object[] { "ListOfSub", ORIGINAL.subList(1, 3), ORIGINAL.subList(1, 3) }, + new Object[] { "SingleList", Collections.singletonList("a"), List.of("a") }, + new Object[] { "UnmodColl", ucoll(new ArrayList<>(ORIGINAL)), ORIGINAL }, + new Object[] { "UnmodList", ulist(new ArrayList<>(ORIGINAL)), ORIGINAL }, + new Object[] { "UnmodNav", unav(new TreeSet<>(ORIGINAL)), ORIGINAL }, + new Object[] { "UnmodSet", uset(new LinkedHashSet<>(ORIGINAL)), ORIGINAL }, + new Object[] { "UnmodSorted", usorted(new TreeSet<>(ORIGINAL)), ORIGINAL } + ).iterator(); + } + + @DataProvider(name="checkedList") + public Iterator checkedList() { + return Arrays.asList( + new Object[] { "ChkList", cklist(new ArrayList<>(ORIGINAL)), ORIGINAL } + ).iterator(); + } + + @DataProvider(name="checkedNavSet") + public Iterator checkedNavSet() { + return Arrays.asList( + new Object[] { "ChkNav", cknav(new TreeSet<>(ORIGINAL)), ORIGINAL } + ).iterator(); + } + + @DataProvider(name="checkedSortedSet") + public Iterator checkedSortedSet() { + return Arrays.asList( + new Object[] { "ChkSorted", cksorted(new TreeSet<>(ORIGINAL)), ORIGINAL } + ).iterator(); + } + + // mode bit tests for subList testing + + boolean reverseList(int mode) { return (mode & 1) != 0; } + boolean reverseSub(int mode) { return (mode & 2) != 0; } + boolean isReversed(int mode) { return reverseList(mode) ^ reverseSub(mode); } + + List applyMode(int mode, List base) { + var list = reverseList(mode) ? base.reversed() : base; + var sub = list.subList(2, 5); + return reverseSub(mode) ? sub.reversed() : sub; + } + + /** + * Generate cases for testing subLists. For each different List implementation, generate 4 + * cases from the two bits of the testing mode int value: + * + * (bit 1) if true, the List is reversed + * (bit 2) if true, the subList is reversed + * + * @return the generated cases + */ + @DataProvider(name="subListMods") + public Iterator subListMods() { + var cases = new ArrayList(); + for (int mode = 0; mode < 4; mode++) { + cases.addAll(Arrays.asList( + new Object[] { "ArrayList", mode, new ArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "COWAL", mode, new CopyOnWriteArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedList", mode, new LinkedList<>(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleList", mode, new SimpleList<>(ORIGINAL), ORIGINAL } + )); + } + return cases.iterator(); + } + + @DataProvider(name="iteratorMods") + public Iterator iteratorMods() { + var cases = new ArrayList(); + for (boolean rev : List.of(false, true)) { + cases.addAll(Arrays.asList( + new Object[] { "ArrayList", rev, new ArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedList", rev, new LinkedList<>(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleList", rev, new SimpleList<>(ORIGINAL), ORIGINAL } + )); + } + return cases.iterator(); + } + + @DataProvider(name="subListIteratorMods") + public Iterator subListIteratorMods() { + var cases = new ArrayList(); + for (int mode = 0; mode < 4; mode++) { + cases.addAll(Arrays.asList( + new Object[] { "ArrayList", mode, new ArrayList<>(ORIGINAL), ORIGINAL }, + new Object[] { "LinkedList", mode, new LinkedList<>(ORIGINAL), ORIGINAL }, + new Object[] { "SimpleList", mode, new SimpleList<>(ORIGINAL), ORIGINAL } + )); + } + return cases.iterator(); + } + + // ========== Assertions ========== + + /** + * Basic checks over the contents of a SequencedCollection, + * compared to a reference List, in one direction. + * + * @param seq the SequencedCollection under test + * @param ref the reference List + */ + public void checkContents1(SequencedCollection seq, List ref) { + var list1 = new ArrayList(); + for (var s : seq) + list1.add(s); + assertEquals(list1, ref); + + var list2 = new ArrayList(); + seq.forEach(list2::add); + assertEquals(list2, ref); + + var list3 = Arrays.asList(seq.toArray()); + assertEquals(list3, ref); + + var list4 = Arrays.asList(seq.toArray(new String[0])); + assertEquals(list4, ref); + + var list5 = Arrays.asList(seq.toArray(String[]::new)); + assertEquals(list5, ref); + + var list6 = seq.stream().toList(); + assertEquals(list6, ref); + + var list7 = seq.parallelStream().toList(); + assertEquals(list7, ref); + + assertEquals(seq.size(), ref.size()); + assertEquals(seq.isEmpty(), ref.isEmpty()); + + for (var s : ref) { + assertTrue(seq.contains(s)); + } + } + + /** + * Check the contents of a SequencedCollection against a reference List, + * in both directions. + * + * @param seq the SequencedCollection under test + * @param ref the reference List + */ + public void checkContents(SequencedCollection seq, List ref) { + checkContents1(seq, ref); + + var rref = copyReversed(ref); + var rseq = seq.reversed(); + checkContents1(rseq, rref); + + var rrseq = rseq.reversed(); + checkContents1(rrseq, ref); + } + + /** + * Check that modification operations will throw UnsupportedOperationException, + * in one direction. + * + * @param seq the SequencedCollection under test + */ + public void checkUnmodifiable1(SequencedCollection seq) { + final var UOE = UnsupportedOperationException.class; + + assertThrows(UOE, () -> seq.add("x")); + assertThrows(UOE, () -> seq.clear()); + assertThrows(UOE, () -> { var it = seq.iterator(); it.next(); it.remove(); }); + assertThrows(UOE, () -> seq.removeIf(x -> true)); + + assertThrows(UOE, () -> seq.addFirst("x")); + assertThrows(UOE, () -> seq.addLast("x")); + assertThrows(UOE, () -> seq.removeFirst()); + assertThrows(UOE, () -> seq.removeLast()); + +// TODO these ops should throw unconditionally, but they don't in some implementations + // assertThrows(UOE, () -> seq.addAll(List.of())); + // assertThrows(UOE, () -> seq.remove("x")); + // assertThrows(UOE, () -> seq.removeAll(List.of())); + // assertThrows(UOE, () -> seq.removeIf(x -> false)); + // assertThrows(UOE, () -> seq.retainAll(seq)); + assertThrows(UOE, () -> seq.addAll(seq)); + assertThrows(UOE, () -> seq.remove(seq.iterator().next())); + assertThrows(UOE, () -> seq.removeAll(seq)); + assertThrows(UOE, () -> seq.retainAll(List.of())); + } + + /** + * Check that modification operations will throw UnsupportedOperationException, + * in both directions. + * + * @param seq the SequencedCollection under test + */ + public void checkUnmodifiable(SequencedCollection seq) { + checkUnmodifiable1(seq); + checkUnmodifiable1(seq.reversed()); + } + + static final Class CCE = ClassCastException.class; + + public void checkCheckedList(List list) { + List objList = (List)(List)list; + assertThrows(CCE, () -> { objList.addFirst(new Object()); }); + assertThrows(CCE, () -> { objList.addLast(new Object()); }); + assertThrows(CCE, () -> { objList.reversed().addFirst(new Object()); }); + assertThrows(CCE, () -> { objList.reversed().addLast(new Object()); }); + } + + public void checkCheckedNavSet(NavigableSet set) { + NavigableSet objSet = (NavigableSet)(NavigableSet)set; + assertThrows(CCE, () -> { objSet.add(new Object()); }); + assertThrows(CCE, () -> { objSet.reversed().add(new Object()); }); + } + + public void checkCheckedSortedSet(SortedSet set) { + SortedSet objSet = (SortedSet)(SortedSet)set; + assertThrows(CCE, () -> { objSet.add(new Object()); }); + assertThrows(CCE, () -> { objSet.reversed().add(new Object()); }); + } + + // ========== Tests ========== + + @Test(dataProvider="all") + public void testFundamentals(String label, SequencedCollection seq, List ref) { + checkContents(seq, ref); + } + + @Test(dataProvider="populated") + public void testGetFirst(String label, SequencedCollection seq, List ref) { + assertEquals(seq.getFirst(), ref.get(0)); + assertEquals(seq.reversed().getFirst(), ref.get(ref.size() - 1)); + checkContents(seq, ref); + } + + @Test(dataProvider="populated") + public void testGetLast(String label, SequencedCollection seq, List ref) { + assertEquals(seq.getLast(), ref.get(ref.size() - 1)); + assertEquals(seq.reversed().getLast(), ref.get(0)); + checkContents(seq, ref); + } + + @Test(dataProvider="empties") + public void testEmptyGetFirst(String label, SequencedCollection seq, List ref) { + assertThrows(NoSuchElementException.class, () -> seq.getFirst()); + assertThrows(NoSuchElementException.class, () -> seq.reversed().getFirst()); + checkContents(seq, ref); + } + + @Test(dataProvider="empties") + public void testEmptyGetLast(String label, SequencedCollection seq, List ref) { + assertThrows(NoSuchElementException.class, () -> seq.getLast()); + assertThrows(NoSuchElementException.class, () -> seq.reversed().getLast()); + checkContents(seq, ref); + } + + @Test(dataProvider="adds") + public void testAddFirst(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + ref.add(0, "x"); + seq.addFirst("x"); + checkContents(seq, ref); + } + + @Test(dataProvider="adds") + public void testAddFirstRev(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + ref.add("x"); + seq.reversed().addFirst("x"); + checkContents(seq, ref); + } + + @Test(dataProvider="adds") + public void testAddLast(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + ref.add("x"); + seq.addLast("x"); + checkContents(seq, ref); + } + + @Test(dataProvider="adds") + public void testAddLastRev(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + ref.add(0, "x"); + seq.reversed().addLast("x"); + checkContents(seq, ref); + } + + @Test(dataProvider="unpositionedAdd") + public void testUnpositionedAdd(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + ref.add("x"); + seq.add("x"); + checkContents(seq, ref); + } + + @Test(dataProvider="unpositionedAdd") + public void testUnpositionedAddRev(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + ref.add("x"); + seq.reversed().add("x"); + checkContents(seq, ref); + } + + @Test(dataProvider="removes") + public void testRemoveFirst(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + var exp = ref.remove(0); + var act = seq.removeFirst(); + assertEquals(act, exp); + checkContents(seq, ref); + } + + @Test(dataProvider="removes") + public void testRemoveFirstRev(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + var exp = ref.remove(ref.size() - 1); + var act = seq.reversed().removeFirst(); + assertEquals(act, exp); + checkContents(seq, ref); + } + + @Test(dataProvider="removes") + public void testRemoveLast(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + var exp = ref.remove(ref.size() - 1); + var act = seq.removeLast(); + assertEquals(act, exp); + checkContents(seq, ref); + } + + @Test(dataProvider="removes") + public void testRemoveLastRev(String label, SequencedCollection seq, List baseref) { + var ref = new ArrayList<>(baseref); + var exp = ref.remove(0); + var act = seq.reversed().removeLast(); + assertEquals(act, exp); + checkContents(seq, ref); + } + + @Test(dataProvider="emptyRemoves") + public void testEmptyRemoveFirst(String label, SequencedCollection seq, List baseref) { + assertThrows(NoSuchElementException.class, () -> seq.removeFirst()); + assertThrows(NoSuchElementException.class, () -> seq.reversed().removeFirst()); + checkContents(seq, baseref); + } + + @Test(dataProvider="emptyRemoves") + public void testEmptyRemoveLast(String label, SequencedCollection seq, List baseref) { + assertThrows(NoSuchElementException.class, () -> seq.removeLast()); + assertThrows(NoSuchElementException.class, () -> seq.reversed().removeLast()); + checkContents(seq, baseref); + } + + @Test(dataProvider="serializable") + public void testSerializable(String label, SequencedCollection seq, List ref) + throws ClassNotFoundException, IOException + { + var baos = new ByteArrayOutputStream(); + try (var oos = new ObjectOutputStream(baos)) { + oos.writeObject(seq); + } + + try (var bais = new ByteArrayInputStream(baos.toByteArray()); + var ois = new ObjectInputStream(bais)) { + var seq2 = (SequencedCollection) ois.readObject(); + checkContents(seq2, ref); + } + } + + @Test(dataProvider="notSerializable") + public void testNotSerializable(String label, SequencedCollection seq) + throws ClassNotFoundException, IOException + { + var baos = new ByteArrayOutputStream(); + try (var oos = new ObjectOutputStream(baos)) { + assertThrows(ObjectStreamException.class, () -> oos.writeObject(seq)); + } + } + + @Test(dataProvider="doubleReverse") + public void testDoubleReverse(String label, SequencedCollection seq) { + var rrseq = seq.reversed().reversed(); + assertSame(rrseq, seq); + } + + @Test(dataProvider="unmodifiable") + public void testUnmodifiable(String label, SequencedCollection seq, List ref) { + checkUnmodifiable(seq); + checkContents(seq, ref); + } + + @Test(dataProvider="checkedList") + public void testCheckedList(String label, List list, List ref) { + checkCheckedList(list); + checkContents(list, ref); + } + + @Test(dataProvider="checkedNavSet") + public void testCheckedNavSet(String label, NavigableSet set, List ref) { + checkCheckedNavSet(set); + checkContents(set, ref); + } + + @Test(dataProvider="checkedSortedSet") + public void testCheckedSortedSet(String label, SortedSet set, List ref) { + checkCheckedSortedSet(set); + checkContents(set, ref); + } + + // Indexes for subList modification tests: + // 0 1 2 3 4 5 6 + // a, b, c, d, e, f, g + // c, d, e + + @Test(dataProvider="subListMods") + public void testSubListGet(String label, int mode, List list, List base) { + var sub = applyMode(mode, list); + assertEquals(sub.getFirst(), isReversed(mode) ? "e" : "c"); + assertEquals(sub.getLast(), isReversed(mode) ? "c" : "e"); + } + + @Test(dataProvider="subListMods") + public void testSubListAddFirst(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + refList.add(isReversed(mode) ? 5 : 2, "x"); + sub.addFirst("x"); + refSub.add(0, "x"); + + checkContents(sub, refSub); + checkContents(list, refList); + } + + @Test(dataProvider="subListMods") + public void testSubListAddLast(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + refList.add(isReversed(mode) ? 2 : 5, "x"); + sub.addLast("x"); + refSub.add("x"); + + checkContents(sub, refSub); + checkContents(list, refList); + } + + @Test(dataProvider="subListMods") + public void testSubListRemoveFirst(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + refList.remove(isReversed(mode) ? 4 : 2); + var act = sub.removeFirst(); + var exp = refSub.remove(0); + + assertEquals(act, exp); + checkContents(sub, refSub); + checkContents(list, refList); + } + + @Test(dataProvider="subListMods") + public void testSubListRemoveLast(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + refList.remove(isReversed(mode) ? 2 : 4); + var act = sub.removeLast(); + var exp = refSub.remove(refSub.size() - 1); + + assertEquals(act, exp); + checkContents(sub, refSub); + checkContents(list, refList); + } + + @Test(dataProvider="subListMods") + public void testSubListAddAllFirst(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + if (isReversed(mode)) + refList.addAll(5, List.of("y", "x")); + else + refList.addAll(2, List.of("x", "y")); + sub.addAll(0, List.of("x", "y")); + refSub.addAll(0, List.of("x", "y")); + + checkContents(sub, refSub); + checkContents(list, refList); + } + + @Test(dataProvider="subListMods") + public void testSubListAddAllLast(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + if (isReversed(mode)) + refList.addAll(2, List.of("y", "x")); + else + refList.addAll(5, List.of("x", "y")); + sub.addAll(List.of("x", "y")); + refSub.addAll(List.of("x", "y")); + + checkContents(sub, refSub); + checkContents(list, refList); + } + + @Test(dataProvider="iteratorMods") + public void testListIteratorAdd(String label, boolean rev, List list, List base) { + var ref = new ArrayList<>(base); + var it = (rev ? list.reversed() : list).listIterator(); + + ref.add(rev ? 5 : 2, "x"); + it.next(); + it.next(); + it.add("x"); + + assertEquals(it.next(), rev ? "e" : "c"); + checkContents(list, ref); + } + + @Test(dataProvider="iteratorMods") + public void testListIteratorSet(String label, boolean rev, List list, List base) { + var ref = new ArrayList<>(base); + var it = (rev ? list.reversed() : list).listIterator(); + + ref.set(rev ? 5 : 1, "x"); + it.next(); + it.next(); + it.set("x"); + + assertEquals(it.next(), rev ? "e" : "c"); + checkContents(list, ref); + } + + @Test(dataProvider="iteratorMods") + public void testListIteratorRemove(String label, boolean rev, List list, List base) { + var ref = new ArrayList<>(base); + var it = (rev ? list.reversed() : list).listIterator(); + + ref.remove(rev ? 5 : 1); + it.next(); + it.next(); + it.remove(); + + assertEquals(it.next(), rev ? "e" : "c"); + checkContents(list, ref); + } + + // SubList ListIterator modification tests. + + @Test(dataProvider="subListIteratorMods") + public void testSubListIteratorAdd(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + var it = sub.listIterator(); + it.next(); + it.add("x"); + refList.add(isReversed(mode) ? 4 : 3, "x"); + refSub.add(1, "x"); + + assertEquals(it.next(), "d"); + checkContents(sub, refSub); + checkContents(list, refList); + } + + @Test(dataProvider="subListIteratorMods") + public void testSubListIteratorSet(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + var it = sub.listIterator(); + it.next(); + it.set("x"); + refList.set(isReversed(mode) ? 4 : 2, "x"); + refSub.set(0, "x"); + + assertEquals(it.next(), "d"); + checkContents(sub, refSub); + checkContents(list, refList); + } + + @Test(dataProvider="subListIteratorMods") + public void testSubListIteratorRemove(String label, int mode, List list, List base) { + var refList = new ArrayList<>(base); + var sub = applyMode(mode, list); + var refSub = new ArrayList<>(sub); + + var it = sub.listIterator(); + it.next(); + it.remove(); + refList.remove(isReversed(mode) ? 4 : 2); + refSub.remove(0); + + assertEquals(it.next(), "d"); + checkContents(sub, refSub); + checkContents(list, refList); + } +} diff --git a/test/jdk/java/util/SequencedCollection/BasicMap.java b/test/jdk/java/util/SequencedCollection/BasicMap.java new file mode 100644 index 0000000000000..96a18cc88b76b --- /dev/null +++ b/test/jdk/java/util/SequencedCollection/BasicMap.java @@ -0,0 +1,892 @@ +/* + * 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(); + } + + // ========== 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); + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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()); }); + } + + // ========== 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); + } +} diff --git a/test/jdk/java/util/SequencedCollection/SimpleDeque.java b/test/jdk/java/util/SequencedCollection/SimpleDeque.java new file mode 100644 index 0000000000000..c1ed07210fbac --- /dev/null +++ b/test/jdk/java/util/SequencedCollection/SimpleDeque.java @@ -0,0 +1,200 @@ +/* + * 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.util.*; + +/** + * A complete Deque implementation that inherits the reversed() method + * from SequencedCollection. Useful for testing ReverseOrderDequeView. + * Underlying implementation provided by ArrayDeque. + */ +public class SimpleDeque implements Deque { + + final Deque deque; + + public SimpleDeque() { + deque = new ArrayDeque<>(); + } + + public SimpleDeque(Collection c) { + deque = new ArrayDeque<>(c); + } + + // ========== Object ========== + + public boolean equals(Object o) { + return deque.equals(o); + } + + public int hashCode() { + return deque.hashCode(); + } + + public String toString() { + return deque.toString(); + } + + // ========== Collection ========== + + public boolean add(E e) { + return deque.add(e); + } + + public boolean addAll(Collection c) { + return deque.addAll(c); + } + + public void clear() { + deque.clear(); + } + + public boolean contains(Object o) { + return deque.contains(o); + } + + public boolean containsAll(Collection c) { + return deque.containsAll(c); + } + + public boolean isEmpty() { + return deque.isEmpty(); + } + + public Iterator iterator() { + return deque.iterator(); + } + + public boolean remove(Object o) { + return deque.remove(o); + } + + public boolean removeAll(Collection c) { + return deque.removeAll(c); + } + + public boolean retainAll(Collection c) { + return deque.retainAll(c); + } + + public int size() { + return deque.size(); + } + + public Object[] toArray() { + return deque.toArray(); + } + + public T[] toArray(T[] a) { + return deque.toArray(a); + } + + // ========== Deque ========== + + public void addFirst(E e) { + deque.addFirst(e); + } + + public void addLast(E e) { + deque.addLast(e); + } + + public boolean offerFirst(E e) { + return deque.offerFirst(e); + } + + public boolean offerLast(E e) { + return deque.offerLast(e); + } + + public E removeFirst() { + return deque.removeFirst(); + } + + public E removeLast() { + return deque.removeLast(); + } + + public E pollFirst() { + return deque.pollFirst(); + } + + public E pollLast() { + return deque.pollLast(); + } + + public E getFirst() { + return deque.getFirst(); + } + + public E getLast() { + return deque.getLast(); + } + + public E peekFirst() { + return deque.peekFirst(); + } + + public E peekLast() { + return deque.peekLast(); + } + + public boolean removeFirstOccurrence(Object o) { + return deque.removeFirstOccurrence(o); + } + + public boolean removeLastOccurrence(Object o) { + return deque.removeLastOccurrence(o); + } + + public boolean offer(E e) { + return deque.offer(e); + } + + public E remove() { + return deque.remove(); + } + + public E poll() { + return deque.poll(); + } + + public E element() { + return deque.element(); + } + + public E peek() { + return deque.peek(); + } + + public void push(E e) { + deque.push(e); + } + + public E pop() { + return deque.pop(); + } + + public Iterator descendingIterator() { + return deque.descendingIterator(); + } +} diff --git a/test/jdk/java/util/SequencedCollection/SimpleList.java b/test/jdk/java/util/SequencedCollection/SimpleList.java new file mode 100644 index 0000000000000..f717bf05b4407 --- /dev/null +++ b/test/jdk/java/util/SequencedCollection/SimpleList.java @@ -0,0 +1,152 @@ +/* + * 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.util.*; + +/** + * A complete List implementation that inherits the reversed() method + * from List. Useful for testing ReverseOrderListView. + * Underlying implementation provided by ArrayList. + */ +public class SimpleList implements List { + + final List list; + + public SimpleList() { + list = new ArrayList<>(); + } + + public SimpleList(Collection c) { + list = new ArrayList<>(c); + } + + // ========== Object ========== + + public boolean equals(Object o) { + return list.equals(o); + } + + public int hashCode() { + return list.hashCode(); + } + + public String toString() { + return list.toString(); + } + + // ========== Collection ========== + + public boolean add(E e) { + return list.add(e); + } + + public boolean addAll(Collection c) { + return list.addAll(c); + } + + public void clear() { + list.clear(); + } + + public boolean contains(Object o) { + return list.contains(o); + } + + public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + public boolean isEmpty() { + return list.isEmpty(); + } + + public Iterator iterator() { + return list.iterator(); + } + + public boolean remove(Object o) { + return list.remove(o); + } + + public boolean removeAll(Collection c) { + return list.removeAll(c); + } + + public boolean retainAll(Collection c) { + return list.retainAll(c); + } + + public int size() { + return list.size(); + } + + public Object[] toArray() { + return list.toArray(); + } + + public T[] toArray(T[] a) { + return list.toArray(a); + } + + // ========== List ========== + + public void add(int index, E element) { + list.add(index, element); + } + + public boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + public E get(int index) { + return list.get(index); + } + + public int indexOf(Object o) { + return list.indexOf(o); + } + + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + public ListIterator listIterator() { + return list.listIterator(); + } + + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + public E remove(int index) { + return list.remove(index); + } + + public E set(int index, E element) { + return list.set(index, element); + } + + public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } +} diff --git a/test/jdk/java/util/SequencedCollection/SimpleSortedMap.java b/test/jdk/java/util/SequencedCollection/SimpleSortedMap.java new file mode 100644 index 0000000000000..4c439d51e28aa --- /dev/null +++ b/test/jdk/java/util/SequencedCollection/SimpleSortedMap.java @@ -0,0 +1,134 @@ +/* + * 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.util.*; + +/** + * A SortedMap implementation that does not implement NavigableMap. Useful for + * testing ReverseOrderSortedMapView. Underlying implementation provided by TreeMap. + */ +public class SimpleSortedMap implements SortedMap { + final SortedMap map; + + public SimpleSortedMap() { + map = new TreeMap<>(); + } + + public SimpleSortedMap(Comparator comparator) { + map = new TreeMap<>(comparator); + } + + public SimpleSortedMap(Map m) { + map = new TreeMap<>(m); + } + + // ========== Object ========== + + public boolean equals(Object o) { + return map.equals(o); + } + + public int hashCode() { + return map.hashCode(); + } + + public String toString() { + return map.toString(); + } + + // ========== Map ========== + + public void clear() { + map.clear(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public Set> entrySet() { + return map.entrySet(); + } + + public V get(Object key) { + return map.get(key); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public Set keySet() { + return map.keySet(); + } + + public V put(K key, V value) { + return map.put(key, value); + } + + public void putAll(Map m) { + map.putAll(m); + } + + public V remove(Object key) { + return map.remove(key); + } + + public int size() { + return map.size(); + } + + public Collection values() { + return map.values(); + } + + // ========== SortedMap ========== + + public Comparator comparator() { + return map.comparator(); + } + + public K firstKey() { + return map.firstKey(); + } + + public SortedMap headMap(K toKey) { + return map.headMap(toKey); + } + + public K lastKey() { + return map.lastKey(); + } + + public SortedMap subMap(K fromKey, K toKey) { + return map.subMap(fromKey, toKey); + } + + public SortedMap tailMap(K fromKey) { + return map.tailMap(fromKey); + } +} diff --git a/test/jdk/java/util/SequencedCollection/SimpleSortedSet.java b/test/jdk/java/util/SequencedCollection/SimpleSortedSet.java new file mode 100644 index 0000000000000..6ca2156a47d42 --- /dev/null +++ b/test/jdk/java/util/SequencedCollection/SimpleSortedSet.java @@ -0,0 +1,139 @@ +/* + * 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.util.*; + +/** + * A SortedSet implementation that does not implement NavigableSet. Useful for + * testing ReverseOrderSortedSetView. Underlying implementation provided by TreeSet. + */ +public class SimpleSortedSet implements SortedSet { + + final SortedSet set; + + public SimpleSortedSet() { + set = new TreeSet(); + } + + public SimpleSortedSet(Collection c) { + set = new TreeSet<>(c); + } + + public SimpleSortedSet(Comparator comparator) { + set = new TreeSet<>(comparator); + } + + // ========== Object ========== + + public boolean equals(Object o) { + return set.equals(o); + } + + public int hashCode() { + return set.hashCode(); + } + + public String toString() { + return set.toString(); + } + + // ========== Collection ========== + + public boolean add(E e) { + return set.add(e); + } + + public boolean addAll(Collection c) { + return set.addAll(c); + } + + public void clear() { + set.clear(); + } + + public boolean contains(Object o) { + return set.contains(o); + } + + public boolean containsAll(Collection c) { + return set.containsAll(c); + } + + public boolean isEmpty() { + return set.isEmpty(); + } + + public Iterator iterator() { + return set.iterator(); + } + + public boolean remove(Object o) { + return set.remove(o); + } + + public boolean removeAll(Collection c) { + return set.removeAll(c); + } + + public boolean retainAll(Collection c) { + return set.retainAll(c); + } + + public int size() { + return set.size(); + } + + public Object[] toArray() { + return set.toArray(); + } + + public T[] toArray(T[] a) { + return set.toArray(a); + } + + // ========== SortedSet ========== + + public Comparator comparator() { + return set.comparator(); + } + + public E first() { + return set.first(); + } + + public SortedSet headSet(E toElement) { + return set.headSet(toElement); + } + + public E last() { + return set.last(); + } + + public SortedSet subSet(E fromElement, E toElement) { + return set.subSet(fromElement, toElement); + } + + public SortedSet tailSet(E fromElement) { + return set.tailSet(fromElement); + } +} diff --git a/test/jdk/jdk/internal/util/ArraysSupport/Reverse.java b/test/jdk/jdk/internal/util/ArraysSupport/Reverse.java new file mode 100644 index 0000000000000..6e0ebd0625719 --- /dev/null +++ b/test/jdk/jdk/internal/util/ArraysSupport/Reverse.java @@ -0,0 +1,78 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8266571 + * @modules java.base/jdk.internal.util + * @run testng Reverse + * @summary Tests for ArraysSupport.reverse + */ + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.util.Arrays; +import jdk.internal.util.ArraysSupport; + +public class Reverse { + @DataProvider(name = "data") + public Object[][] data() { + return new Object[][][] { + // pairs of actual, expected + { + { }, + { } + }, { + { "a" }, + { "a" } + }, { + { "a", "b" }, + { "b", "a" } + }, { + { "a", "b", "c" }, + { "c", "b", "a" } + }, { + { "a", "b", "c", "d" }, + { "d", "c", "b", "a" } + }, { + { "a", "b", "c", "d", "e" }, + { "e", "d", "c", "b", "a" } + } + }; + } + + @Test(dataProvider = "data") + public void testReverse(Object[] actual, Object[] expected) { + Object[] r = ArraysSupport.reverse(actual); + assertSame(r, actual); + assertEquals(actual.length, expected.length); + for (int i = 0; i < actual.length; i++) { + assertEquals(actual[i], expected[i], + "mismatch: actual=" + Arrays.asList(actual) + + " expected=" + Arrays.asList(expected) + " at index " + i); + } + } +} diff --git a/test/langtools/tools/javac/api/TestJavacTaskScanner.java b/test/langtools/tools/javac/api/TestJavacTaskScanner.java index e8110396dacb2..e302247bd68fc 100644 --- a/test/langtools/tools/javac/api/TestJavacTaskScanner.java +++ b/test/langtools/tools/javac/api/TestJavacTaskScanner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -102,7 +102,7 @@ public void run() { System.out.println("#allMembers: " + numAllMembers); check(numTokens, "#Tokens", 1054); - check(numParseTypeElements, "#parseTypeElements", 158); + check(numParseTypeElements, "#parseTypeElements", 180); check(numAllMembers, "#allMembers", 52); } diff --git a/test/langtools/tools/javac/processing/model/type/BoundsTest.java b/test/langtools/tools/javac/processing/model/type/BoundsTest.java index b7e9121a9569e..f72fc6461b93c 100644 --- a/test/langtools/tools/javac/processing/model/type/BoundsTest.java +++ b/test/langtools/tools/javac/processing/model/type/BoundsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -70,7 +70,7 @@ public class BoundsTest { }; private static final String[] Single_supers = { "java.lang.Object", - "java.util.Collection" + "java.util.SequencedCollection" }; private static final String NoBounds_name = "NoBoundsTest.java";