From 5fface2dab5c8f9332a96ade5dddbb515ceee57c Mon Sep 17 00:00:00 2001 From: Chris Povirk Date: Tue, 23 Jul 2024 16:45:10 -0400 Subject: [PATCH] Annotate the sequenced collections for nullness. Compare https://github.com/jspecify/jdk/pull/65 --- .../java/util/SequencedCollection.java | 207 ++++++++++ .../share/classes/java/util/SequencedMap.java | 355 ++++++++++++++++++ .../share/classes/java/util/SequencedSet.java | 62 +++ 3 files changed, 624 insertions(+) create mode 100644 src/java.base/share/classes/java/util/SequencedCollection.java create mode 100644 src/java.base/share/classes/java/util/SequencedMap.java create mode 100644 src/java.base/share/classes/java/util/SequencedSet.java 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 00000000000..a9914caba51 --- /dev/null +++ b/src/java.base/share/classes/java/util/SequencedCollection.java @@ -0,0 +1,207 @@ +/* + * 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 org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** + * 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 + */ +@AnnotatedFor("nullness") +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 00000000000..98ba08ce1f0 --- /dev/null +++ b/src/java.base/share/classes/java/util/SequencedMap.java @@ -0,0 +1,355 @@ +/* + * 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 jdk.internal.util.NullableKeyValueHolder; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** + * 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 + */ +@AnnotatedFor("nullness") +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.@Nullable Entry firstEntry() { + var it = entrySet().iterator(); + return it.hasNext() ? new NullableKeyValueHolder<>(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.@Nullable Entry lastEntry() { + var it = reversed().entrySet().iterator(); + return it.hasNext() ? new NullableKeyValueHolder<>(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.@Nullable Entry pollFirstEntry() { + var it = entrySet().iterator(); + if (it.hasNext()) { + var entry = new NullableKeyValueHolder<>(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.@Nullable Entry pollLastEntry() { + var it = reversed().entrySet().iterator(); + if (it.hasNext()) { + var entry = new NullableKeyValueHolder<>(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 @Nullable 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 @Nullable V putLast(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Returns a {@code SequencedSet} view of this map's {@link #keySet keySet}. + * + * @implSpec + * The implementation in this interface returns a {@code SequencedSet} instance + * that behaves as follows. Its {@link SequencedSet#add add} and {@link + * SequencedSet#addAll addAll} methods throw {@link UnsupportedOperationException}. + * Its {@link SequencedSet#reversed reversed} method returns the {@link + * #sequencedKeySet sequencedKeySet} view of the {@link #reversed reversed} view of + * this map. Each of its other methods calls the corresponding method of the {@link + * #keySet keySet} view of this map. + * + * @return a {@code SequencedSet} view of this map's {@code keySet} + */ + default SequencedSet sequencedKeySet() { + class SeqKeySet extends AbstractMap.ViewCollection implements SequencedSet { + Collection view() { + return SequencedMap.this.keySet(); + } + public SequencedSet reversed() { + return SequencedMap.this.reversed().sequencedKeySet(); + } + public boolean equals(Object other) { + return view().equals(other); + } + public int hashCode() { + return view().hashCode(); + } + } + return new SeqKeySet(); + } + + /** + * Returns a {@code SequencedCollection} view of this map's {@link #values values} collection. + * + * @implSpec + * The implementation in this interface returns a {@code SequencedCollection} instance + * that behaves as follows. Its {@link SequencedCollection#add add} and {@link + * SequencedCollection#addAll addAll} methods throw {@link UnsupportedOperationException}. + * Its {@link SequencedCollection#reversed reversed} method returns the {@link + * #sequencedValues sequencedValues} view of the {@link #reversed reversed} view of + * this map. Its {@link Object#equals equals} and {@link Object#hashCode hashCode} methods + * are inherited from {@link Object}. Each of its other methods calls the corresponding + * method of the {@link #values values} view of this map. + * + * @return a {@code SequencedCollection} view of this map's {@code values} collection + */ + default SequencedCollection sequencedValues() { + class SeqValues extends AbstractMap.ViewCollection implements SequencedCollection { + Collection view() { + return SequencedMap.this.values(); + } + public SequencedCollection reversed() { + return SequencedMap.this.reversed().sequencedValues(); + } + } + return new SeqValues(); + } + + /** + * Returns a {@code SequencedSet} view of this map's {@link #entrySet entrySet}. + * + * @implSpec + * The implementation in this interface returns a {@code SequencedSet} instance + * that behaves as follows. Its {@link SequencedSet#add add} and {@link + * SequencedSet#addAll addAll} methods throw {@link UnsupportedOperationException}. + * Its {@link SequencedSet#reversed reversed} method returns the {@link + * #sequencedEntrySet sequencedEntrySet} view of the {@link #reversed reversed} view of + * this map. Each of its other methods calls the corresponding method of the {@link + * #entrySet entrySet} view of this map. + * + * @return a {@code SequencedSet} view of this map's {@code entrySet} + */ + default SequencedSet> sequencedEntrySet() { + class SeqEntrySet extends AbstractMap.ViewCollection> + implements SequencedSet> { + Collection> view() { + return SequencedMap.this.entrySet(); + } + public SequencedSet> reversed() { + return SequencedMap.this.reversed().sequencedEntrySet(); + } + public boolean equals(Object other) { + return view().equals(other); + } + public int hashCode() { + return view().hashCode(); + } + } + 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 00000000000..3cdd54dc797 --- /dev/null +++ b/src/java.base/share/classes/java/util/SequencedSet.java @@ -0,0 +1,62 @@ +/* + * 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 org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** + * 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 + */ +@AnnotatedFor("nullness") +public interface SequencedSet extends SequencedCollection, Set { + /** + * {@inheritDoc} + * + * @return a reverse-ordered view of this collection, as a {@code SequencedSet} + */ + SequencedSet reversed(); +}